refactor(init-nix): unify installer logic and add robust retry handling
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-container (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled

Refactored the Nix initialization script to reduce duplicated code and
centralize the installation workflow. The core functionality remains
unchanged, but all installer calls now use a unified function with retry
support to ensure resilient downloads in CI and container environments.

Key improvements:
- Added download retry logic (5 minutes total, 20-second intervals)
- Consolidated installer invocation into `install_nix_with_retry`
- Reduced code duplication across container/host install paths
- Preserved existing installation behavior for all environments
- Maintained `nixbld` group and build-user handling
- Improved consistency and readability without altering semantics

This prevents intermittent failures such as:
“curl: (6) Could not resolve host: nixos.org”
and ensures stable, deterministic Nix setup in CI pipelines.

https://chatgpt.com/share/693b13ce-fdcc-800f-a7bc-81c67478edff
This commit is contained in:
Kevin Veen-Birkenbach
2025-12-11 19:56:10 +01:00
parent 587cb2e516
commit 8bb99c99b7

View File

@@ -3,21 +3,22 @@ set -euo pipefail
echo "[init-nix] Starting Nix initialization..." echo "[init-nix] Starting Nix initialization..."
NIX_INSTALL_URL="${NIX_INSTALL_URL:-https://nixos.org/nix/install}"
NIX_DOWNLOAD_MAX_TIME=300 # 5 minutes
NIX_DOWNLOAD_SLEEP_INTERVAL=20 # 20 seconds
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Helper: detect whether we are inside a container (Docker/Podman/etc.) # Detect whether we are inside a container (Docker/Podman/etc.)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
is_container() { is_container() {
# Docker / Podman markers
if [[ -f /.dockerenv ]] || [[ -f /run/.containerenv ]]; then if [[ -f /.dockerenv ]] || [[ -f /run/.containerenv ]]; then
return 0 return 0
fi fi
# cgroup hints
if grep -qiE 'docker|container|podman|lxc' /proc/1/cgroup 2>/dev/null; then if grep -qiE 'docker|container|podman|lxc' /proc/1/cgroup 2>/dev/null; then
return 0 return 0
fi fi
# Environment variable used by some runtimes
if [[ -n "${container:-}" ]]; then if [[ -n "${container:-}" ]]; then
return 0 return 0
fi fi
@@ -26,219 +27,206 @@ is_container() {
} }
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Helper: ensure Nix binaries are on PATH (multi-user or single-user) # Ensure Nix binaries are on PATH (multi-user or single-user)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
ensure_nix_on_path() { ensure_nix_on_path() {
# Multi-user profile (daemon install)
if [[ -x /nix/var/nix/profiles/default/bin/nix ]]; then if [[ -x /nix/var/nix/profiles/default/bin/nix ]]; then
export PATH="/nix/var/nix/profiles/default/bin:${PATH}" export PATH="/nix/var/nix/profiles/default/bin:${PATH}"
fi fi
# Single-user profile (current user)
if [[ -x "${HOME}/.nix-profile/bin/nix" ]]; then if [[ -x "${HOME}/.nix-profile/bin/nix" ]]; then
export PATH="${HOME}/.nix-profile/bin:${PATH}" export PATH="${HOME}/.nix-profile/bin:${PATH}"
fi fi
# Single-user profile for dedicated "nix" user (container case)
if [[ -x /home/nix/.nix-profile/bin/nix ]]; then if [[ -x /home/nix/.nix-profile/bin/nix ]]; then
export PATH="/home/nix/.nix-profile/bin:${PATH}" export PATH="/home/nix/.nix-profile/bin:${PATH}"
fi fi
} }
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Helper: ensure Nix build group and users exist (build-users-group = nixbld) # Ensure Nix build group and users exist (build-users-group = nixbld)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
ensure_nix_build_group() { ensure_nix_build_group() {
# Ensure nixbld group (build-users-group for Nix)
if ! getent group nixbld >/dev/null 2>&1; then if ! getent group nixbld >/dev/null 2>&1; then
echo "[init-nix] Creating group 'nixbld'..." echo "[init-nix] Creating group 'nixbld'..."
groupadd -r nixbld groupadd -r nixbld
fi fi
# Ensure Nix build users (nixbld1..nixbld10) as members of nixbld
for i in $(seq 1 10); do for i in $(seq 1 10); do
if ! id "nixbld$i" >/dev/null 2>&1; then if ! id "nixbld$i" >/dev/null 2>&1; then
echo "[init-nix] Creating build user nixbld$i..." echo "[init-nix] Creating build user nixbld$i..."
# -r: system account, -g: primary group, -G: supplementary (ensures membership is listed)
useradd -r -g nixbld -G nixbld -s /usr/sbin/nologin "nixbld$i" useradd -r -g nixbld -G nixbld -s /usr/sbin/nologin "nixbld$i"
fi fi
done done
} }
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Fast path: Nix already available # Download and run Nix installer with retry
# Usage: install_nix_with_retry daemon|no-daemon [run_as_user]
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
if command -v nix >/dev/null 2>&1; then install_nix_with_retry() {
echo "[init-nix] Nix already available on PATH: $(command -v nix)" local mode="$1"
exit 0 local run_as="${2:-}"
fi local installer elapsed=0 mode_flag
ensure_nix_on_path case "${mode}" in
daemon) mode_flag="--daemon" ;;
no-daemon) mode_flag="--no-daemon" ;;
*)
echo "[init-nix] ERROR: Invalid mode '${mode}', expected 'daemon' or 'no-daemon'."
exit 1
;;
esac
if command -v nix >/dev/null 2>&1; then installer="$(mktemp -t nix-installer.XXXXXX)"
echo "[init-nix] Nix found after adjusting PATH: $(command -v nix)"
exit 0
fi
echo "[init-nix] Nix not found, starting installation logic..." echo "[init-nix] Downloading Nix installer from ${NIX_INSTALL_URL} with retry (max ${NIX_DOWNLOAD_MAX_TIME}s)..."
IN_CONTAINER=0 while true; do
if is_container; then if curl -fL "${NIX_INSTALL_URL}" -o "${installer}"; then
IN_CONTAINER=1 echo "[init-nix] Successfully downloaded Nix installer to ${installer}"
echo "[init-nix] Detected container environment." break
else
echo "[init-nix] No container detected."
fi
# ---------------------------------------------------------------------------
# Container + root: install Nix as dedicated "nix" user (single-user)
# ---------------------------------------------------------------------------
if [[ "${IN_CONTAINER}" -eq 1 && "${EUID:-0}" -eq 0 ]]; then
echo "[init-nix] Running as root inside a container using dedicated 'nix' user."
# Ensure build group/users for Nix
ensure_nix_build_group
# Ensure "nix" user (home at /home/nix)
if ! id nix >/dev/null 2>&1; then
echo "[init-nix] Creating user 'nix'..."
# Resolve a valid shell path across distros:
# - Debian/Ubuntu: /bin/bash
# - Arch: /usr/bin/bash (often symlinked)
# Fall back to /bin/sh on ultra-minimal systems.
BASH_SHELL="$(command -v bash || true)"
if [[ -z "${BASH_SHELL}" ]]; then
BASH_SHELL="/bin/sh"
fi fi
useradd -m -r -g nixbld -s "${BASH_SHELL}" nix
fi
# Ensure /nix exists and is writable by the "nix" user. local curl_exit=$?
# echo "[init-nix] WARNING: Failed to download Nix installer (curl exit code ${curl_exit})."
# In some base images (or previous runs), /nix may already exist and be
# owned by root. In that case the Nix single-user installer will abort with: elapsed=$((elapsed + NIX_DOWNLOAD_SLEEP_INTERVAL))
# if (( elapsed >= NIX_DOWNLOAD_MAX_TIME )); then
# "directory /nix exists, but is not writable by you" echo "[init-nix] ERROR: Giving up after ${elapsed}s trying to download Nix installer."
# rm -f "${installer}"
# To keep container runs idempotent and robust, we always enforce exit 1
# ownership nix:nixbld here. fi
if [[ ! -d /nix ]]; then
echo "[init-nix] Creating /nix with owner nix:nixbld..." echo "[init-nix] Retrying in ${NIX_DOWNLOAD_SLEEP_INTERVAL}s (elapsed: ${elapsed}s/${NIX_DOWNLOAD_MAX_TIME}s)..."
mkdir -m 0755 /nix sleep "${NIX_DOWNLOAD_SLEEP_INTERVAL}"
chown nix:nixbld /nix done
else
current_owner="$(stat -c '%U' /nix 2>/dev/null || echo '?')" if [[ -n "${run_as}" ]]; then
current_group="$(stat -c '%G' /nix 2>/dev/null || echo '?')" echo "[init-nix] Running installer as user '${run_as}' with mode '${mode}'..."
if [[ "${current_owner}" != "nix" || "${current_group}" != "nixbld" ]]; then if command -v sudo >/dev/null 2>&1; then
echo "[init-nix] /nix already exists with owner ${current_owner}:${current_group} fixing to nix:nixbld..." sudo -u "${run_as}" bash -lc "sh '${installer}' ${mode_flag}"
chown -R nix:nixbld /nix
else else
echo "[init-nix] /nix already exists with correct owner nix:nixbld." su - "${run_as}" -c "sh '${installer}' ${mode_flag}"
fi fi
if [[ ! -w /nix ]]; then
echo "[init-nix] WARNING: /nix is still not writable after chown; Nix installer may fail."
fi
fi
# Run Nix single-user installer as "nix"
echo "[init-nix] Installing Nix as user 'nix' (single-user, --no-daemon)..."
if command -v sudo >/dev/null 2>&1; then
sudo -u nix bash -lc 'sh <(curl -L https://nixos.org/nix/install) --no-daemon'
else else
su - nix -c 'sh <(curl -L https://nixos.org/nix/install) --no-daemon' echo "[init-nix] Running installer as current user with mode '${mode}'..."
sh "${installer}" "${mode_flag}"
fi fi
# After installation, expose nix to root via PATH and symlink rm -f "${installer}"
ensure_nix_on_path }
if [[ -x /home/nix/.nix-profile/bin/nix ]]; then # ---------------------------------------------------------------------------
if [[ ! -e /usr/local/bin/nix ]]; then # Main
echo "[init-nix] Creating /usr/local/bin/nix symlink -> /home/nix/.nix-profile/bin/nix" # ---------------------------------------------------------------------------
ln -s /home/nix/.nix-profile/bin/nix /usr/local/bin/nix main() {
fi # Fast path: Nix already available
if command -v nix >/dev/null 2>&1; then
echo "[init-nix] Nix already available on PATH: $(command -v nix)"
return 0
fi fi
ensure_nix_on_path ensure_nix_on_path
if command -v nix >/dev/null 2>&1; then if command -v nix >/dev/null 2>&1; then
echo "[init-nix] Nix successfully installed (container mode) at: $(command -v nix)" echo "[init-nix] Nix found after adjusting PATH: $(command -v nix)"
else return 0
echo "[init-nix] WARNING: Nix installation finished in container, but 'nix' is still not on PATH."
fi fi
# Optionally add PATH hints to /etc/profile (best effort) echo "[init-nix] Nix not found, starting installation logic..."
if [[ -w /etc/profile ]]; then
if ! grep -q 'Nix profiles' /etc/profile 2>/dev/null; then
cat <<'EOF' >> /etc/profile
# Nix profiles (added by package-manager init-nix.sh) local IN_CONTAINER=0
if [ -d /nix/var/nix/profiles/default/bin ]; then if is_container; then
PATH="/nix/var/nix/profiles/default/bin:$PATH" IN_CONTAINER=1
fi echo "[init-nix] Detected container environment."
if [ -d "$HOME/.nix-profile/bin" ]; then else
PATH="$HOME/.nix-profile/bin:$PATH" echo "[init-nix] No container detected."
fi
EOF
echo "[init-nix] Appended Nix PATH setup to /etc/profile (container mode)."
fi
fi fi
echo "[init-nix] Nix initialization complete (container root mode)." # -------------------------------------------------------------------------
exit 0 # Container + root: dedicated "nix" user, single-user install
fi # -------------------------------------------------------------------------
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
# Non-container or non-root container: normal installer paths
# --------------------------------------------------------------------------- if ! id nix >/dev/null 2>&1; then
if [[ "${IN_CONTAINER}" -eq 0 ]]; then echo "[init-nix] Creating user 'nix'..."
# Real host local BASH_SHELL
if command -v systemctl >/dev/null 2>&1; then BASH_SHELL="$(command -v bash || true)"
echo "[init-nix] Host with systemd using multi-user install (--daemon)." [[ -z "${BASH_SHELL}" ]] && BASH_SHELL="/bin/sh"
if [[ "${EUID:-0}" -eq 0 ]]; then useradd -m -r -g nixbld -s "${BASH_SHELL}" nix
# Prepare build-users-group for Nix daemon installs
ensure_nix_build_group
fi fi
sh <(curl -L https://nixos.org/nix/install) --daemon
else
if [[ "${EUID:-0}" -eq 0 ]]; then
echo "[init-nix] WARNING: Running as root without systemd on host."
echo "[init-nix] Falling back to single-user install (--no-daemon), but this is not recommended."
# IMPORTANT: This is where Debian/Ubuntu inside your CI end up. if [[ ! -d /nix ]]; then
# We must ensure 'nixbld' exists before running the installer, echo "[init-nix] Creating /nix with owner nix:nixbld..."
# otherwise modern Nix fails with: "the group 'nixbld' ... does not exist". mkdir -m 0755 /nix
ensure_nix_build_group chown nix:nixbld /nix
sh <(curl -L https://nixos.org/nix/install) --no-daemon
else else
echo "[init-nix] Non-root host without systemd using single-user install (--no-daemon)." local current_owner current_group
# Non-root cannot create nixbld group; rely on upstream defaults current_owner="$(stat -c '%U' /nix 2>/dev/null || echo '?')"
sh <(curl -L https://nixos.org/nix/install) --no-daemon 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
if [[ ! -w /nix ]]; then
echo "[init-nix] WARNING: /nix is not writable after chown; Nix installer may fail."
fi
fi fi
fi
else install_nix_with_retry "no-daemon" "nix"
ensure_nix_on_path
if [[ -x /home/nix/.nix-profile/bin/nix && ! -e /usr/local/bin/nix ]]; then
echo "[init-nix] Creating /usr/local/bin/nix symlink -> /home/nix/.nix-profile/bin/nix"
ln -s /home/nix/.nix-profile/bin/nix /usr/local/bin/nix
fi
# -------------------------------------------------------------------------
# Host (no container)
# -------------------------------------------------------------------------
elif [[ "${IN_CONTAINER}" -eq 0 ]]; then
if command -v systemctl >/dev/null 2>&1; then
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
if [[ "${EUID:-0}" -eq 0 ]]; then
echo "[init-nix] Host without systemd as root using single-user install (--no-daemon)."
ensure_nix_build_group
else
echo "[init-nix] Host without systemd as non-root using single-user install (--no-daemon)."
fi
install_nix_with_retry "no-daemon"
fi
# -------------------------------------------------------------------------
# Container, but not root (rare) # Container, but not root (rare)
echo "[init-nix] Container as non-root user using single-user install (--no-daemon)." # -------------------------------------------------------------------------
sh <(curl -L https://nixos.org/nix/install) --no-daemon else
fi echo "[init-nix] Container as non-root using single-user install (--no-daemon)."
install_nix_with_retry "no-daemon"
fi
# --------------------------------------------------------------------------- # -------------------------------------------------------------------------
# After installation: fix PATH (runtime + shell profiles) # After installation: PATH + /etc/profile
# --------------------------------------------------------------------------- # -------------------------------------------------------------------------
ensure_nix_on_path ensure_nix_on_path
if ! command -v nix >/dev/null 2>&1; then if ! command -v nix >/dev/null 2>&1; then
echo "[init-nix] WARNING: Nix installation finished, but 'nix' is still not on PATH." echo "[init-nix] WARNING: Nix installation finished, but 'nix' is still not on PATH."
echo "[init-nix] You may need to source your shell profile manually." echo "[init-nix] You may need to source your shell profile manually."
exit 0 else
fi echo "[init-nix] Nix successfully installed at: $(command -v nix)"
fi
echo "[init-nix] Nix successfully installed at: $(command -v nix)" if [[ -w /etc/profile ]] && ! grep -q 'Nix profiles' /etc/profile 2>/dev/null; then
# Update global /etc/profile if writable (helps especially on minimal systems)
if [[ -w /etc/profile ]]; then
if ! grep -q 'Nix profiles' /etc/profile 2>/dev/null; then
cat <<'EOF' >> /etc/profile cat <<'EOF' >> /etc/profile
# Nix profiles (added by package-manager init-nix.sh) # Nix profiles (added by package-manager init-nix.sh)
@@ -251,6 +239,8 @@ fi
EOF EOF
echo "[init-nix] Appended Nix PATH setup to /etc/profile" echo "[init-nix] Appended Nix PATH setup to /etc/profile"
fi fi
fi
echo "[init-nix] Nix initialization complete." echo "[init-nix] Nix initialization complete."
}
main "$@"