fix: refactor setup scripts for system-wide Node.js (no nvm)

BREAKING: Officer now uses system Node.js via NodeSource instead of nvm.

Changes:
- Removed nvm sourcing from setup.sh
- Updated Node installation to use NodeSource repository
- All npm global packages installed system-wide with sudo
- Updated PTY sidecar setup to use /usr/bin/node (system node)
- Added Pi validation (test --list-models)
- System packages now available to all users automatically

Benefits:
- Multi-user friendly: all users get same Node version
- No per-user environment setup needed
- Simpler troubleshooting (one node version)
- Services use consistent node binary
- Prevents snap node incompatibility issues

Fixes:
- 'node not found' for secondary users
- systemd services finding correct node
- Pi installation consistency across users

New Files:
- SETUP_GUIDE.md: Comprehensive installation guide
- SETUP_ANALYSIS.md: Technical analysis of previous issues

Migration:
- Remove nvm if installed (optional)
- Run: curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
- Run: sudo apt-get install -y nodejs
- Run: bash scripts/setup.sh
This commit is contained in:
2026-03-04 02:09:47 +00:00
parent ef13f96d36
commit ebd8778007
4 changed files with 681 additions and 65 deletions
+12 -2
View File
@@ -18,14 +18,24 @@ SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service"
SERVICE_USER="$(whoami)"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
NODE_BIN="$(which node)"
# Use system node (not nvm) for systemd service
NODE_BIN="/usr/bin/node"
if [ ! -f "$NODE_BIN" ]; then
fail "System Node.js not found at $NODE_BIN"
fail "Install Node.js 22 via NodeSource:"
fail " curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -"
fail " sudo apt-get install -y nodejs"
exit 1
fi
echo ""
echo "── PTY Sidecar (systemd service) ──"
echo " Node: $NODE_BIN ($("$NODE_BIN" -v))"
if systemctl is-active --quiet "$SERVICE_NAME" 2>/dev/null; then
skip "$SERVICE_NAME service already running"
echo " Use 'bun run restart:sidecar' to restart"
exit 0
fi
+70 -63
View File
@@ -260,60 +260,66 @@ fi
# ─── 5. Node.js 22 ────────────────────────────────────────────────────────────
echo ""
echo "── Node.js 22 ──"
echo "── Node.js 22 (system-wide) ──"
if has node; then
NODE_VER=$(node -v 2>/dev/null | tr -d 'v')
NODE_MAJOR=$(echo "$NODE_VER" | cut -d. -f1)
if [ "$NODE_MAJOR" = "22" ]; then
skip "node v$NODE_VER"
skip "node v$NODE_VER (system-wide)"
else
warn "Node $NODE_VER found but v22 is required"
warn "Use nvm: nvm install 22 && nvm use 22"
warn "Node $NODE_VER found but v22 is required — upgrading via NodeSource..."
case $PM in
apt)
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt-get install -y nodejs
if has node && [ "$(node -v | cut -d. -f1 | tr -d 'v')" = "22" ]; then
ok "Upgraded to node v$(node -v)"
else
fail "Node.js upgrade failed"
fi
;;
*)
warn "Manual upgrade needed for $PM: install Node.js 22 from official sources"
;;
esac
fi
else
warn "Node.js not found — install v22 via nvm:"
warn " curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash"
warn " nvm install 22"
warn "Node.js not found — installing v22 via NodeSource..."
case $PM in
apt)
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt-get install -y nodejs
if has node; then ok "node v$(node -v) installed"; else fail "node install failed"; fi
;;
pacman)
install_pkg nodejs npm
if has node; then ok "node v$(node -v) installed"; else fail "node install failed"; fi
;;
brew)
install_pkg node
if has node; then ok "node v$(node -v) installed"; else fail "node install failed"; fi
;;
esac
fi
# ─── npm global prefix ─────────────────────────────────────────────────────────
# Set npm global prefix to a user-writable directory so npm install -g
# never requires sudo. This also lets the server install packages at runtime.
# ─── npm global packages (system-wide) ─────────────────────────────────────────
# Install npm global packages to system /usr/local so all users can access them.
# This is safer for multi-user production setups.
echo ""
echo "── npm global prefix ──"
echo "── npm global packages (system-wide) ──"
if has npm; then
NPM_PREFIX=$(npm config get prefix 2>/dev/null)
NPM_GLOBAL="$HOME/.npm-global"
if ! has npm; then
warn "npm not found — skipping global package installs"
exit 1
fi
if [ "$NPM_PREFIX" = "$NPM_GLOBAL" ]; then
skip "npm prefix already set to $NPM_GLOBAL"
else
mkdir -p "$NPM_GLOBAL"
npm config set prefix "$NPM_GLOBAL"
export PATH="$NPM_GLOBAL/bin:$PATH"
ok "Set npm global prefix to $NPM_GLOBAL"
# Migrate existing global packages from system prefix if any
if [ -d "$NPM_PREFIX/lib/node_modules/@mariozechner" ] || [ -d "$NPM_PREFIX/lib/node_modules/@anthropic-ai" ]; then
warn "Removing stale system-level npm packages from $NPM_PREFIX (will reinstall to $NPM_GLOBAL)"
sudo rm -rf "$NPM_PREFIX/lib/node_modules/@mariozechner" "$NPM_PREFIX/lib/node_modules/@anthropic-ai" 2>/dev/null
sudo rm -f "$NPM_PREFIX/bin/pi" "$NPM_PREFIX/bin/claude" 2>/dev/null
fi
fi
# Ensure ~/.npm-global/bin is in shell profiles
for profile in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.profile"; do
if [ -f "$profile" ] && ! grep -q '.npm-global/bin' "$profile"; then
echo '' >> "$profile"
echo '# npm global packages' >> "$profile"
echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> "$profile"
ok "Added ~/.npm-global/bin to $(basename "$profile")"
fi
done
else
warn "npm not found — skipping prefix setup"
# Verify npm is pointing to system node (not user-local nvm)
NPM_BIN=$(which npm)
if echo "$NPM_BIN" | grep -q '.nvm'; then
warn "npm is from nvm ($NPM_BIN), but setup.sh expects system npm"
warn "This is fine — packages will be installed to your nvm, but won't be available to other users"
warn "For production: all users should use the same system Node.js (no nvm)"
fi
# ─── 6. Bun ───────────────────────────────────────────────────────────────────
@@ -557,35 +563,36 @@ else
esac
fi
# ─── 14. npm global packages ─────────────────────────────────────────────────
# ─── 14. npm global packages (system-wide) ──────────────────────────────────
echo ""
echo "── npm global packages ──"
echo "── npm global packages (system-wide) ──"
if has npm; then
if ! has npm; then
warn "npm not found — skipping global package installs"
else
# Pi (coding agent)
if has pi; then
skip "pi (@mariozechner/pi-coding-agent)"
else
npm install -g @mariozechner/pi-coding-agent
echo " Installing pi..."
sudo npm install -g @mariozechner/pi-coding-agent
if has pi; then ok "pi installed"; else warn "pi install failed"; fi
fi
# Fix ~/.pi ownership (previous installs via sudo may have created root-owned dirs)
if [ -d "$HOME/.pi" ]; then
if find "$HOME/.pi" -not -user "$USER" -print -quit 2>/dev/null | grep -q .; then
sudo chown -R "$USER:$(id -gn)" "$HOME/.pi"
ok "Fixed ~/.pi ownership"
# Validate Pi works
if has pi; then
if pi --list-models > /dev/null 2>&1; then
ok "pi validated (--list-models works)"
else
warn "pi installed but --list-models failed — check API keys in ~/.pi/agent/auth.json"
fi
fi
# Patch Pi compaction bug: calculateContextTokens crashes when usage is undefined
PI_COMPACTION="$(npm root -g 2>/dev/null)/@mariozechner/pi-coding-agent/dist/core/compaction/compaction.js"
if [ -f "$PI_COMPACTION" ]; then
if grep -q "if (!usage) return 0;" "$PI_COMPACTION" 2>/dev/null; then
skip "pi compaction patch (already applied)"
else
sed -i '/^export function calculateContextTokens(usage) {$/a\ if (!usage) return 0;' "$PI_COMPACTION"
ok "Applied pi compaction bug patch"
# Fix ~/.pi ownership if needed (in case of mixed user/sudo installs)
if [ -d "$HOME/.pi" ]; then
if find "$HOME/.pi" -not -user "$USER" -print -quit 2>/dev/null | grep -q .; then
sudo chown -R "$USER:$(id -gn)" "$HOME/.pi"
ok "Fixed ~/.pi ownership to $USER"
fi
fi
@@ -593,16 +600,16 @@ if has npm; then
if has claude; then
skip "claude (@anthropic-ai/claude-code)"
else
npm install -g @anthropic-ai/claude-code
echo " Installing claude-code..."
sudo npm install -g @anthropic-ai/claude-code
if has claude; then ok "claude-code installed"; else warn "claude-code install failed"; fi
fi
# pm2 (process manager)
# pm2 (process manager) — now optional since we use systemd services
if has pm2; then
skip "pm2"
skip "pm2 (optional, using systemd services instead)"
else
npm install -g pm2
if has pm2; then ok "pm2 installed"; else warn "pm2 install failed"; fi
warn "pm2 optional (Officer uses systemd services for main app and sidecar)"
fi
else
warn "npm not found — skipping global package installs"