Shell environment: zsh login shell, persistence and git ownership fixes

Root's login shell is now /bin/zsh (5.9, staged in /boot/extra so rc.local
reinstalls it at boot). bash stays the fallback; both shells source one shared
env and alias file. Shell config consolidated on the flash drive at
/boot/config/shell/, installed by install.sh from both boot paths, which were
previously fighting over /root/.bashrc.

Four things that were silently broken are fixed:

- /boot/config/passwd cannot set root's shell at all — rc.S.cont reads it but
  hardcodes /bin/bash. The change is made from the go file instead, guarded so
  a missing zsh falls back to bash rather than locking root out.
- git refused to work in every project share (dubious ownership: owned by
  jazzymc, not root), which silently emptied every branch lookup. safe.directory
  entries added.
- /root/.gitconfig was in RAM, so the credential helper and store died at every
  reboot.
- Login shells lost the custom PATH, because /etc/zprofile hardcodes it after
  ~/.zshenv. Also fixed the literal backslash in the go file's shebang.

Documents the Shell Environment section and corrects the Boot Sequence, which
the go file change made out of date.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
jazzymcandClaude Opus 5 committed 2026-11-07 09:39:42 +00:00
1 parent 0a115a4220
commit 37c86313be
2 files changed
+106 -4

No files matched your search

+92 -4
View File
@@ -234,15 +234,103 @@ All projects at `/mnt/user/projects/`, opened as default folder in VS Code.
---
## Shell Environment
Root's login shell is **zsh 5.9**. bash stays as the fallback and as what scripts
use; both shells source the same environment and alias files, so they cannot drift.
| Property | Value |
|----------|-------|
| **Login shell** | `/bin/zsh`, set by `install.sh` — *not* by `/boot/config/passwd`, see Gotchas |
| **zsh package** | `/boot/extra/zsh-5.9-x86_64-1_slack15.0.txz` (Slackware 15.0 patches, md5 verified) |
| **Config source** | `/boot/config/shell/` on the flash drive |
| **Installer** | `bash /boot/config/shell/install.sh`, also the `reloadrc` alias |
| **Shell state** | `/mnt/user/appdata/shell/` — history, zsh completion dump, git credentials |
| **tmux** | `/mnt/user/appdata/tmux/tmux.conf` → `/root/.tmux.conf` (tmux 3.5a AppImage) |
| **SSH client** | `/boot/config/ssh/root/config` (`/root/.ssh` → `/boot/config/ssh/root`) |
### Files
| Installed | Source | Purpose |
|-----------|--------|---------|
| `/root/.zshrc` | `shell/zshrc` | Interactive zsh: prompt, completion, keys, history |
| `/root/.zshenv` | `shell/zshenv` | Environment for every zsh |
| `/root/.zprofile` | `shell/zprofile` | PATH for login zsh — see Gotchas |
| `/root/.bashrc` | `shell/bashrc` | Interactive bash |
| `/root/.bash_profile` | written by `install.sh` | Sources `.bashrc` |
| `/root/.gitconfig` | `shell/gitconfig` | Credential helper, `safe.directory` |
| — | `shell/env.sh` | PATH, `EDITOR`/`PAGER`, `HOST_LABEL`; sourced by both shells |
| — | `shell/aliases.sh` | Aliases and functions; sourced by both shells |
Prompt (both shells, same shape):
```
09:41 root@XTRM-U ~ampeco/backend (develop*)
❯
```
No framework, no plugin manager, no Nerd Font — `vcs_info` and zsh builtins only.
`*` is unstaged, `+` staged; the arrow turns red and the exit code is shown on
failure. Named directories shorten the share paths (`~projects`, `~ampeco`,
`~appdata`, `~vault`, `~flash`), and `cdpath` makes `cd backend` work from
anywhere, matching the `tmux-work` bookmarks.
### Persistence
`/` is a RAM rootfs, so every dotfile in `/root` is rebuilt at boot and anything
edited there is lost. The flash drive holds the real copies; `install.sh` lays
them down and is idempotent. It runs **twice**: from `/boot/config/go` before the
array mounts, so a shell is usable even if the array never comes up, and again
from `install-claude.sh` once `/mnt/user/appdata/shell/` is reachable. The zsh
binary itself comes back because `rc.local` installs `/boot/extra/*.txz` at boot.
Shell state goes on the NVMe cache pool, not the flash drive, which must not take
a write per command.
### Gotchas
1. **`/boot/config/passwd` cannot set root's shell.** `/etc/rc.d/rc.S.cont` reads
that file but hardcodes the shell:
`sed -i "s|^root:.*|root:x:0:0:$COMMENT:/root:/bin/bash|" /etc/passwd`.
The shell field is discarded, so editing it does nothing. `install.sh` changes
`/etc/passwd` afterwards instead, and only if `/bin/zsh` exists — if it is
missing it installs it from `/boot/extra`, and failing that leaves root on bash
and logs why, rather than locking root out of ssh and the web terminal. The
Unraid web terminal reads root's shell from the passwd file too
(`OpenTerminal.php`: `posix_getpwuid(0)['shell']`).
2. **`/etc/zprofile` clobbers PATH for login shells.** Slackware hardcodes
`PATH="/usr/local/bin:/usr/bin:/bin"` there, and zsh reads it *after*
`~/.zshenv`. A login shell — what ssh and the web terminal start — therefore
lost the custom PATH, and `sc-story`, `vault`, `tok` and `claude` fell off it.
Set in `~/.zprofile` (read after `/etc/zprofile`) and re-asserted at the top of
`~/.zshrc`, the last startup file zsh reads.
3. **git needs `safe.directory` for the project shares.** `/mnt/user/projects` is
owned by `jazzymc`, not root, so git returned *detected dubious ownership* and
every branch lookup came back empty. Entries for `/mnt/user/projects/*` and
`/mnt/user/appdata/*` are in the flash `gitconfig`; a trailing `/*` covers every
repository underneath (git 2.56).
### Reverting to bash
```bash
sed -i 's|^\(root:.*\):/bin/zsh$|\1:/bin/bash|' /etc/passwd # immediate
# then comment out the install.sh call in /boot/config/go to make it stick
```
---
## Boot Sequence
`/boot/config/go` triggers on Unraid boot:
1. **Wait for array** — polls for `/mnt/user/appdata/claude-code` (up to 5 min)
2. **Claude Code setup** — `/mnt/user/appdata/claude-code/install-claude.sh`
1. **Shell config** — `bash /boot/config/shell/install.sh`
- Installs root's dotfiles from `/boot/config/shell/` and sets `/bin/zsh` as the login shell
- Deliberately before the array wait, so a usable shell exists even if the array never mounts
2. **Wait for array** — polls for `/mnt/user/appdata/claude-code` (up to 5 min)
3. **Claude Code setup** — `/mnt/user/appdata/claude-code/install-claude.sh`
- Creates symlinks (`/root/.local/bin/claude`, `/root/.claude`, `/root/.claude.json`)
- Writes `.bashrc` with persistent npm PATH
3. **OpenVSCode Server** — `/mnt/user/appdata/openvscode/start.sh`
- Re-runs `install.sh`, now that `/mnt/user/appdata/shell/` is reachable
4. **OpenVSCode Server** — `/mnt/user/appdata/openvscode/start.sh`
- Kills any existing instance
- Starts on port 3100 with persistent config dir
- Sources Claude/Cooperator PATH for terminal sessions
+14
View File
@@ -2,6 +2,20 @@
**Purpose:** Major infrastructure events only. Minor changes are in git commit messages.
---
## 2026-11-07
### Host: zsh as root's login shell, and four silent failures fixed
- **[HOST]** Root's login shell is now `/bin/zsh` (zsh 5.9 from the Slackware 15.0 patches tree, md5 verified, staged in `/boot/extra/` so `rc.local` reinstalls it at every boot). It applies to ssh, the console and the Unraid web terminal, which reads root's shell from the passwd file (`OpenTerminal.php`: `posix_getpwuid(0)['shell']`). bash remains the fallback; both shells source one shared env and alias file so they cannot drift.
- **[HOST]** **`/boot/config/passwd` cannot set root's shell.** `/etc/rc.d/rc.S.cont` reads that file but hardcodes `/bin/bash`, discarding the shell field, so editing it does nothing. The change is made by `/boot/config/shell/install.sh`, which `/boot/config/go` calls after `rc.S.cont` has run. It installs zsh from `/boot/extra` if the binary is missing, and leaves root on bash if it genuinely cannot, rather than locking root out of ssh and the web terminal.
- **[HOST]** Shell config consolidated on the flash drive at `/boot/config/shell/` — `/` is a RAM rootfs, so `/root/.bashrc` was lost at every reboot and the custom `PATH` in it would have gone at the next restart. Two boot paths had also been fighting over the file: `install-claude.sh` overwrote it with a one-line version ~10s after the array mounted. Both now call the same installer. Shell state (history, zsh completion dump, git credentials) moved to `/mnt/user/appdata/shell/` on the cache pool, not the flash.
- **[FIXED]** **git refused to work in every project share.** `/mnt/user/projects` is owned by `jazzymc`, not root, so every git call returned *detected dubious ownership* and every branch lookup silently came back empty — the shell prompt, the Claude Code status line and plain `git status` alike. `safe.directory` entries added for `/mnt/user/projects/*` and `/mnt/user/appdata/*`; a trailing `/*` covers every repository underneath on git 2.56.
- **[FIXED]** `/root/.gitconfig` was in RAM, so `credential.helper = store` and the credential store itself died at every reboot. Both persist now, with the store at `/mnt/user/appdata/shell/git-credentials`.
- **[FIXED]** Login shells lost the custom PATH. Slackware's `/etc/zprofile` hardcodes `PATH="/usr/local/bin:/usr/bin:/bin"` and zsh reads it *after* `~/.zshenv`, so `sc-story`, `vault`, `tok` and `claude` were all missing from a real ssh login. Set in `~/.zprofile`, which is read after `/etc/zprofile`, and re-asserted in `~/.zshrc`.
- **[FIXED]** `/boot/config/go` line 1 was `#\!/bin/bash` — a literal backslash, so not a valid shebang. It had been booting on the caller's `/bin/sh` fallback the whole time.
- Also added, none of it previously configured: tmux keybindings and a status bar (prefix stays `C-b`), an `~/.ssh/config` with 30s keepalives and connection multiplexing (there was none at all), and a Claude Code status line whose context figure is coloured against the 120k checkpoint rule. Three hosts in `known_hosts` are in that ssh config but commented out, pending usernames: `192.168.10.103` (Nobara), `192.168.10.2` (CAP XL), and whichever host `proxmox_key` targets.
- Detail in `docs/12-DEVELOPMENT-ENVIRONMENT.md` § Shell Environment, and in the vault note `Unraid/terminal-shell-configuration.md`.
---
## 2026-11-03