fix(B-0835 Bug 3b): password activation-script — fixes operationally-ignored custom password (timing/path-mismatch root cause)#5351
Merged
AceHack merged 1 commit intoMay 26, 2026
Conversation
…tivation-script runtime read — fixes operationally-ignored custom password ROOT CAUSE: prior implementation read /etc/zeta/initial-hashedpassword via builtins.readFile at NixOS evaluation time (build-time). During nixos-install from live ISO: - Build evaluation happens BEFORE chroot pivot to install target - Path /etc/zeta/initial-hashedpassword refers to LIVE ISO (file absent) NOT install target /mnt/etc/zeta/initial-hashedpassword (file present) - Flake pure-mode refuses builtins.readFile on non-store absolute paths - Build silently falls back to default-hash; operator's custom password is preserved on disk but NEVER applied to user config FIX: use NixOS system.activationScripts to read the file at ACTIVATION TIME (runtime on the installed system, when /etc/zeta/ IS the actual path with the operator's hash). Build-time config uses the default-hash; activation overrides via `usermod -p $hash zeta` if operator-chosen hash file exists. Works for: - Fresh installs from live ISO (activation runs post-pivot; /etc/zeta/initial-hashedpassword present) - Subsequent nixos-rebuilds on installed system (file persists; activation re-applies the operator's hash) - CI eval (file absent; activation skips; default-hash stays) Empirical anchor: operator 2026-05-26 physical hardware-support test: "the password i set it still says password: zeta-change-me" + "the password error is not just display issue it's operational bug the password i set earlier in install is ignored". Per `.claude/rules/methodology-hard-limits.md` + B-0794 homelab-mode: - NO secret material in module source (only the public default-fallback) - NO secret printed in activation log (only "applied" or "skipped" status) - Hash file at /etc/zeta/initial-hashedpassword is chmod 0600 root:root (written that way by zeta-install.sh Step 6.55) - usermod -p directly writes to /etc/shadow (root-only readable) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
Pull request overview
Fixes B-0835 Bug 3b in the NixOS install flow where an operator-provided password hash was ignored due to evaluation-time file reads pointing at the wrong root (live ISO vs install target) and/or being blocked in pure evaluation.
Changes:
- Removes evaluation-time
builtins.readFile/builtins.pathExistspassword-hash injection logic. - Sets a build-time fallback hash for
users.users.zeta.hashedPasswordand adds an activation-time script that applies/etc/zeta/initial-hashedpassword(when present) viausermod -p. - Updates module commentary to document the root cause and the activation-time fix behavior across install/rebuild/CI scenarios.
AceHack
pushed a commit
that referenced
this pull request
May 26, 2026
…d files (cluster-node-id + operator-authorized-keys) + log/comment accuracy 4 legitimate Copilot findings on prior #5354 commit, all real: 1. **Trap-based cleanup**: prior cleanup only fired on success path. If nixos-install fails OR Ctrl-C, /etc/zeta/cluster-node-id symlink would persist + dangle when /mnt is unmounted. FIX: trap EXIT handler runs cleanup on ALL exit paths (success/failure/signal). Defense-in-depth via explicit cleanup at end too. 2. **Misleading log message**: prior "symlinking $X → /etc/zeta/..." printed even when no symlink was actually created. FIX: move log inside the maybe_symlink helper so it only prints on actual creation. 3. **Comment vs code mismatch**: prior comment said "Symlinking /mnt/etc/zeta → /etc/zeta" (directory-level) but code only handled the single cluster-node-id file. FIX: rewrote comment to match per-file approach + named all affected modules. 4. **Safety note wrong about operator-authorized-keys.nix**: prior note claimed "initial-password.nix doesn't use builtins.readFile" but didn't acknowledge that operator-authorized-keys.nix DOES use builtins.readFile on /etc/zeta/operator-authorized-keys. With --impure now active, that module ALSO needs the symlink-or-it- silently-loses-iter-5.4.0-pubkeys. FIX: extended the symlink approach to operator-authorized-keys too + updated safety note to correctly distinguish all 3 modules: - injected-hostname.nix → symlinked (Bug 1 fix) - operator-authorized-keys.nix → symlinked (sibling-bug-class) - initial-password.nix → activation-script (Bug 3b fix) Helper function maybe_symlink() centralizes the "create only if file exists AND destination doesn't" logic; trap handler removes only files this script created. Composes with PR #5351 (Bug 3b activation-script) — together the two PRs fix all 3 instances of the build-time-eval-vs-install-time-write path-mismatch bug class. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
AceHack
added a commit
that referenced
this pull request
May 26, 2026
…val reads cluster-node-id (same bug class as Bug 3b) (#5354) * fix(B-0835 Bug 1): hostname injection — symlink /mnt/etc/zeta → /etc/zeta + --impure so flake eval reads cluster-node-id ROOT CAUSE: same bug class as Bug 3b (password). injected-hostname.nix reads /etc/zeta/cluster-node-id via builtins.pathExists + builtins.readFile at NixOS evaluation time (flake build-time). During nixos-install from live ISO: - zeta-install.sh Step 6.6 writes /mnt/etc/zeta/cluster-node-id ✓ - Flake eval reads /etc/zeta/cluster-node-id (LIVE ISO context; absent) - Module falls through to flake's hardcoded networking.hostName - Operator gets flake-default hostname (e.g., "control-plane") instead of unique node-<6hex> that iter-5.2.2 generated FIX (different from Bug 3b's activation-script approach because hostname CANNOT cleanly change at activation — many services bake hostname at build time): 1. Symlink /mnt/etc/zeta/cluster-node-id → /etc/zeta/cluster-node-id BEFORE nixos-install runs. Makes the file visible at the path injected-hostname.nix expects during flake eval phase. 2. Add --impure flag to nixos-install so flake pure-mode allows builtins.pathExists + builtins.readFile on the non-store path. 3. Cleanup the symlink AFTER nixos-install (no dangling reference if /mnt is unmounted before reboot). Subsequent rebuilds on the installed system work without the symlink because /etc/zeta/cluster-node-id IS on the installed root filesystem (written by the install). Empirical anchor: operator 2026-05-26 physical hardware-support test showed "control-plane login:" instead of unique node-<6hex>. Safety: - Only impure read is operator-chosen hostname (not a secret) - Other modules (initial-password.nix per Bug 3b fix) use activation-scripts so they don't need --impure - Symlink-then-cleanup is idempotent + reversible Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(PR-5354): Copilot 4 findings — trap-cleanup + symlink ALL affected files (cluster-node-id + operator-authorized-keys) + log/comment accuracy 4 legitimate Copilot findings on prior #5354 commit, all real: 1. **Trap-based cleanup**: prior cleanup only fired on success path. If nixos-install fails OR Ctrl-C, /etc/zeta/cluster-node-id symlink would persist + dangle when /mnt is unmounted. FIX: trap EXIT handler runs cleanup on ALL exit paths (success/failure/signal). Defense-in-depth via explicit cleanup at end too. 2. **Misleading log message**: prior "symlinking $X → /etc/zeta/..." printed even when no symlink was actually created. FIX: move log inside the maybe_symlink helper so it only prints on actual creation. 3. **Comment vs code mismatch**: prior comment said "Symlinking /mnt/etc/zeta → /etc/zeta" (directory-level) but code only handled the single cluster-node-id file. FIX: rewrote comment to match per-file approach + named all affected modules. 4. **Safety note wrong about operator-authorized-keys.nix**: prior note claimed "initial-password.nix doesn't use builtins.readFile" but didn't acknowledge that operator-authorized-keys.nix DOES use builtins.readFile on /etc/zeta/operator-authorized-keys. With --impure now active, that module ALSO needs the symlink-or-it- silently-loses-iter-5.4.0-pubkeys. FIX: extended the symlink approach to operator-authorized-keys too + updated safety note to correctly distinguish all 3 modules: - injected-hostname.nix → symlinked (Bug 1 fix) - operator-authorized-keys.nix → symlinked (sibling-bug-class) - initial-password.nix → activation-script (Bug 3b fix) Helper function maybe_symlink() centralizes the "create only if file exists AND destination doesn't" logic; trap handler removes only files this script created. Composes with PR #5351 (Bug 3b activation-script) — together the two PRs fix all 3 instances of the build-time-eval-vs-install-time-write path-mismatch bug class. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Lior <lior@zeta.dev> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes B-0835 Bug 3b — the custom password the operator set during install was operationally ignored because of a build-time-eval vs install-time-write path mismatch.
Root cause
Prior implementation used `builtins.readFile` at NixOS evaluation time:
Fix
Replace `builtins.readFile` with `system.activationScripts.zetaInitialPassword` that reads at activation time (runtime on installed system):
```nix
system.activationScripts.zetaInitialPassword = {
deps = [ "users" ];
text = ''
if [ -f "${hashFile}" ]; then
hash=$(cat "${hashFile}" | tr -d '\n')
if [ -n "$hash" ] && [ "${hash:0:3}" = '$6$' ]; then
usermod -p "$hash" zeta
fi
fi
'';
};
```
Works for 3 scenarios
Security properties preserved
Empirical anchor
Operator 2026-05-26 physical hardware-support test: "the password i set it still says password: zeta-change-me" + "the password error is not just display issue it's operational bug the password i set earlier in install is ignored".
Test plan
🤖 Generated with Claude Code