#!/bin/bash # First-login one-shot setup for the user. # Installs Claude Code (official) + NVM + node LTS + vscode extensions. # Idempotent: creates ~/.first-login-done marker on success. # NOTE: do NOT use `set -u` here — nvm.sh references unbound vars. LOG="$HOME/.first-login.log" exec > >(tee -a "$LOG") 2>&1 echo "==> [$(date)] first-login setup starting" # Need network. Wait up to 60s for default route + DNS. for i in $(seq 1 30); do getent hosts github.com >/dev/null 2>&1 && break sleep 2 done if ! getent hosts github.com >/dev/null 2>&1; then echo "!! no network; aborting first-login setup (will retry next login)" exit 0 fi # --- Claude Code (official native installer) — runs FIRST so failures in # downstream NVM/node/etc. don't block claude installation. --- mkdir -p "$HOME/.local/bin" export PATH="$HOME/.local/bin:$PATH" if ! command -v claude >/dev/null 2>&1 && [[ ! -x "$HOME/.local/bin/claude" ]]; then echo "==> installing Claude Code via official installer" curl -fsSL https://claude.ai/install.sh | bash || { echo "!! claude install failed"; } fi # --- NVM (best effort; nvm.sh has unbound vars so isolate it) --- if [[ ! -s "$HOME/.nvm/nvm.sh" ]]; then echo "==> installing NVM" export NVM_DIR="$HOME/.nvm" curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash || \ echo "!! NVM install failed (continuing)" fi if [[ -s "$HOME/.nvm/nvm.sh" ]]; then export NVM_DIR="$HOME/.nvm" # nvm.sh trips `set -u` on STABLE/PROVIDED_VERSION; isolate in subshell. ( set +u # shellcheck disable=SC1091 . "$NVM_DIR/nvm.sh" if ! nvm ls --no-colors 2>/dev/null | grep -qE 'lts/'; then echo "==> installing node LTS" nvm install --lts || echo "!! node install failed" fi nvm use --lts >/dev/null 2>&1 || true ) || true # Symlink the resulting node/npm into ~/.local/bin so they're on PATH # for non-nvm shells. NODE_BIN_DIR="$(ls -d "$HOME"/.nvm/versions/node/v*/bin 2>/dev/null | sort -V | tail -1)" if [[ -n "$NODE_BIN_DIR" && -d "$NODE_BIN_DIR" ]]; then for bin in node npm npx; do [[ -x "$NODE_BIN_DIR/$bin" ]] && ln -sf "$NODE_BIN_DIR/$bin" "$HOME/.local/bin/$bin" done fi fi # --- VS Code extensions --- EXT_FILE=/etc/installer-vscode-extensions.txt if [[ -r "$EXT_FILE" ]] && command -v code >/dev/null 2>&1; then echo "==> installing VS Code extensions" while read -r ext; do [[ -z "$ext" || "$ext" =~ ^# ]] && continue echo " -> $ext" code --install-extension "$ext" --force >/dev/null 2>&1 || \ echo " (failed: $ext)" done < "$EXT_FILE" fi touch "$HOME/.first-login-done" echo "==> [$(date)] first-login setup done"