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:
1 parent
0a115a4220
commit
37c86313be
2 files changed
+106
-4
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user