diff --git a/CLAUDE.md b/CLAUDE.md index 6fcd1cb..915d5ef 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -33,6 +33,7 @@ laptop" shape). Pre-change inventory: `docs/reference/inventory-2026-09-15.md`. - Backup: `~/Music/Ableton` + `~/Documents` -> `pve173:/tank/backups/mac` nightly 03:30 via launchd; key is rrsync-jailed; sanoid keeps history (tank_media template). +- ssh aliases (`config/ssh_homelab`, included from `~/.ssh/config`) mirror the `~/bin/CLAUDE.md` set. Two keys: `id_ed25519` = backup (rrsync-jailed on pve173), `id_ed25519_homelab` = interactive. `scripts/authorize-mac-key.sh` (from steel141) pushes the interactive key; re-run it when an offline host comes back. - SparkFun PPP service left in place (macOS won't remove the sole service on a port; harmless). ## Conventions diff --git a/DECISIONS.md b/DECISIONS.md index e2e02c0..b9f527d 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -19,6 +19,8 @@ Format: `YYYY-MM-DD: ` - 2026-09-15: **Unattended sudo = temporary `/etc/sudoers.d/mac-setup` (NOPASSWD), not pty password injection.** Piping the password into `ssh -tt` raced and hung 14 min on the real run (passed a fast test). The drop-in is validated before activation, removed on every run.sh exit path, and self-destructs on the Mac after 40 min. See run.sh / _install_sudoers.sh. - 2026-09-15: Mac's backup key on pve173 is **rrsync-restricted** (`command="/usr/bin/rrsync /tank/backups/mac"`) — a travelling laptop's key must never be unrestricted root on the tank host. Proven: an interactive command over that key is refused. - 2026-09-15: **Optional/cosmetic steps must be non-fatal under `set -e`.** The SparkFun removal (macOS refuses) and the GUI-domain `launchctl bootstrap` (flaky over SSH) both aborted the run until guarded with `|| warn`. Rule for this repo: anything that can legitimately fail without breaking the goal gets a best-effort guard. +- 2026-09-15: **Two ssh keys on the Mac: `id_ed25519` (backup, rrsync-jailed) + `id_ed25519_homelab` (interactive, `Host *` default).** sshd honours the first matching authorized_keys line per key, so one key can't be both jailed and a shell on pve173. `backup.sh` pins the jailed key with `-i` + `IdentitiesOnly`; verify with `id`, not `true` (rrsync whitelists `true` as a ping). Key push = `scripts/authorize-mac-key.sh` from steel141 (uses claude's aliases; pve nodes share `/etc/pve/priv/authorized_keys`, so one write covers all four). +- 2026-09-15: Mac ssh aliases mirror the curated set in `~/bin/CLAUDE.md` (not claude's full `~/.ssh/config`) — stale/undocumented entries (nixos, thinkpad-nano, mortdec-auth, vast) left out. `bedroom` = root, matching steel141's alias (was `seth`, undocumented). - 2026-09-15: `defaults displaysleep` on AC = 30 min, not 0 — display sleep doesn't stop Live's audio; only system `sleep` does. ## Deferred / Rejected diff --git a/config/ssh_homelab b/config/ssh_homelab index 30abfad..e85c914 100644 --- a/config/ssh_homelab +++ b/config/ssh_homelab @@ -1,5 +1,10 @@ -# Included from ~/.ssh/config. Key ~/.ssh/id_ed25519 is generated by setup.sh; -# only pve173 is authorized so far (rrsync-restricted, for backup.sh). +# Included from ~/.ssh/config (setup.sh adds the Include line). Two keys, both made by setup.sh: +# ~/.ssh/id_ed25519_homelab interactive — authorized on the hosts below by scripts/authorize-mac-key.sh +# ~/.ssh/id_ed25519 backup only — rrsync-jailed on pve173; backup.sh pins it with -i +# They must stay separate: sshd honours the FIRST matching authorized_keys line per key, so one key +# can't be both jailed (nightly rsync) and unrestricted (a shell) on pve173. + +# --- Proxmox nodes --- Host pve173 HostName 192.168.0.173 User root @@ -12,12 +17,64 @@ Host pve197 Host pve241 HostName 192.168.0.241 User root + +# --- Machines --- Host steel141 HostName 192.168.0.141 User seth Host bedroom HostName 192.168.0.235 + User root +Host vdj-rig + HostName 192.168.0.143 User seth +Host thinkcentre + HostName 192.168.0.211 + User seth +Host seth-pi + HostName 192.168.0.102 + User seth +Host pinail + HostName 192.168.0.203 + User pi +# TP-Link switch: password only (no pubkey support) — $HOMELAB_PASSWORD +Host switch + HostName 192.168.0.250 + User admin + PubkeyAuthentication no + +# --- Key CTs --- +Host caddy + HostName 192.168.0.185 + User root +Host openclaw2 + HostName 192.168.0.175 + User root +Host rtorrent + HostName 192.168.0.213 + User root +Host emby + HostName 192.168.0.215 + User root +Host mcsmanager + HostName 192.168.0.244 + User root +Host pihole + HostName 192.168.0.153 + User root +Host arr + HostName 192.168.0.158 + User root + +# --- Tailscale-only (dead until Tailscale is logged in on the Mac — docs/manual-checklist.md) --- +Host yoga + HostName 100.113.203.99 + User seth +Host bebop + HostName 100.78.2.106 + User seth + Host * - IdentityFile ~/.ssh/id_ed25519 + IdentityFile ~/.ssh/id_ed25519_homelab + StrictHostKeyChecking accept-new ServerAliveInterval 30 diff --git a/scripts/authorize-mac-key.sh b/scripts/authorize-mac-key.sh new file mode 100755 index 0000000..a6f8743 --- /dev/null +++ b/scripts/authorize-mac-key.sh @@ -0,0 +1,12 @@ +#!/bin/bash +# From steel141: put the Mac's interactive pubkey on every LAN host in config/ssh_homelab, via +# claude's own aliases (same names, same target users). Idempotent; called by run.sh, safe alone. +# steel141 itself is the one exception: seth's authorized_keys needs sudo from the claude account. +set -euo pipefail +PUB=$(ssh -o BatchMode=yes mac cat .ssh/id_ed25519_homelab.pub) +[[ $PUB == ssh-ed25519\ AAAA* ]] || { echo "no ~/.ssh/id_ed25519_homelab on the Mac — run setup.sh first"; exit 1; } +ADD='umask 077; mkdir -p ~/.ssh; if grep -qF "$PUB" ~/.ssh/authorized_keys 2>/dev/null; then echo "[skip] $H"; else echo "$PUB" >> ~/.ssh/authorized_keys; echo "[set] $H"; fi' +for H in pve112 pve173 pve197 pve241 bedroom vdj-rig thinkcentre seth-pi pinail caddy openclaw2 rtorrent emby mcsmanager pihole arr; do + ssh -o BatchMode=yes -o ConnectTimeout=5 "$H" "PUB='$PUB' H=$H bash -s" <<<"$ADD" || echo "[FAIL] $H" +done +sudo -n -u seth env PUB="$PUB" H=steel141 bash -c "$ADD" diff --git a/scripts/backup.sh b/scripts/backup.sh index 7c06dc8..db8e5a4 100755 --- a/scripts/backup.sh +++ b/scripts/backup.sh @@ -7,4 +7,6 @@ src=() for d in "$HOME/Music/Ableton" "$HOME/Documents"; do [[ -d $d ]] && src+=("$d"); done [[ ${#src[@]} -gt 0 ]] || { echo "nothing to back up yet"; exit 0; } # Remote path is relative to the rrsync root (/tank/backups/mac) set in root's authorized_keys on pve173. -exec /opt/homebrew/bin/rsync -a --delete -e 'ssh -o BatchMode=yes' "${src[@]}" root@192.168.0.173:/ +# -i pins the jailed key; the Host * default (id_ed25519_homelab) has a root shell on pve173 and must NOT be offered first. +# Check the jail holds: ssh -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 root@192.168.0.173 id -> "rrsync error: ... does not run rsync" (NOT `true`: rrsync whitelists it as a ping). +exec /opt/homebrew/bin/rsync -a --delete -e "ssh -o BatchMode=yes -o IdentitiesOnly=yes -i $HOME/.ssh/id_ed25519" "${src[@]}" root@192.168.0.173:/ diff --git a/scripts/run.sh b/scripts/run.sh index 1c6c5f7..c5177d6 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -27,6 +27,7 @@ cleanup; trap - EXIT ssh -o BatchMode=yes mac 'test ! -e /etc/sudoers.d/mac-setup && echo "[ok] temp sudoers removed" || echo "[WARN] temp sudoers STILL PRESENT"' mkdir -p .backup/mac && rsync -a mac:.mac-setup-backup/ .backup/mac/ +scripts/authorize-mac-key.sh [[ ${1:-} == --no-tank ]] && exit 0 PUB=$(ssh mac cat .ssh/id_ed25519.pub) ssh pve173 "PUB='$PUB' bash -s" < scripts/tank-side.sh diff --git a/scripts/setup.sh b/scripts/setup.sh index 94769ae..02f848a 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -65,6 +65,9 @@ mkdir -p "$HOME/.ssh"; chmod 700 "$HOME/.ssh" if [[ -f $HOME/.ssh/id_ed25519 ]]; then log skip "ssh key exists"; else ssh-keygen -t ed25519 -N '' -C "seth@$HOSTNAME_WANT" -f "$HOME/.ssh/id_ed25519" >/dev/null; log set "generated ~/.ssh/id_ed25519"; CHANGED=1 fi +if [[ -f $HOME/.ssh/id_ed25519_homelab ]]; then log skip "ssh homelab key exists"; else # interactive key; see config/ssh_homelab header + ssh-keygen -t ed25519 -N '' -C "seth@$HOSTNAME_WANT-homelab" -f "$HOME/.ssh/id_ed25519_homelab" >/dev/null; log set "generated ~/.ssh/id_ed25519_homelab"; CHANGED=1 +fi if [[ -f $HOME/.ssh/config ]] && grep -q '^Include ~/mac/config/ssh_homelab' "$HOME/.ssh/config"; then log skip "ssh config include"; else [[ -f $HOME/.ssh/config ]] && cp "$HOME/.ssh/config" "$BK/ssh_config-$TS" { echo 'Include ~/mac/config/ssh_homelab'; [[ -f $HOME/.ssh/config ]] && cat "$HOME/.ssh/config"; } > "$HOME/.ssh/config.new"