diff --git a/packaging/arch/PKGBUILD b/packaging/arch/PKGBUILD index 7711427..fec7261 100644 --- a/packaging/arch/PKGBUILD +++ b/packaging/arch/PKGBUILD @@ -51,8 +51,8 @@ package() { "$pkgdir/usr/bin/pkgmgr" # Install Nix init helper - install -Dm0755 "scripts/init-nix.sh" \ - "$pkgdir/usr/lib/package-manager/init-nix.sh" + install -Dm0755 "scripts/nix/init.sh" \ + "$pkgdir/usr/lib/package-manager/nix/init.sh" # Install the full repository into /usr/lib/package-manager mkdir -p "$pkgdir/usr/lib/package-manager" diff --git a/packaging/arch/package-manager.install b/packaging/arch/package-manager.install index 3ed7da1..65f858b 100644 --- a/packaging/arch/package-manager.install +++ b/packaging/arch/package-manager.install @@ -1,9 +1,9 @@ post_install() { - /usr/lib/package-manager/init-nix.sh || echo ">>> ERROR: /usr/lib/package-manager/init-nix.sh not found or not executable." + /usr/lib/package-manager/nix/init.sh || echo ">>> ERROR: /usr/lib/package-manager/nix/init.sh not found or not executable." } post_upgrade() { - /usr/lib/package-manager/init-nix.sh || echo ">>> ERROR: /usr/lib/package-manager/init-nix.sh not found or not executable." + /usr/lib/package-manager/nix/init.sh || echo ">>> ERROR: /usr/lib/package-manager/nix/init.sh not found or not executable." } post_remove() { diff --git a/packaging/debian/postinst b/packaging/debian/postinst index 17cc3c6..3f416b3 100755 --- a/packaging/debian/postinst +++ b/packaging/debian/postinst @@ -3,7 +3,7 @@ set -e case "$1" in configure) - /usr/lib/package-manager/init-nix.sh || echo ">>> ERROR: /usr/lib/package-manager/init-nix.sh not found or not executable." + /usr/lib/package-manager/nix/init.sh || echo ">>> ERROR: /usr/lib/package-manager/nix/init.sh not found or not executable." ;; esac diff --git a/packaging/debian/rules b/packaging/debian/rules index 17e3a73..af6a5d6 100755 --- a/packaging/debian/rules +++ b/packaging/debian/rules @@ -32,8 +32,9 @@ override_dh_auto_install: debian/package-manager/usr/bin/pkgmgr # Install shared Nix init script - install -m0755 scripts/init-nix.sh \ - debian/package-manager/usr/lib/package-manager/init-nix.sh + install -d debian/package-manager/usr/lib/package-manager/nix + install -m0755 scripts/nix/init.sh \ + debian/package-manager/usr/lib/package-manager/nix/init.sh # Copy full project source into /usr/lib/package-manager, # but do not include the debian/ directory itself. diff --git a/packaging/fedora/package-manager.spec b/packaging/fedora/package-manager.spec index 1afbe8f..083a5a4 100644 --- a/packaging/fedora/package-manager.spec +++ b/packaging/fedora/package-manager.spec @@ -12,7 +12,7 @@ BuildArch: noarch # NOTE: # Nix is a runtime requirement, but it is *not* declared here as a hard # RPM dependency, because many distributions do not ship a "nix" RPM. -# Instead, Nix is installed and initialized by init-nix.sh, which is +# Instead, Nix is installed and initialized by nix/init.sh, which is # called in the %post scriptlet below. %description @@ -22,7 +22,7 @@ manager via a local Nix flake: nix run /usr/lib/package-manager#pkgmgr -- ... Nix is a runtime requirement and is installed/initialized by the -init-nix.sh helper during package installation if it is not yet +nix/init.sh helper during package installation if it is not yet available on the system. %prep @@ -45,7 +45,8 @@ cp -a . %{buildroot}/usr/lib/package-manager/ install -m0755 scripts/pkgmgr-wrapper.sh %{buildroot}%{_bindir}/pkgmgr # Shared Nix init script (ensure it is executable in the installed tree) -install -m0755 scripts/init-nix.sh %{buildroot}/usr/lib/package-manager/init-nix.sh +install -d %{buildroot}/usr/lib/package-manager/nix +install -m0755 scripts/nix/init.sh %{buildroot}/usr/lib/package-manager/nix/init.sh # Remove packaging-only and development artefacts from the installed tree rm -rf \ @@ -60,7 +61,7 @@ rm -rf \ %{buildroot}/usr/lib/package-manager/.gitkeep || true %post -/usr/lib/package-manager/init-nix.sh || echo ">>> ERROR: /usr/lib/package-manager/init-nix.sh not found or not executable." +/usr/lib/package-manager/nix/init.sh || echo ">>> ERROR: /usr/lib/package-manager/nix/init.sh not found or not executable." %postun echo ">>> package-manager removed. Nix itself was not removed." diff --git a/scripts/init-nix.sh b/scripts/init-nix.sh deleted file mode 100755 index 42ba9e6..0000000 --- a/scripts/init-nix.sh +++ /dev/null @@ -1,385 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -echo "[init-nix] Starting Nix initialization..." - -NIX_INSTALL_URL="${NIX_INSTALL_URL:-https://nixos.org/nix/install}" -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.) -# --------------------------------------------------------------------------- -is_container() { - [[ -f /.dockerenv || -f /run/.containerenv ]] && return 0 - grep -qiE 'docker|container|podman|lxc' /proc/1/cgroup 2>/dev/null && return 0 - [[ -n "${container:-}" ]] && return 0 - return 1 -} - -# --------------------------------------------------------------------------- -# Ensure Nix binaries are on PATH (additive, never destructive) -# --------------------------------------------------------------------------- -ensure_nix_on_path() { - if [[ -x /nix/var/nix/profiles/default/bin/nix ]]; then - PATH="/nix/var/nix/profiles/default/bin:$PATH" - fi - if [[ -x "$HOME/.nix-profile/bin/nix" ]]; then - PATH="$HOME/.nix-profile/bin:$PATH" - 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 a path to a real executable (follows symlinks) -# --------------------------------------------------------------------------- -real_exe() { - local p="${1:-}" - [[ -z "$p" ]] && return 1 - - local r - r="$(readlink -f "$p" 2>/dev/null || echo "$p")" - - [[ -x "$r" ]] && { echo "$r"; return 0; } - return 1 -} - -# --------------------------------------------------------------------------- -# Resolve nix binary path robustly (works across distros + Arch /usr/sbin) -# --------------------------------------------------------------------------- -resolve_nix_bin() { - local nix_cmd="" - nix_cmd="$(command -v nix 2>/dev/null || true)" - [[ -n "$nix_cmd" ]] && real_exe "$nix_cmd" && return 0 - - # IMPORTANT: prefer system locations before /usr/local to avoid self-symlink traps - [[ -x /usr/sbin/nix ]] && { echo "/usr/sbin/nix"; return 0; } # Arch package can land here - [[ -x /usr/bin/nix ]] && { echo "/usr/bin/nix"; return 0; } - [[ -x /bin/nix ]] && { echo "/bin/nix"; return 0; } - - # /usr/local last, and only if it resolves to a real executable - [[ -e /usr/local/bin/nix ]] && real_exe "/usr/local/bin/nix" && return 0 - - [[ -x /nix/var/nix/profiles/default/bin/nix ]] && { - echo "/nix/var/nix/profiles/default/bin/nix"; return 0; - } - - [[ -x "$HOME/.nix-profile/bin/nix" ]] && { - 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; - } - - return 1 -} - -# --------------------------------------------------------------------------- -# Ensure globally reachable nix symlink(s) (CI / non-login shells) - root only -# -# Key rule: -# - Never overwrite distro-managed nix locations (Arch may ship nix in /usr/sbin). -# - But for sudo secure_path (CentOS), /usr/local/bin is often NOT included. -# Therefore: also create /usr/bin/nix (and /usr/sbin/nix) ONLY if they do not exist. -# --------------------------------------------------------------------------- -ensure_global_nix_symlinks() { - 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 global symlink(s)." - return 0 - fi - - # Always link to the real executable to avoid /usr/local/bin/nix -> /usr/local/bin/nix - nix_bin="$(real_exe "$nix_bin" 2>/dev/null || echo "$nix_bin")" - - local targets=() - - # Always provide /usr/local/bin/nix for CI shells - mkdir -p /usr/local/bin 2>/dev/null || true - targets+=("/usr/local/bin/nix") - - # Provide sudo-friendly locations only if they are NOT present (do not override distro paths) - if [[ ! -e /usr/bin/nix ]]; then - targets+=("/usr/bin/nix") - fi - if [[ ! -e /usr/sbin/nix ]]; then - targets+=("/usr/sbin/nix") - fi - - local target current_real - for target in "${targets[@]}"; do - current_real="" - if [[ -e "$target" ]]; then - current_real="$(real_exe "$target" 2>/dev/null || true)" - fi - - if [[ -n "$current_real" && "$current_real" == "$nix_bin" ]]; then - echo "[init-nix] $target already points to: $nix_bin" - continue - fi - - # If something exists but is not the same (and we promised not to override), skip. - if [[ -e "$target" && "$target" != "/usr/local/bin/nix" ]]; then - echo "[init-nix] WARNING: $target exists; not overwriting." - continue - fi - - if ln -sf "$nix_bin" "$target" 2>/dev/null; then - echo "[init-nix] Ensured $target -> $nix_bin" - else - echo "[init-nix] WARNING: Failed to ensure $target symlink." - fi - done -} - -# --------------------------------------------------------------------------- -# 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 - - nix_bin="$(real_exe "$nix_bin" 2>/dev/null || echo "$nix_bin")" - - 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" - - PATH="$HOME/.local/bin:$PATH" - export PATH - - 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() { - 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 - 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" - local run_as="${2:-}" - local installer elapsed=0 mode_flag - - 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 - - installer="$(mktemp -t nix-installer.XXXXXX)" - 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)) - 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 - 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 - - rm -f "$installer" -} - -# --------------------------------------------------------------------------- -# 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)" - ensure_nix_on_path - - if [[ "${EUID:-0}" -eq 0 ]]; then - ensure_global_nix_symlinks "$(resolve_nix_bin 2>/dev/null || true)" - else - ensure_user_nix_symlink "$(resolve_nix_bin 2>/dev/null || true)" - fi - - return 0 - fi - - ensure_nix_on_path - - if command -v nix >/dev/null 2>&1; then - echo "[init-nix] Nix found after PATH adjustment: $(command -v nix)" - if [[ "${EUID:-0}" -eq 0 ]]; then - ensure_global_nix_symlinks "$(resolve_nix_bin 2>/dev/null || true)" - else - ensure_user_nix_symlink "$(resolve_nix_bin 2>/dev/null || true)" - fi - return 0 - fi - - local IN_CONTAINER=0 - 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 - - 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(s) (sudo secure_path friendly) - 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 - 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 - echo "[init-nix] No systemd detected: using single-user install (--no-daemon)." - if [[ "${EUID:-0}" -eq 0 ]]; then - ensure_nix_build_group - fi - install_nix_with_retry "no-daemon" - fi - fi - - # ------------------------------------------------------------------------- - # After install: PATH + symlink(s) - # ------------------------------------------------------------------------- - 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 "$nix_bin_post" - else - ensure_user_nix_symlink "$nix_bin_post" - fi - - # 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 available at: $(command -v nix)" - echo "[init-nix] Nix initialization complete." -} - -main "$@" diff --git a/scripts/nix/README.md b/scripts/nix/README.md new file mode 100644 index 0000000..1836038 --- /dev/null +++ b/scripts/nix/README.md @@ -0,0 +1,53 @@ +# Nix Bootstrap (package-manager) + +This directory contains the **Nix initialization and bootstrap logic** used by *package-manager* to ensure the `nix` command is available on supported systems (host machines and CI containers). + +It is invoked during package installation (Arch/Debian/Fedora scriptlets) and can also be called manually. + +--- + +## Entry Point + +- *scripts/nix/init.sh* + Main bootstrap script. It: + - checks whether `nix` is already available + - adjusts `PATH` for common Nix locations + - installs Nix when missing (daemon install on systemd hosts, single-user in containers) + - ensures predictable `nix` availability via symlinks (without overwriting distro-managed paths) + - validates that `nix` is usable at the end (CI-safe) + +--- + +## Library Layout + +The entry point sources small, focused modules from *scripts/nix/lib/*: + +- *config.sh* — configuration defaults (installer URL, retry timing) +- *detect.sh* — container detection helpers +- *path.sh* — PATH adjustments and `nix` binary resolution helpers +- *symlinks.sh* — user/global symlink helpers for stable `nix` discovery +- *users.sh* — build group/users and container ownership/perms helpers +- *install.sh* — installer download + retry logic and execution helpers + +Each library file includes a simple guard to prevent double-sourcing. + +--- + +## When It Runs + +This bootstrap is typically executed automatically: + +- Arch: post-install / post-upgrade hook +- Debian: `postinst` +- Fedora/RPM: `%post` + + +--- + +## Notes / Design Goals + +- **Cross-distro compatibility:** supports common Linux layouts (including Arch placing `nix` in */usr/sbin*). +- **Non-destructive behavior:** avoids overwriting distro-managed `nix` binaries. +- **CI robustness:** retry logic for downloads and a final `nix` availability check. +- **Container-safe defaults:** single-user install as a dedicated `nix` user when running as root in containers. + diff --git a/scripts/nix/init.sh b/scripts/nix/init.sh new file mode 100755 index 0000000..9371ff7 --- /dev/null +++ b/scripts/nix/init.sh @@ -0,0 +1,130 @@ +#!/usr/bin/env bash +set -euo pipefail + +# shellcheck source=lib/config.sh +# shellcheck source=lib/detect.sh +# shellcheck source=lib/path.sh +# shellcheck source=lib/symlinks.sh +# shellcheck source=lib/users.sh +# shellcheck source=lib/install.sh + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +source "${SCRIPT_DIR}/lib/config.sh" +source "${SCRIPT_DIR}/lib/detect.sh" +source "${SCRIPT_DIR}/lib/path.sh" +source "${SCRIPT_DIR}/lib/symlinks.sh" +source "${SCRIPT_DIR}/lib/users.sh" +source "${SCRIPT_DIR}/lib/install.sh" + +echo "[init-nix] Starting Nix initialization..." + +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)" + ensure_nix_on_path + + if [[ "${EUID:-0}" -eq 0 ]]; then + ensure_global_nix_symlinks "$(resolve_nix_bin 2>/dev/null || true)" + else + ensure_user_nix_symlink "$(resolve_nix_bin 2>/dev/null || true)" + fi + + return 0 + fi + + ensure_nix_on_path + + if command -v nix >/dev/null 2>&1; then + echo "[init-nix] Nix found after PATH adjustment: $(command -v nix)" + if [[ "${EUID:-0}" -eq 0 ]]; then + ensure_global_nix_symlinks "$(resolve_nix_bin 2>/dev/null || true)" + else + ensure_user_nix_symlink "$(resolve_nix_bin 2>/dev/null || true)" + fi + return 0 + fi + + local IN_CONTAINER=0 + 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 + + 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 + + ensure_nix_store_dir_for_container_user + + install_nix_with_retry "no-daemon" "nix" + + ensure_nix_on_path + + # Ensure stable global symlink(s) (sudo secure_path friendly) + ensure_global_nix_symlinks "/home/nix/.nix-profile/bin/nix" + + # Ensure non-root users can traverse and execute nix user profile + ensure_container_profile_perms + + # ------------------------------------------------------------------------- + # Host (no container) + # ------------------------------------------------------------------------- + else + 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 + echo "[init-nix] No systemd detected: using single-user install (--no-daemon)." + if [[ "${EUID:-0}" -eq 0 ]]; then + ensure_nix_build_group + fi + install_nix_with_retry "no-daemon" + fi + fi + + # ------------------------------------------------------------------------- + # After install: PATH + symlink(s) + # ------------------------------------------------------------------------- + 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 "$nix_bin_post" + else + ensure_user_nix_symlink "$nix_bin_post" + fi + + # 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 available at: $(command -v nix)" + echo "[init-nix] Nix initialization complete." +} + +main "$@" diff --git a/scripts/nix/lib/config.sh b/scripts/nix/lib/config.sh new file mode 100644 index 0000000..bbf389d --- /dev/null +++ b/scripts/nix/lib/config.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +# Prevent double-sourcing +if [[ -n "${PKGMGR_NIX_CONFIG_SH:-}" ]]; then + return 0 +fi +PKGMGR_NIX_CONFIG_SH=1 + +NIX_INSTALL_URL="${NIX_INSTALL_URL:-https://nixos.org/nix/install}" +NIX_DOWNLOAD_MAX_TIME="${NIX_DOWNLOAD_MAX_TIME:-300}" +NIX_DOWNLOAD_SLEEP_INTERVAL="${NIX_DOWNLOAD_SLEEP_INTERVAL:-20}" diff --git a/scripts/nix/lib/detect.sh b/scripts/nix/lib/detect.sh new file mode 100644 index 0000000..9cc7a4b --- /dev/null +++ b/scripts/nix/lib/detect.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +if [[ -n "${PKGMGR_NIX_DETECT_SH:-}" ]]; then + return 0 +fi +PKGMGR_NIX_DETECT_SH=1 + +# Detect whether we are inside a container (Docker/Podman/etc.) +is_container() { + [[ -f /.dockerenv || -f /run/.containerenv ]] && return 0 + grep -qiE 'docker|container|podman|lxc' /proc/1/cgroup 2>/dev/null && return 0 + [[ -n "${container:-}" ]] && return 0 + return 1 +} diff --git a/scripts/nix/lib/install.sh b/scripts/nix/lib/install.sh new file mode 100644 index 0000000..5548381 --- /dev/null +++ b/scripts/nix/lib/install.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash + +if [[ -n "${PKGMGR_NIX_INSTALL_SH:-}" ]]; then + return 0 +fi +PKGMGR_NIX_INSTALL_SH=1 + +# Requires: NIX_INSTALL_URL, NIX_DOWNLOAD_MAX_TIME, NIX_DOWNLOAD_SLEEP_INTERVAL + +# 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" + local run_as="${2:-}" + local installer elapsed=0 mode_flag + + 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 + + installer="$(mktemp -t nix-installer.XXXXXX)" + 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)) + 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 + 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 + + rm -f "$installer" +} diff --git a/scripts/nix/lib/path.sh b/scripts/nix/lib/path.sh new file mode 100644 index 0000000..1272ab9 --- /dev/null +++ b/scripts/nix/lib/path.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash + +if [[ -n "${PKGMGR_NIX_PATH_SH:-}" ]]; then + return 0 +fi +PKGMGR_NIX_PATH_SH=1 + +# Ensure Nix binaries are on PATH (additive, never destructive) +ensure_nix_on_path() { + if [[ -x /nix/var/nix/profiles/default/bin/nix ]]; then + PATH="/nix/var/nix/profiles/default/bin:$PATH" + fi + if [[ -x "$HOME/.nix-profile/bin/nix" ]]; then + PATH="$HOME/.nix-profile/bin:$PATH" + 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 a path to a real executable (follows symlinks) +real_exe() { + local p="${1:-}" + [[ -z "$p" ]] && return 1 + + local r + r="$(readlink -f "$p" 2>/dev/null || echo "$p")" + + [[ -x "$r" ]] && { echo "$r"; return 0; } + return 1 +} + +# Resolve nix binary path robustly (works across distros + Arch /usr/sbin) +resolve_nix_bin() { + local nix_cmd="" + nix_cmd="$(command -v nix 2>/dev/null || true)" + [[ -n "$nix_cmd" ]] && real_exe "$nix_cmd" && return 0 + + # IMPORTANT: prefer system locations before /usr/local to avoid self-symlink traps + [[ -x /usr/sbin/nix ]] && { echo "/usr/sbin/nix"; return 0; } # Arch package can land here + [[ -x /usr/bin/nix ]] && { echo "/usr/bin/nix"; return 0; } + [[ -x /bin/nix ]] && { echo "/bin/nix"; return 0; } + + # /usr/local last, and only if it resolves to a real executable + [[ -e /usr/local/bin/nix ]] && real_exe "/usr/local/bin/nix" && return 0 + + [[ -x /nix/var/nix/profiles/default/bin/nix ]] && { + echo "/nix/var/nix/profiles/default/bin/nix"; return 0; + } + + [[ -x "$HOME/.nix-profile/bin/nix" ]] && { + 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; + } + + return 1 +} diff --git a/scripts/nix/lib/symlinks.sh b/scripts/nix/lib/symlinks.sh new file mode 100644 index 0000000..2a57844 --- /dev/null +++ b/scripts/nix/lib/symlinks.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash + +if [[ -n "${PKGMGR_NIX_SYMLINKS_SH:-}" ]]; then + return 0 +fi +PKGMGR_NIX_SYMLINKS_SH=1 + +# Requires: real_exe, resolve_nix_bin +# shellcheck disable=SC2034 + +# Ensure globally reachable nix symlink(s) (CI / non-login shells) - root only +ensure_global_nix_symlinks() { + 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 global symlink(s)." + return 0 + fi + + # Always link to the real executable to avoid /usr/local/bin/nix -> /usr/local/bin/nix + nix_bin="$(real_exe "$nix_bin" 2>/dev/null || echo "$nix_bin")" + + local targets=() + + # Always provide /usr/local/bin/nix for CI shells + mkdir -p /usr/local/bin 2>/dev/null || true + targets+=("/usr/local/bin/nix") + + # Provide sudo-friendly locations only if they are NOT present (do not override distro paths) + if [[ ! -e /usr/bin/nix ]]; then + targets+=("/usr/bin/nix") + fi + if [[ ! -e /usr/sbin/nix ]]; then + targets+=("/usr/sbin/nix") + fi + + local target current_real + for target in "${targets[@]}"; do + current_real="" + if [[ -e "$target" ]]; then + current_real="$(real_exe "$target" 2>/dev/null || true)" + fi + + if [[ -n "$current_real" && "$current_real" == "$nix_bin" ]]; then + echo "[init-nix] $target already points to: $nix_bin" + continue + fi + + # If something exists but is not the same (and we promised not to override), skip. + if [[ -e "$target" && "$target" != "/usr/local/bin/nix" ]]; then + echo "[init-nix] WARNING: $target exists; not overwriting." + continue + fi + + if ln -sf "$nix_bin" "$target" 2>/dev/null; then + echo "[init-nix] Ensured $target -> $nix_bin" + else + echo "[init-nix] WARNING: Failed to ensure $target symlink." + fi + done +} + +# 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 + + nix_bin="$(real_exe "$nix_bin" 2>/dev/null || echo "$nix_bin")" + + 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" + + PATH="$HOME/.local/bin:$PATH" + export PATH + + if [[ -w "$HOME/.profile" ]] && ! grep -q 'nix/init.sh' "$HOME/.profile" 2>/dev/null; then + cat >>"$HOME/.profile" <<'EOF' + +# PATH for nix (added by package-manager nix/init.sh) +if [ -d "$HOME/.local/bin" ]; then + PATH="$HOME/.local/bin:$PATH" +fi +EOF + fi +} diff --git a/scripts/nix/lib/users.sh b/scripts/nix/lib/users.sh new file mode 100644 index 0000000..319890a --- /dev/null +++ b/scripts/nix/lib/users.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +if [[ -n "${PKGMGR_NIX_USERS_SH:-}" ]]; then + return 0 +fi +PKGMGR_NIX_USERS_SH=1 + +# Ensure Nix build group and users exist (build-users-group = nixbld) - root only +ensure_nix_build_group() { + 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 + 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 +} + +# Container-only helper: /nix ownership + perms for single-user install as 'nix' +ensure_nix_store_dir_for_container_user() { + if [[ ! -d /nix ]]; then + echo "[init-nix] Creating /nix with owner nix:nixbld..." + mkdir -m 0755 /nix + chown nix:nixbld /nix + return 0 + fi + + 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 +} + +# Container-only helper: make nix profile executable/traversable for non-root +ensure_container_profile_perms() { + 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 +} diff --git a/scripts/pkgmgr-wrapper.sh b/scripts/pkgmgr-wrapper.sh index 117ff27..9cf0eaf 100755 --- a/scripts/pkgmgr-wrapper.sh +++ b/scripts/pkgmgr-wrapper.sh @@ -28,11 +28,11 @@ if ! command -v nix >/dev/null 2>&1; then fi # --------------------------------------------------------------------------- -# If nix is still missing, try to run init-nix.sh once +# If nix is still missing, try to run nix/init.sh once # --------------------------------------------------------------------------- if ! command -v nix >/dev/null 2>&1; then - if [[ -x "${FLAKE_DIR}/init-nix.sh" ]]; then - "${FLAKE_DIR}/init-nix.sh" || true + if [[ -x "${FLAKE_DIR}/nix/init.sh" ]]; then + "${FLAKE_DIR}/nix/init.sh" || true fi fi