feat(ssh): full homelab alias set on the Mac + separate interactive key
config/ssh_homelab now carries the curated alias set from ~/bin/CLAUDE.md (pve nodes, machines, key CTs, switch, tailscale hosts). Interactive auth uses a NEW key, id_ed25519_homelab, because the existing id_ed25519 is rrsync-jailed on pve173 and sshd honours the first matching authorized_keys line per key — one key can't be both a jail and a shell there. - setup.sh generates id_ed25519_homelab (idempotent) - backup.sh pins the jailed key: -o IdentitiesOnly=yes -i $HOME/.ssh/id_ed25519 - scripts/authorize-mac-key.sh (run on steel141) appends the pubkey on every LAN host via claude's aliases; wired into run.sh - bedroom alias: seth -> root (matches steel141; was undocumented) Verified from the Mac: 12 aliases land as the right user@host; the backup key is refused a shell by rrsync (checked with id — rrsync whitelists true); backup.sh still runs. Offline at push time: bedroom thinkcentre seth-pi pinail openclaw2 — re-run authorize-mac-key.sh when they're up. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -33,6 +33,7 @@ laptop" shape).
|
|||||||
Pre-change inventory: `docs/reference/inventory-2026-09-15.md`.
|
Pre-change inventory: `docs/reference/inventory-2026-09-15.md`.
|
||||||
- Backup: `~/Music/Ableton` + `~/Documents` -> `pve173:/tank/backups/mac` nightly
|
- Backup: `~/Music/Ableton` + `~/Documents` -> `pve173:/tank/backups/mac` nightly
|
||||||
03:30 via launchd; key is rrsync-jailed; sanoid keeps history (tank_media template).
|
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).
|
- SparkFun PPP service left in place (macOS won't remove the sole service on a port; harmless).
|
||||||
|
|
||||||
## Conventions
|
## Conventions
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ Format: `YYYY-MM-DD: <decision> — <why>`
|
|||||||
- 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: **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: 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: **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.
|
- 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
|
## Deferred / Rejected
|
||||||
|
|||||||
+60
-3
@@ -1,5 +1,10 @@
|
|||||||
# Included from ~/.ssh/config. Key ~/.ssh/id_ed25519 is generated by setup.sh;
|
# Included from ~/.ssh/config (setup.sh adds the Include line). Two keys, both made by setup.sh:
|
||||||
# only pve173 is authorized so far (rrsync-restricted, for backup.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
|
Host pve173
|
||||||
HostName 192.168.0.173
|
HostName 192.168.0.173
|
||||||
User root
|
User root
|
||||||
@@ -12,12 +17,64 @@ Host pve197
|
|||||||
Host pve241
|
Host pve241
|
||||||
HostName 192.168.0.241
|
HostName 192.168.0.241
|
||||||
User root
|
User root
|
||||||
|
|
||||||
|
# --- Machines ---
|
||||||
Host steel141
|
Host steel141
|
||||||
HostName 192.168.0.141
|
HostName 192.168.0.141
|
||||||
User seth
|
User seth
|
||||||
Host bedroom
|
Host bedroom
|
||||||
HostName 192.168.0.235
|
HostName 192.168.0.235
|
||||||
|
User root
|
||||||
|
Host vdj-rig
|
||||||
|
HostName 192.168.0.143
|
||||||
User seth
|
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 *
|
Host *
|
||||||
IdentityFile ~/.ssh/id_ed25519
|
IdentityFile ~/.ssh/id_ed25519_homelab
|
||||||
|
StrictHostKeyChecking accept-new
|
||||||
ServerAliveInterval 30
|
ServerAliveInterval 30
|
||||||
|
|||||||
Executable
+12
@@ -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"
|
||||||
+3
-1
@@ -7,4 +7,6 @@ src=()
|
|||||||
for d in "$HOME/Music/Ableton" "$HOME/Documents"; do [[ -d $d ]] && src+=("$d"); done
|
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; }
|
[[ ${#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.
|
# 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:/
|
||||||
|
|||||||
@@ -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"'
|
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/
|
mkdir -p .backup/mac && rsync -a mac:.mac-setup-backup/ .backup/mac/
|
||||||
|
scripts/authorize-mac-key.sh
|
||||||
[[ ${1:-} == --no-tank ]] && exit 0
|
[[ ${1:-} == --no-tank ]] && exit 0
|
||||||
PUB=$(ssh mac cat .ssh/id_ed25519.pub)
|
PUB=$(ssh mac cat .ssh/id_ed25519.pub)
|
||||||
ssh pve173 "PUB='$PUB' bash -s" < scripts/tank-side.sh
|
ssh pve173 "PUB='$PUB' bash -s" < scripts/tank-side.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
|
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
|
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
|
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
|
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"
|
[[ -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"
|
{ echo 'Include ~/mac/config/ssh_homelab'; [[ -f $HOME/.ssh/config ]] && cat "$HOME/.ssh/config"; } > "$HOME/.ssh/config.new"
|
||||||
|
|||||||
Reference in New Issue
Block a user