# 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: ```bash 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`). ## Phase 2 — Re-home data (rsync over the 10.100 link) - [ ] `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.