Files
esh-pfi-infrastructure/migration-plan.md
T
vh 6e2f80e18e docs(nh3-dev): add Mac Pro migration plan; retire vestigial volva/heid daemons
- migration-plan.md: workstation-only migration of nh3-dev's dev env to
  the M2 Ultra Mac Pro Rack (on-subnet, sidecars stay on Linux).
- nh3-dev README: drop volva from live sidecars + add retired note.
  volva.service/heid.service removed by infra-ops at heid's request
  (re-architected daemon->CC-orchestrator; volva.service was crash-looping).
2026-06-07 21:45:17 -07:00

7.1 KiB

nh3-dev → Mac Pro Rackmount migration plan

Status: planning — not started. Picked up later. Date framed: 2026-06-07 Audit snapshot: 2026-06-07 (the unsaved-work table goes stale fast — re-run before acting).


Locked decisions (operator, 2026-06-07)

Fork Decision Consequence
Chipset Apple Silicon (2023 Mac Pro Rack, M2 Ultra, ARM64) Docker images need arm64 variants; x86-only binaries need Rosetta 2. macOS, not Linux.
Scope Workstation only Move Hat 1 (dev env). Leave Hat 2 (fleet sidecars) on the existing Linux VM.
Network Racked in NH3, on the 10.100 subnet Direct fleet reach; no WireGuard needed. Gets a new IP — 10.100.10.50 stays with the sidecars.

The governing insight

nh3-dev currently wears two hats on one box, and only one follows you to the Mac:

  • Hat 1 — your dev environment (moves to the Mac): ~40 repos under ~/development, all of ~/.claude (skills, agents, auto-memory, settings — 1.8 GB), ~/.ssh (incl. infra-ops_ed25519), .zshrc → dotfiles/, .gitconfig, .cargo, .config, toolchain.
  • Hat 2 — load-bearing fleet sidecars (STAY on Linux, untouched): althing-forseti (file-based agent message-bus curator), the ttyd seats (ttyd-ro/ttyd-rw/ttyd-caddy), mead-hall :5173, NH3 egress SOCKS5 proxy socks5h://10.100.10.50:1080, NFS mounts to nh3-nas.
    • (2026-06-08: volva.service + heid.service removed — vestigial daemons from before Heid/Volva became CC-session orchestrators; not part of Hat 2. One fewer service in the split.)

Keeping Hat 2 on the existing VM (a) avoids porting Linux systemd/dante/Docker services to macOS launchd/Colima, and (b) un-tangles a latent bug: today a dev-box reboot kills fleet egress. After the split, the Mac reboots freely and nothing else notices. 10.100.10.50 stays bound to the sidecars.


⚠️ Phase 0 — Safety net (data-loss clock; do FIRST)

Why this matters: a "clean re-clone" on the Mac would silently drop unsaved work in ~15 repos. The correct cutover is rsync the working trees wholesale, not re-clone. Push everything pushable first as belt-and-suspenders.

Unsaved-work audit (snapshot 2026-06-07 — RE-RUN before acting)

Category Repos Risk on naive re-clone
Local-only (no upstream branch) bloom_music, comfy-dev, csm-expressiva, eitri-smithy, iris-friday, mead-hall May exist ONLY on this box → re-clone impossible. Must be copied.
Unpushed commits eshpfi-management (+14), heid (+4), brokkr-smithy, bifrost, chatterbox-fast, nevermore, ratatoskr, skaldsong, task-board, vastai-app (each +1) Lost on re-clone.
Uncommitted working changes vastai-app (21), voice-library (16), vor (7), heid/eitri-smithy (6), Worldtree/dvalin-smithy/task-board (4)… Never committed → lost on re-clone.
Stashes skaldsong (1), Worldtree (1) Stashes don't travel through remotes at all.

Re-run the audit:

cd ~/development
for d in */; do d=${d%/}; [ -d "$d/.git" ] || continue; cd "$d"
  dirty=$(git status --porcelain 2>/dev/null | grep -c .)
  stash=$(git stash list 2>/dev/null | wc -l | tr -d ' ')
  if git rev-parse '@{u}' >/dev/null 2>&1; then
    n=$(git rev-list '@{u}..HEAD' --count 2>/dev/null); [ "$n" != "0" ] && state="ahead-$n" || state=""
  else state="NO-UPSTREAM"; fi
  { [ "$dirty" != "0" ] || [ "$stash" != "0" ] || [ -n "$state" ]; } && printf '%-34s dirty=%-3s stash=%-2s %s\n' "$d" "$dirty" "$stash" "$state"
  cd ..; done

Phase 0 actions

  • Push every repo with a remote + unpushed commits.
  • For the 6 "no-upstream" repos: confirm whether a remote exists at all (git remote -v). Truly local-only → copy is the ONLY path; flag them.
  • Do NOT auto-commit dirty trees — rsync carries them verbatim; pushing is just the redundant backup.
  • Confirm nh3-dev will keep running headless after cutover (Hat 2 lives on).

Phase 1 — Provision the Mac (Homebrew baseline)

  • Xcode Command Line Tools + Homebrew.
  • brew install: uv, node, rustup, git, gh, tea, zellij, ripgrep, fd, jq, rsync. Install Claude Code.
  • zsh is already the macOS default — but prune Linux-isms from .zshrc (apt, /etc/profile.d sourcing, PLAYWRIGHT_BROWSERS_PATH=/opt/ms-playwright).
  • rsync (trees + dirty state): ~/development, ~/.claude (1.8 GB), ~/.ssh, ~/.config, ~/.gitconfig, ~/.cargo, dotfiles/.
  • chmod 600 ~/.ssh/* after copy (rsync can mangle perms; macOS ssh is strict).

Phase 3 — Repath for macOS ⚠️ (the silent breaker)

  • macOS home is /Users/lkraven, not /home/lkraven. Sweep all of ~/.claude + dotfiles for /home/lkraven and rewrite → $HOME / /Users/lkraven.
    • Already-known hits in ~/.claude/settings.json: the statusline-command.sh path and the althing/plugin path.
    • grep -rn '/home/lkraven' ~/.claude ~/dotfiles to find the rest.
  • Reinstall MCP/plugins (task-board from gitea vh/task-board, althing) and re-point absolute paths.
  • ARM64 reality:
    • Docker (if needed locally) → OrbStack/Colima + arm64 images.
    • Playwright → npx playwright install chromium (arm64 supported; no /opt/ms-playwright shared trick needed on a single-user box).
    • Python wheels are almost all arm64-mac now — only flag x86-only wheels (→ Rosetta 2).

Phase 4 — Fleet citizenship for the new box

  • Assign a new IP on 10.100 (NOT 10.100.10.50 — that's the sidecars'). Add to ~/.ssh/config, fleet DNS, and scripts/add-host.sh if tracking here.
  • WireGuard: not needed (no WG tunnel terminates on this box; Irvine reach was never a Hat-1 concern).
  • Stand up a backup for the Mac (Time Machine, or a new restic client → rest-server-nh3). The risk table is the argument for not skipping this — real local-only state travels with you.

Small decisions left (with recommendations)

  • NFS /mnt/smithy + /mnt/backup on the Mac? → Recommend /mnt/smithy yes (R14/smithy workflows use it), /mnt/backup no. (macOS NFS works, but the Synology export must list the Mac's new IP; macOS prefers autofs over fstab.)
  • iPad/web seat into the Mac's dev session? → Recommend defer. (ttyd seats stay pointed at the Linux zellij session; a Mac seat is a fresh launchd port — only if you still drive from iPad.)
  • Hostname → Avoid nh3-dev2 confusion; pick role-clear, e.g. nh3-mac / nh3-workbench.

/elitk takeaway

Treat this as "set up a new workbench and copy my stuff over the network," not "move the server." Push everything first, then copy the folders wholesale (don't re-clone — you'd lose unsaved work in ~15 repos), fix the /home//Users/ paths, and leave the little fleet-utility box humming untouched in the rack.

Next action when resumed: run Phase 0 (push pushable repos; confirm which of the 6 no-upstream repos are truly local-only). It's the only step with a data-loss clock; everything else waits on the hardware.