Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 40 additions & 21 deletions full-ai-cluster/nixos/modules/common.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
61 changes: 39 additions & 22 deletions full-ai-cluster/usb-nixos-installer/zeta-install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
35 changes: 28 additions & 7 deletions tools/setup/linux.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
Loading