diff --git a/full-ai-cluster/nixos/modules/common.nix b/full-ai-cluster/nixos/modules/common.nix index fab70d3da5..a0aabb153d 100644 --- a/full-ai-cluster/nixos/modules/common.nix +++ b/full-ai-cluster/nixos/modules/common.nix @@ -169,15 +169,28 @@ # node-register tooling). gh - # iter-5.5.0 (B-0848 Phase 2, Aaron 2026-05-27): bun for the - # node-local Claude Code agent (per .claude/rules/rule-0-no-sh-files.md - # — bun is Zeta's canonical TS/JS runtime, NOT nodejs). claude-code - # is published as an npm package but bun has high Node-compat AND - # bun's `bun install --global` + `bun x` work as npm/npx replacements. - # bun installs to /home/zeta/.bun/bin/ (per-user writable; NixOS - # /nix/store is RO). zeta-install.sh Step 6.95 does the bun install - # + interactive `claude login` + credential persistence + repo pre-clone. - bun + # iter-5.5.0 (B-0848 Phase 2, operator 2026-05-27 ALIGNMENT catch): + # `mise` is Zeta's canonical runtime version manager — the .mise.toml + # at repo root pins bun = "1.3" + dotnet + python = "3.14" + java + + # uv + actionlint + shellcheck + node + markdownlint-cli2 for ALL + # contexts (dev laptops + CI runners + devcontainers per GOVERNANCE + # §24 three-way parity). Cluster nodes inherit the SAME runtime + # pins via mise reading the same .mise.toml — single source of truth. + # + # Earlier draft of this PR added `bun` directly via nixpkgs which + # DRIFTED from the .mise.toml-pinned bun = "1.3" (would have run + # whatever bun version nixpkgs ships — could mismatch dev). Operator + # caught: "we already do this we've drifted for nixos for some + # reason for bun". + # + # zeta-install.sh Step 6.95a now invokes the canonical entry + # `tools/setup/install.sh` from the pre-cloned Zeta repo (which + # detects Linux, dispatches to linux.sh, which detects NixOS via + # /etc/NIXOS marker file and routes directly to common/mise.sh). + # Mise then installs bun + all other .mise.toml runtimes for the + # zeta user. Subsequent `bun install --global @anthropic-ai/claude-code` + # uses the mise-managed bun. + mise # iter-5.5 NetBIOS client tools — `samba` package brings # nmblookup/smbclient binaries so operator can query NetBIOS name @@ -192,23 +205,29 @@ samba ]; - # iter-5.5.0 (B-0848 Phase 2, Aaron 2026-05-27): user-local bun prefix - # on PATH for all login shells so `claude` (installed via - # `bun install --global` to /home/zeta/.bun/bin in zeta-install.sh - # Step 6.95) is reachable without manual PATH munging on first login. - # Per .claude/rules/rule-0-no-sh-files.md: bun is canonical TS/JS - # runtime in Zeta (NOT nodejs). + # iter-5.5.0 (B-0848 Phase 2, operator 2026-05-27 ALIGNMENT catch): + # PATH setup for both mise-managed runtimes AND bun's --global prefix. + # mise puts shims at ~/.local/share/mise/shims/ (which mise activation + # auto-prepends), AND bun's `bun install --global` lands binaries at + # ~/.bun/bin/ (where claude-code ends up). Both need to be on PATH. environment.sessionVariables = { BUN_INSTALL = "$HOME/.bun"; }; - # /etc/profile.d/ snippet so $HOME-relative PATH extension happens - # at shell-init time (NixOS sessionVariables stores literal `$HOME` - # which wouldn't expand correctly without per-shell init). + # /etc/profile.d/ snippet: mise activation + bun global bin. + # mise activate writes shims to ~/.local/share/mise/shims/ and adds + # them to PATH automatically; bun --global writes binaries to + # ~/.bun/bin/ which we add explicitly. $HOME expansion happens at + # shell-init time when this file sources. environment.etc."profile.d/zeta-user-paths.sh".text = '' - # iter-5.5.0 (B-0848): include user's bun-global bin on PATH so - # claude-code (and any other `bun install --global` user-scope - # binaries) are reachable without manual setup. + # iter-5.5.0 (B-0848): mise + bun PATH setup for the zeta user. + # mise activate sets up shims for all .mise.toml runtimes (bun, + # node, dotnet, python, java, uv, actionlint, shellcheck, etc.) + if command -v mise >/dev/null 2>&1; then + eval "$(mise activate bash)" + fi + # bun's `bun install --global` writes binaries here (claude-code + # lands at $HOME/.bun/bin/claude). if [ -d "$HOME/.bun/bin" ]; then export PATH="$HOME/.bun/bin:$PATH" fi diff --git a/full-ai-cluster/usb-nixos-installer/zeta-install.sh b/full-ai-cluster/usb-nixos-installer/zeta-install.sh index 4266a33231..1cc27b0452 100755 --- a/full-ai-cluster/usb-nixos-installer/zeta-install.sh +++ b/full-ai-cluster/usb-nixos-installer/zeta-install.sh @@ -1070,23 +1070,43 @@ fi if [ -d "$ZETA_HOME" ]; then echo "[iter-5.5.0] ── claude-code install + credential persistence (B-0848) ──" - # 6.95a — install claude-code globally for the zeta user via bun - # (per .claude/rules/rule-0-no-sh-files.md: bun is Zeta's canonical - # TS/JS runtime, NOT nodejs). bun is npm-compat: `bun install --global` - # installs npm packages; binaries land in $BUN_INSTALL/bin (~/.bun/bin - # by default). - echo "[iter-5.5.0] installing @anthropic-ai/claude-code via bun (writable prefix in zeta home)..." + # 6.95a — bootstrap runtimes via mise (.mise.toml single source of + # truth; operator 2026-05-27 ALIGNMENT catch). Then install claude-code + # via bun. We pre-clone the Zeta repo at Step 6.95d-equivalent BEFORE + # this step so .mise.toml is available; reorder vs the original PR. + # + # Pre-clone the repo NOW (was Step 6.95d; moved up so 6.95a can read + # .mise.toml). Subsequent 6.95d block is a no-op if directory exists. + if [ ! -d "$ZETA_HOME/Zeta" ]; then + echo "[iter-5.5.0] pre-cloning Zeta repo to $ZETA_HOME/Zeta..." + sudo -u "#$ZETA_UID" git clone https://github.com/Lucent-Financial-Group/Zeta.git "$ZETA_HOME/Zeta" 2>&1 | tail -3 || \ + echo "[iter-5.5.0] WARN: clone failed — claude-code install will also fail; can retry post-reboot" + fi + + # 6.95a-bootstrap — invoke the canonical install entry from the + # pre-cloned repo. tools/setup/install.sh dispatches to linux.sh which + # detects NixOS via /etc/NIXOS, skips apt, and routes to + # common/mise.sh — which reads .mise.toml and installs bun = + # "1.3" + other pinned runtimes for the zeta user. Same single source + # of truth dev laptops + CI runners + devcontainers use (GOVERNANCE + # §24 three-way-parity extended to NixOS cluster nodes). + if [ -d "$ZETA_HOME/Zeta" ]; then + echo "[iter-5.5.0] running tools/setup/install.sh (mise-based runtime bootstrap)..." + sudo HOME="$ZETA_HOME" -u "#$ZETA_UID" \ + bash -c "cd $ZETA_HOME/Zeta && tools/setup/install.sh" 2>&1 | tail -10 || \ + echo "[iter-5.5.0] WARN: install.sh FAILED — runtimes may be partial; can retry post-reboot via 'cd ~/Zeta && tools/setup/install.sh'" + fi + + # 6.95a-claude — install claude-code via the mise-managed bun. + # bun is now on the zeta user's PATH via mise activation; --global + # binaries land in ~/.bun/bin/ regardless of bun version. + echo "[iter-5.5.0] installing @anthropic-ai/claude-code via mise-managed bun..." sudo mkdir -p "$ZETA_HOME/.bun/bin" sudo chown -R "$ZETA_UID:$ZETA_GID" "$ZETA_HOME/.bun" - if command -v bun >/dev/null 2>&1; then - # BUN_INSTALL sets the writable per-user prefix. Run as zeta user - # via sudo -u so ownership starts correct. - sudo HOME="$ZETA_HOME" BUN_INSTALL="$ZETA_HOME/.bun" -u "#$ZETA_UID" \ - bun install --global @anthropic-ai/claude-code 2>&1 | tail -5 || \ - echo "[iter-5.5.0] WARN: bun install claude-code FAILED — can retry post-reboot" - else - echo "[iter-5.5.0] WARN: bun not on installer PATH; skipping (post-reboot has bun from common.nix)" - fi + # Source mise activation so the subshell finds bun via mise shims. + sudo HOME="$ZETA_HOME" BUN_INSTALL="$ZETA_HOME/.bun" -u "#$ZETA_UID" \ + bash -c 'eval "$(mise activate bash 2>/dev/null || true)"; bun install --global @anthropic-ai/claude-code' 2>&1 | tail -5 || \ + echo "[iter-5.5.0] WARN: bun install claude-code FAILED — can retry post-reboot via 'bun install --global @anthropic-ai/claude-code'" # 6.95b — interactive claude login (mirror iter-5.4.0 gh auth login) CLAUDE_BIN="$ZETA_HOME/.bun/bin/claude" @@ -1132,14 +1152,11 @@ if [ -d "$ZETA_HOME" ]; then echo "[iter-5.5.0] /root/.config/gh absent; nothing to persist (gh auth login was skipped?)" fi - # 6.95d — pre-clone Zeta repo for first-login operator convenience - if [ ! -d "$ZETA_HOME/Zeta" ]; then - echo "[iter-5.5.0] pre-cloning Zeta repo to $ZETA_HOME/Zeta (operator convenience)" - sudo -u "#$ZETA_UID" git clone https://github.com/Lucent-Financial-Group/Zeta.git "$ZETA_HOME/Zeta" 2>&1 | tail -3 || \ - echo "[iter-5.5.0] WARN: clone failed — operator can clone manually post-reboot" - fi + # 6.95d — pre-clone now happens up in 6.95a-bootstrap (before mise + # install needs .mise.toml). This sub-step is intentionally empty + # since the clone moved up. - echo "[iter-5.5.0] ── DONE — first login will have: gh + claude + bun + kubectl + helm + k9s + argocd on PATH; ~/Zeta cloned; ~/.config/{gh,claude} populated; ~/.bun/bin on PATH ──" + echo "[iter-5.5.0] ── DONE — first login will have: mise-managed runtimes (bun/node/python/dotnet/java/uv/etc) + gh + claude + kubectl + helm + k9s + argocd on PATH; ~/Zeta cloned (via 6.95a-bootstrap); ~/.config/{gh,claude} populated; ~/.bun/bin on PATH ──" else echo "[iter-5.5.0] $ZETA_HOME absent; skipping (nixos-install ordering changed?)" fi diff --git a/tools/setup/linux.sh b/tools/setup/linux.sh index 4bd86dc2cb..4aea388313 100755 --- a/tools/setup/linux.sh +++ b/tools/setup/linux.sh @@ -32,17 +32,38 @@ SETUP_DIR="$REPO_ROOT/tools/setup" # shellcheck source=tools/setup/common/curl-fetch.sh source "$SETUP_DIR/common/curl-fetch.sh" -# ── Detect apt availability (Debian/Ubuntu) ───────────────────────── -if ! command -v apt-get >/dev/null 2>&1; then - echo "error: this script currently supports Debian/Ubuntu only" - echo " RHEL/Fedora/Arch/Alpine support is backlogged — see" - echo " docs/research/build-machine-setup.md" - exit 1 +# ── Detect NixOS — skip apt step entirely, use systemPackages instead ── +# iter-5.5.0 (B-0848 Phase 2, operator 2026-05-27 ALIGNMENT catch): +# NixOS provides system packages declaratively via common.nix +# environment.systemPackages, NOT apt. The same install.sh entry-point +# can still bootstrap a NixOS cluster node by skipping the apt step and +# going directly to mise.sh for runtime version management. Operator +# framing: "our install.sh for mac and linux this is our default" — +# extending NixOS support keeps that default operational on cluster +# nodes invoked from zeta-install.sh Step 6.95a. +if [ -f /etc/NIXOS ]; then + echo "✓ NixOS detected — skipping apt (system packages declared in common.nix);" + echo " proceeding directly to mise + downstream runtime setup" + IS_NIXOS=1 +else + IS_NIXOS=0 + # ── Detect apt availability (Debian/Ubuntu) ───────────────────────── + if ! command -v apt-get >/dev/null 2>&1; then + echo "error: this script currently supports Debian/Ubuntu + NixOS" + echo " (NixOS detected via /etc/NIXOS marker file)" + echo " RHEL/Fedora/Arch/Alpine support is backlogged — see" + echo " docs/research/build-machine-setup.md" + exit 1 + fi fi # ── 1. apt packages (from manifest) ───────────────────────────────── +# NixOS handles system packages via common.nix systemPackages declarative; +# skip the entire apt step. mise + downstream still run. APT_MANIFEST="$SETUP_DIR/manifests/apt" -if [ -f "$APT_MANIFEST" ]; then +if [ "$IS_NIXOS" = 1 ]; then + echo "✓ skipping apt (NixOS — see common.nix environment.systemPackages)" +elif [ -f "$APT_MANIFEST" ]; then # Extract non-comment non-empty lines via awk (doesn't fail # under pipefail when manifest is all comments — unlike # `grep -vE` which exits 1 on no-match).