diff --git a/scripts/init-nix.sh b/scripts/init-nix.sh index 0c96397..7799598 100755 --- a/scripts/init-nix.sh +++ b/scripts/init-nix.sh @@ -4,8 +4,8 @@ set -euo pipefail echo "[init-nix] Starting Nix initialization..." NIX_INSTALL_URL="${NIX_INSTALL_URL:-https://nixos.org/nix/install}" -NIX_DOWNLOAD_MAX_TIME=300 -NIX_DOWNLOAD_SLEEP_INTERVAL=20 +NIX_DOWNLOAD_MAX_TIME="${NIX_DOWNLOAD_MAX_TIME:-300}" +NIX_DOWNLOAD_SLEEP_INTERVAL="${NIX_DOWNLOAD_SLEEP_INTERVAL:-20}" # --------------------------------------------------------------------------- # Detect whether we are inside a container (Docker/Podman/etc.) @@ -21,31 +21,33 @@ is_container() { # Ensure Nix binaries are on PATH (additive, never destructive) # --------------------------------------------------------------------------- ensure_nix_on_path() { - [[ -x /nix/var/nix/profiles/default/bin/nix ]] && \ + if [[ -x /nix/var/nix/profiles/default/bin/nix ]]; then PATH="/nix/var/nix/profiles/default/bin:$PATH" - - [[ -x "$HOME/.nix-profile/bin/nix" ]] && \ + fi + if [[ -x "$HOME/.nix-profile/bin/nix" ]]; then PATH="$HOME/.nix-profile/bin:$PATH" - - [[ -x /home/nix/.nix-profile/bin/nix ]] && \ + fi + if [[ -x /home/nix/.nix-profile/bin/nix ]]; then PATH="/home/nix/.nix-profile/bin:$PATH" - + fi + if [[ -d "$HOME/.local/bin" ]]; then + PATH="$HOME/.local/bin:$PATH" + fi export PATH } # --------------------------------------------------------------------------- -# Resolve nix binary path robustly (Arch-safe) +# Resolve nix binary path robustly (works across distros + Arch /usr/sbin) # --------------------------------------------------------------------------- resolve_nix_bin() { - local nix_cmd - + local nix_cmd="" nix_cmd="$(command -v nix 2>/dev/null || true)" [[ -n "$nix_cmd" ]] && { echo "$nix_cmd"; return 0; } # Prefer canonical locations [[ -x /usr/local/bin/nix ]] && { echo "/usr/local/bin/nix"; return 0; } [[ -x /usr/bin/nix ]] && { echo "/usr/bin/nix"; return 0; } - [[ -x /usr/sbin/nix ]] && { echo "/usr/sbin/nix"; return 0; } + [[ -x /usr/sbin/nix ]] && { echo "/usr/sbin/nix"; return 0; } # Arch package can land here [[ -x /bin/nix ]] && { echo "/bin/nix"; return 0; } [[ -x /nix/var/nix/profiles/default/bin/nix ]] && { @@ -56,6 +58,10 @@ resolve_nix_bin() { echo "$HOME/.nix-profile/bin/nix"; return 0; } + [[ -x "$HOME/.local/bin/nix" ]] && { + echo "$HOME/.local/bin/nix"; return 0; + } + [[ -x /home/nix/.nix-profile/bin/nix ]] && { echo "/home/nix/.nix-profile/bin/nix"; return 0; } @@ -64,7 +70,7 @@ resolve_nix_bin() { } # --------------------------------------------------------------------------- -# Ensure globally reachable nix symlinks (CI / non-login shells) +# Ensure globally reachable nix symlinks (CI / non-login shells) - root only # --------------------------------------------------------------------------- ensure_global_nix_symlinks() { local nix_bin="${1:-}" @@ -72,33 +78,77 @@ ensure_global_nix_symlinks() { [[ -z "$nix_bin" ]] && nix_bin="$(resolve_nix_bin 2>/dev/null || true)" if [[ -z "$nix_bin" || ! -x "$nix_bin" ]]; then - echo "[init-nix] WARNING: nix binary not found, cannot create symlinks." + echo "[init-nix] WARNING: nix binary not found, cannot create global symlinks." return 0 fi - mkdir -p /usr/local/bin || true + mkdir -p /usr/local/bin 2>/dev/null || true - ln -sf "$nix_bin" /usr/local/bin/nix && \ + if ln -sf "$nix_bin" /usr/local/bin/nix 2>/dev/null; then echo "[init-nix] Ensured /usr/local/bin/nix -> $nix_bin" + else + echo "[init-nix] WARNING: Failed to ensure /usr/local/bin/nix symlink." + fi + # Best-effort for these locations ln -sf "$nix_bin" /usr/bin/nix 2>/dev/null || true ln -sf "$nix_bin" /bin/nix 2>/dev/null || true } # --------------------------------------------------------------------------- -# Ensure Nix build group and users exist +# Ensure user-level nix symlink (works without root; CI-safe) +# --------------------------------------------------------------------------- +ensure_user_nix_symlink() { + local nix_bin="${1:-}" + + [[ -z "$nix_bin" ]] && nix_bin="$(resolve_nix_bin 2>/dev/null || true)" + + if [[ -z "$nix_bin" || ! -x "$nix_bin" ]]; then + echo "[init-nix] WARNING: nix binary not found, cannot create user symlink." + return 0 + fi + + mkdir -p "$HOME/.local/bin" 2>/dev/null || true + ln -sf "$nix_bin" "$HOME/.local/bin/nix" + + echo "[init-nix] Ensured $HOME/.local/bin/nix -> $nix_bin" + + # Make current process work immediately + PATH="$HOME/.local/bin:$PATH" + export PATH + + # Best-effort persist (helps interactive, harmless for CI) + if [[ -w "$HOME/.profile" ]] && ! grep -q 'init-nix.sh' "$HOME/.profile" 2>/dev/null; then + cat >>"$HOME/.profile" <<'EOF' + +# PATH for nix (added by package-manager init-nix.sh) +if [ -d "$HOME/.local/bin" ]; then + PATH="$HOME/.local/bin:$PATH" +fi +EOF + fi +} + +# --------------------------------------------------------------------------- +# Ensure Nix build group and users exist (build-users-group = nixbld) - root only # --------------------------------------------------------------------------- ensure_nix_build_group() { - getent group nixbld >/dev/null 2>&1 || groupadd -r nixbld + if ! getent group nixbld >/dev/null 2>&1; then + echo "[init-nix] Creating group 'nixbld'..." + groupadd -r nixbld + fi for i in $(seq 1 10); do - id "nixbld$i" >/dev/null 2>&1 || \ + if ! id "nixbld$i" >/dev/null 2>&1; then + echo "[init-nix] Creating build user nixbld$i..." useradd -r -g nixbld -G nixbld -s /usr/sbin/nologin "nixbld$i" + fi done } # --------------------------------------------------------------------------- # Download and run Nix installer with retry +# Usage: install_nix_with_retry daemon|no-daemon [run_as_user] # --------------------------------------------------------------------------- install_nix_with_retry() { local mode="$1" @@ -108,32 +158,46 @@ install_nix_with_retry() { case "$mode" in daemon) mode_flag="--daemon" ;; no-daemon) mode_flag="--no-daemon" ;; - *) echo "[init-nix] ERROR: invalid mode $mode"; exit 1 ;; + *) + echo "[init-nix] ERROR: Invalid mode '$mode' (expected 'daemon' or 'no-daemon')." + exit 1 + ;; esac installer="$(mktemp -t nix-installer.XXXXXX)" + # mktemp creates 0600; if we run as another user, it must be readable chmod 0644 "$installer" + echo "[init-nix] Downloading Nix installer from $NIX_INSTALL_URL (max ${NIX_DOWNLOAD_MAX_TIME}s)..." + while true; do if curl -fL "$NIX_INSTALL_URL" -o "$installer"; then + echo "[init-nix] Successfully downloaded installer to $installer" break fi + elapsed=$((elapsed + NIX_DOWNLOAD_SLEEP_INTERVAL)) - (( elapsed >= NIX_DOWNLOAD_MAX_TIME )) && { - echo "[init-nix] ERROR: failed to download installer" + echo "[init-nix] WARNING: Download failed. Retrying in ${NIX_DOWNLOAD_SLEEP_INTERVAL}s (elapsed ${elapsed}s)..." + + if (( elapsed >= NIX_DOWNLOAD_MAX_TIME )); then + echo "[init-nix] ERROR: Giving up after ${elapsed}s trying to download Nix installer." + rm -f "$installer" exit 1 - } + fi + sleep "$NIX_DOWNLOAD_SLEEP_INTERVAL" done if [[ -n "$run_as" ]]; then chown "$run_as:$run_as" "$installer" 2>/dev/null || true - if command -v sudo >/dev/null; then + echo "[init-nix] Running installer as user '$run_as' ($mode_flag)..." + if command -v sudo >/dev/null 2>&1; then sudo -u "$run_as" bash -lc "sh '$installer' $mode_flag" else su - "$run_as" -c "sh '$installer' $mode_flag" fi else + echo "[init-nix] Running installer as current user ($mode_flag)..." sh "$installer" "$mode_flag" fi @@ -144,9 +208,19 @@ install_nix_with_retry() { # Main # --------------------------------------------------------------------------- main() { + # Fast path: already available if command -v nix >/dev/null 2>&1; then echo "[init-nix] Nix already available on PATH: $(command -v nix)" - [[ "${EUID:-0}" -eq 0 ]] && ensure_global_nix_symlinks "$(resolve_nix_bin)" + ensure_nix_on_path + + # Root: ensure global symlinks for CI/non-login shells + if [[ "${EUID:-0}" -eq 0 ]]; then + ensure_global_nix_symlinks "$(resolve_nix_bin)" + else + # User: ensure we have a stable path too + ensure_user_nix_symlink "$(resolve_nix_bin)" + fi + return 0 fi @@ -154,39 +228,111 @@ main() { if command -v nix >/dev/null 2>&1; then echo "[init-nix] Nix found after PATH adjustment: $(command -v nix)" - [[ "${EUID:-0}" -eq 0 ]] && ensure_global_nix_symlinks "$(resolve_nix_bin)" + if [[ "${EUID:-0}" -eq 0 ]]; then + ensure_global_nix_symlinks "$(resolve_nix_bin)" + else + ensure_user_nix_symlink "$(resolve_nix_bin)" + fi return 0 fi local IN_CONTAINER=0 - is_container && IN_CONTAINER=1 + if is_container; then + IN_CONTAINER=1 + echo "[init-nix] Detected container environment." + else + echo "[init-nix] No container detected." + fi + # ------------------------------------------------------------------------- + # Container + root: dedicated "nix" user, single-user install + # ------------------------------------------------------------------------- if [[ "$IN_CONTAINER" -eq 1 && "${EUID:-0}" -eq 0 ]]; then + echo "[init-nix] Container + root: installing as 'nix' user (single-user)." + ensure_nix_build_group - id nix >/dev/null 2>&1 || useradd -m -r -g nixbld -s /bin/bash nix - mkdir -p /nix && chown nix:nixbld /nix - install_nix_with_retry no-daemon nix + + if ! id nix >/dev/null 2>&1; then + echo "[init-nix] Creating user 'nix'..." + local BASH_SHELL + BASH_SHELL="$(command -v bash || true)" + [[ -z "$BASH_SHELL" ]] && BASH_SHELL="/bin/sh" + useradd -m -r -g nixbld -s "$BASH_SHELL" nix + fi + + if [[ ! -d /nix ]]; then + echo "[init-nix] Creating /nix with owner nix:nixbld..." + mkdir -m 0755 /nix + chown nix:nixbld /nix + else + local current_owner current_group + current_owner="$(stat -c '%U' /nix 2>/dev/null || echo '?')" + current_group="$(stat -c '%G' /nix 2>/dev/null || echo '?')" + if [[ "$current_owner" != "nix" || "$current_group" != "nixbld" ]]; then + echo "[init-nix] Fixing /nix ownership from $current_owner:$current_group to nix:nixbld..." + chown -R nix:nixbld /nix + fi + fi + + install_nix_with_retry "no-daemon" "nix" + + ensure_nix_on_path + + # Ensure stable global symlink (so non-login shells find nix) + ensure_global_nix_symlinks "/home/nix/.nix-profile/bin/nix" + + # Ensure non-root users can traverse and execute nix user profile + if [[ -d /home/nix ]]; then + chmod o+rx /home/nix 2>/dev/null || true + fi + if [[ -d /home/nix/.nix-profile ]]; then + chmod -R o+rx /home/nix/.nix-profile 2>/dev/null || true + fi + + # ------------------------------------------------------------------------- + # Host (no container) + # ------------------------------------------------------------------------- else if command -v systemctl >/dev/null 2>&1; then - [[ "${EUID:-0}" -eq 0 ]] && ensure_nix_build_group - install_nix_with_retry daemon + echo "[init-nix] Host with systemd: using multi-user install (--daemon)." + if [[ "${EUID:-0}" -eq 0 ]]; then + ensure_nix_build_group + fi + install_nix_with_retry "daemon" else - install_nix_with_retry no-daemon + echo "[init-nix] No systemd detected: using single-user install (--no-daemon)." + if [[ "${EUID:-0}" -eq 0 ]]; then + # Root on a minimal host still benefits from nixbld users + ensure_nix_build_group + fi + install_nix_with_retry "no-daemon" fi fi + # ------------------------------------------------------------------------- + # After install: PATH + symlinks + # ------------------------------------------------------------------------- ensure_nix_on_path + local nix_bin_post + nix_bin_post="$(resolve_nix_bin 2>/dev/null || true)" + if [[ "${EUID:-0}" -eq 0 ]]; then - ensure_global_nix_symlinks "$(resolve_nix_bin)" + ensure_global_nix_symlinks "$nix_bin_post" + else + ensure_user_nix_symlink "$nix_bin_post" fi - command -v nix >/dev/null 2>&1 || { - echo "[init-nix] ERROR: nix not found after installation" + # Final verification (must succeed for CI) + if ! command -v nix >/dev/null 2>&1; then + echo "[init-nix] ERROR: nix not found after installation." + echo "[init-nix] DEBUG: resolved nix path = ${nix_bin_post:-}" + echo "[init-nix] DEBUG: PATH = $PATH" exit 1 - } + fi - echo "[init-nix] Nix successfully installed at: $(command -v nix)" + echo "[init-nix] Nix successfully available at: $(command -v nix)" + echo "[init-nix] Nix initialization complete." } main "$@"