#!/bin/bash # First-login one-shot setup for the user. # Installs: Claude Code, NVM + node LTS, VS Code extensions, # and (if NIX_PACKAGES_FILE is present) nix user packages # (google-chrome, spotify, discord, localsend, mission-center). # 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 HTTPS connectivity. # Use curl with a short timeout rather than getent — getent blocks indefinitely # when the DNS nameserver is unreachable (e.g. QEMU's 10.0.2.3 not responding). for i in $(seq 1 20); do curl -fsSL --max-time 3 --connect-timeout 3 -o /dev/null https://api.github.com 2>/dev/null && break sleep 3 done if ! curl -fsSL --max-time 3 --connect-timeout 3 -o /dev/null https://api.github.com 2>/dev/null; then echo "!! no network; aborting first-login setup (will retry next login)" exit 0 fi # --- Claude Code (official native installer) --- 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 # --- Nix user packages (google-chrome, spotify, discord, etc.) --- # Present when running from the live ISO (written by build-live-iso.sh). # In the installed system the packages come from first-boot-nix.sh instead. # NOTE: nix packages are intentionally skipped in the live session — they # can total 3-4 GB which exceeds the tmpfs overlay on typical hardware. # They will be installed on the first login after the system is installed. NIX_PACKAGES_FILE="${NIX_PACKAGES_FILE:-/usr/local/libexec/nix-packages.list}" if [[ -r "$NIX_PACKAGES_FILE" ]] && command -v nix >/dev/null 2>&1; then remaining=$(df -k /nix 2>/dev/null | awk 'NR==2{print $4}') # Require at least 4GB free in /nix before attempting package install if [[ -n "$remaining" && "$remaining" -lt 4194304 ]]; then echo "==> skipping nix packages in live session (only ${remaining}KB free in /nix)" echo " packages will be installed on first login after installation" else echo "==> installing nix user packages from $NIX_PACKAGES_FILE" # Source nix profile.d scripts so PATH and env are set. for f in /etc/profile.d/nix*.sh; do # shellcheck disable=SC1090 [[ -r "$f" ]] && . "$f" done # Guard: nix-env --switch-profile creates a circular symlink when given # ~/.nix-profile as the target (it points to itself). Remove it so # 'nix profile add' can initialise a clean profile. if [[ -L "$HOME/.nix-profile" ]]; then _target=$(readlink "$HOME/.nix-profile") if [[ "$_target" == ".nix-profile" || "$_target" == "$HOME/.nix-profile" ]]; then echo " removing circular .nix-profile symlink" rm -f "$HOME/.nix-profile" fi unset _target fi # D-Bus session is available when autostarted from Cinnamon. if [[ -z "${DBUS_SESSION_BUS_ADDRESS:-}" ]]; then eval "$(dbus-launch --sh-syntax 2>/dev/null)" || true fi export NIXPKGS_ALLOW_UNFREE=1 mapfile -t pkgs < <(grep -vE '^\s*(#|$)' "$NIX_PACKAGES_FILE") if [[ ${#pkgs[@]} -gt 0 ]]; then echo " packages: ${pkgs[*]}" # 'nix profile install' is deprecated as of nix 2.x — use 'nix profile add' nix profile add --impure "${pkgs[@]}" 2>&1 || { echo "!! nix profile add failed (partial install may have succeeded)"; } fi fi 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" ( 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 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"