fix(dev-nix): purge stale nixbld users blocking Nix installer

The Nix 2.32.4 installer allocates build users ascending from
NIX_FIRST_BUILD_UID. The pkgmgr-centos:stable base image ships
nixbld1..nixbld32 with descending UIDs (RHEL `useradd -r` default:
nixbld1=997, nixbld2=996, ...) and no nix binary, so the role
detects no install, runs the installer, which then aborts on
"nixbld2 already exists, but with the UID '996'".

Add purge_stale_nixbld.sh, called via ansible.builtin.script
before the installer. Three guards must all hold to purge:
  1. No nix binary in candidate paths.
  2. nixbld2 UID < nixbld1 UID (broken descending layout).
  3. /nix/store does not exist (no live store to orphan).

Idempotent. Distro-agnostic at the script level: Debian/Ubuntu stay
[UNCHANGED] because guard 2 fails (nixbld users absent); Arch never
hits this code path. Production hosts with /nix/store present but
nixbld users get [FAIL] (rc=1) instead of silent destruction.

Fixes the 31/32 test-deploy-server / test-deploy-universal failures
observed in run 26634712613.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 4.7 committed 2026-05-29 16:09:59 +02:00
1 parent 84ca98e107
commit 45eea2892f
2 files changed
+82

No files matched your search

+70
View File
@@ -0,0 +1,70 @@
#!/usr/bin/env bash
# Purge stale nixbld build users when their layout would block the
# multi-user Nix installer (descending UIDs as created by `useradd -r`
# on RHEL-family pkgmgr base images) and no live /nix/store exists.
#
# Idempotent. Safe to re-run.
#
# Three guards must all hold to purge:
# 1. No nix binary in known install paths.
# 2. nixbld2 UID < nixbld1 UID (the broken descending layout).
# 3. /nix/store does not exist (no live store to orphan).
set -euo pipefail
NIX_BINARY_CANDIDATES=(
/nix/var/nix/profiles/default/bin/nix
/nix/var/nix/profiles/per-user/root/profile/bin/nix
/root/.nix-profile/bin/nix
/usr/local/bin/nix
/usr/bin/nix
)
found_binary=""
for p in "${NIX_BINARY_CANDIDATES[@]}"; do
if [[ -x "$p" ]]; then
found_binary="$p"
break
fi
done
if [[ -n "$found_binary" ]]; then
echo "[UNCHANGED] nix binary present at ${found_binary}, leaving nixbld users intact"
exit 0
fi
uid1="$(getent passwd nixbld1 | awk -F: '{print $3}')"
uid2="$(getent passwd nixbld2 | awk -F: '{print $3}')"
if [[ -z "$uid1" || -z "$uid2" ]]; then
echo "[UNCHANGED] nixbld1/nixbld2 not both present, nothing to purge"
exit 0
fi
if (( uid2 >= uid1 )); then
echo "[UNCHANGED] nixbld UIDs ascending (nixbld1=${uid1}, nixbld2=${uid2}), no fix needed"
exit 0
fi
if [[ -d /nix/store ]]; then
echo "[FAIL] /nix/store exists but no nix binary found; refusing to purge build users" >&2
exit 1
fi
removed=0
for i in $(seq 1 32); do
if id "nixbld${i}" >/dev/null 2>&1; then
userdel "nixbld${i}"
removed=$((removed + 1))
fi
done
if getent group nixbld >/dev/null; then
groupdel nixbld
removed=$((removed + 1))
fi
if (( removed > 0 )); then
echo "[CHANGED] purged ${removed} stale nixbld entries (descending UID layout: nixbld1=${uid1}, nixbld2=${uid2})"
else
echo "[UNCHANGED] no nixbld entries found"
fi
@@ -52,6 +52,18 @@
) | default("", true)
}}
- name: Purge stale nixbld users blocking the multi-user installer
ansible.builtin.script:
cmd: "{{ role_path }}/files/purge_stale_nixbld.sh"
register: dev_nix_purge_stale_nixbld_result
changed_when: >
dev_nix_purge_stale_nixbld_result.stdout is search('\\[CHANGED\\]')
failed_when: >
dev_nix_purge_stale_nixbld_result.rc != 0 or
dev_nix_purge_stale_nixbld_result.stdout is search('\\[FAIL\\]')
become: true
when: dev_nix_binary_path_before | length == 0
- name: Read existing nixbld group entry before installer run
ansible.builtin.getent:
database: group