Add rebuild-nexus: rebuild the hypervisor from DEPLOY_FLAKE #61

Merged
vnprc merged 2 commits from agent/rebuild-nexus into master 2026-09-19 21:55:48 +01:00
Member

A running host gains one command, rebuild-nexus [switch|boot|test], which rebuilds the hypervisor from the flake named by DEPLOY_FLAKE and nothing else. It refuses to run when DEPLOY_FLAKE is unset instead of falling back to a checkout, and it prints the flake path, the attribute and the evaluated toplevel store path before it switches, so the operator can see what is about to land. Nothing existing changes: the VM scripts, their fallbacks and the option that renders DEPLOY_FLAKE behave as before; the only edits outside the new files are list entries and doc sentences. The proof is the new test suite in tests/rebuild-nexus.sh (run by the provisioning-contract flake check) plus one cold cross-family review of the diff. If it is wrong, revert this PR; the hand-run sudo nixos-rebuild path is untouched and keeps working.

The issue named the script rebuild-host; the owner asked for it to be rebuild-nexus, and every file, test and doc mention uses that name.

What changes

  • scripts/rebuild-nexus: new host-side script. Mode defaults to switch; boot and test pass through unchanged; anything else is a usage error. With DEPLOY_FLAKE unset or empty it dies with a message naming nexus.provisioning.deployFlake and invokes neither nix nor nixos-rebuild. It evaluates nixosConfigurations.<hostname>.config.system.build.toplevel.outPath with nix eval --raw, prints flake, attribute and path, then runs nixos-rebuild <mode> --flake $DEPLOY_FLAKE#<hostname> --sudo.
  • --sudo is one deliberate addition to the issue's command line: the operator runs the script as themselves so DEPLOY_FLAKE from the login session is honoured, and nixos-rebuild --sudo elevates only the activation step. Running the whole script under sudo would drop the variable, which is the failure the issue exists to stop. rebuild-vm-from-host uses the same flag.
  • tests/rebuild-nexus.sh: eight tests with hostname, nix and nixos-rebuild stubs: recorder arguments for all three modes, usage errors, unset and empty DEPLOY_FLAKE refusing before any tool runs, printed toplevel equal to the stub's nix eval answer, and a failing eval refusing before the recorder.
  • flake.nix, nix/provisioning-scripts.nix: the script and test join the provisioning-contract check (syntax, shellcheck, test run, packaged test -x).
  • nix/configuration.nix, docs/provisioning-scripts.md: the deployFlake option and the provisioning-pointers doc name rebuild-nexus and its lack of a fallback; a short "Rebuilding the host" section tells operators to use it instead of the hand-run command. README.md has no host-rebuild sentence, so it is unchanged.

Risk

R2. The worst credible failure after validation is a local nixos-rebuild --sudo behaving differently on a real host than the recorder shows; that leaves the host on its current generation (a failed build or refused sudo) or activates exactly the toplevel the script printed. The authoritative source, the deploy flake, is never written. Rollback is a revert of this PR. The fixtures cannot exercise a real switch; the issue's on-host check (a lock bump moving one input, readlink of the new generation equal to the printed path, nix store diff-closures showing only that input) is a human step on a deployment host and was not run here.

Validation

Run by the integrator in the PR worktree at 050789f (the head after the fix round):

bash tests/rebuild-nexus.sh            -> 8 rebuild-nexus tests passed.
bash tests/rebuild-vm-from-host.sh     -> 10 rebuild-vm-from-host tests passed.
nix build .#checks.x86_64-linux.provisioning-contract --no-link --print-build-logs --rebuild
                                       -> exit 0; the build log shows "8 rebuild-nexus tests passed." inside the check

The check runs bash -n and shellcheck -x over the new files and test -x on the packaged rebuild-nexus.

Cold cross-family review: gpt-5.6-sol via codex exec, reasoning effort high, read the diff at bbff982 (Claude-family worker authored it). Four should-fix findings, all verified against the source and fixed in 050789f: the printed attribute dropped the quotes the evaluation uses, so a hostname such as 123host could not be pasted into the comparison command; the recorder stub flattened arguments with $*, so a script passing one joined argument would have passed; the test did not pin that the preview prints before the switch, now asserted by a stdout marker from the recorder; and three sentences in the provisioning-pointers doc still promised a fallback that rebuild-nexus refuses, now limited to the VM scripts. No blockers. Per the pass budget, no second reading follows the fix round; the fixes are settled by the test suite and the flake check above.

Closes allod/nexus#59

A running host gains one command, `rebuild-nexus [switch|boot|test]`, which rebuilds the hypervisor from the flake named by `DEPLOY_FLAKE` and nothing else. It refuses to run when `DEPLOY_FLAKE` is unset instead of falling back to a checkout, and it prints the flake path, the attribute and the evaluated toplevel store path before it switches, so the operator can see what is about to land. Nothing existing changes: the VM scripts, their fallbacks and the option that renders `DEPLOY_FLAKE` behave as before; the only edits outside the new files are list entries and doc sentences. The proof is the new test suite in `tests/rebuild-nexus.sh` (run by the `provisioning-contract` flake check) plus one cold cross-family review of the diff. If it is wrong, revert this PR; the hand-run `sudo nixos-rebuild` path is untouched and keeps working. The issue named the script `rebuild-host`; the owner asked for it to be `rebuild-nexus`, and every file, test and doc mention uses that name. ## What changes - `scripts/rebuild-nexus`: new host-side script. Mode defaults to `switch`; `boot` and `test` pass through unchanged; anything else is a usage error. With `DEPLOY_FLAKE` unset or empty it dies with a message naming `nexus.provisioning.deployFlake` and invokes neither `nix` nor `nixos-rebuild`. It evaluates `nixosConfigurations.<hostname>.config.system.build.toplevel.outPath` with `nix eval --raw`, prints flake, attribute and path, then runs `nixos-rebuild <mode> --flake $DEPLOY_FLAKE#<hostname> --sudo`. - `--sudo` is one deliberate addition to the issue's command line: the operator runs the script as themselves so `DEPLOY_FLAKE` from the login session is honoured, and `nixos-rebuild --sudo` elevates only the activation step. Running the whole script under `sudo` would drop the variable, which is the failure the issue exists to stop. `rebuild-vm-from-host` uses the same flag. - `tests/rebuild-nexus.sh`: eight tests with `hostname`, `nix` and `nixos-rebuild` stubs: recorder arguments for all three modes, usage errors, unset and empty `DEPLOY_FLAKE` refusing before any tool runs, printed toplevel equal to the stub's `nix eval` answer, and a failing eval refusing before the recorder. - `flake.nix`, `nix/provisioning-scripts.nix`: the script and test join the `provisioning-contract` check (syntax, shellcheck, test run, packaged `test -x`). - `nix/configuration.nix`, `docs/provisioning-scripts.md`: the `deployFlake` option and the provisioning-pointers doc name `rebuild-nexus` and its lack of a fallback; a short "Rebuilding the host" section tells operators to use it instead of the hand-run command. `README.md` has no host-rebuild sentence, so it is unchanged. ## Risk R2. The worst credible failure after validation is a local `nixos-rebuild --sudo` behaving differently on a real host than the recorder shows; that leaves the host on its current generation (a failed build or refused sudo) or activates exactly the toplevel the script printed. The authoritative source, the deploy flake, is never written. Rollback is a revert of this PR. The fixtures cannot exercise a real switch; the issue's on-host check (a lock bump moving one input, `readlink` of the new generation equal to the printed path, `nix store diff-closures` showing only that input) is a human step on a deployment host and was not run here. ## Validation Run by the integrator in the PR worktree at 050789f (the head after the fix round): ``` bash tests/rebuild-nexus.sh -> 8 rebuild-nexus tests passed. bash tests/rebuild-vm-from-host.sh -> 10 rebuild-vm-from-host tests passed. nix build .#checks.x86_64-linux.provisioning-contract --no-link --print-build-logs --rebuild -> exit 0; the build log shows "8 rebuild-nexus tests passed." inside the check ``` The check runs `bash -n` and `shellcheck -x` over the new files and `test -x` on the packaged `rebuild-nexus`. Cold cross-family review: `gpt-5.6-sol` via `codex exec`, reasoning effort `high`, read the diff at bbff982 (Claude-family worker authored it). Four should-fix findings, all verified against the source and fixed in 050789f: the printed attribute dropped the quotes the evaluation uses, so a hostname such as `123host` could not be pasted into the comparison command; the recorder stub flattened arguments with `$*`, so a script passing one joined argument would have passed; the test did not pin that the preview prints before the switch, now asserted by a stdout marker from the recorder; and three sentences in the provisioning-pointers doc still promised a fallback that `rebuild-nexus` refuses, now limited to the VM scripts. No blockers. Per the pass budget, no second reading follows the fix round; the fixes are settled by the test suite and the flake check above. Closes allod/nexus#59
Rebuilding the host by hand as `sudo nixos-rebuild switch --flake .#<host>`
lets whatever flake is checked out in the current directory pick the
target, which has bricked a host before. rebuild-nexus reads
DEPLOY_FLAKE instead, with no fallback, and prints the flake path, the
attribute, and the evaluated toplevel store path before switching so an
operator can verify the change before it lands.

Refs allod/nexus#59
Print the quoted attribute exactly as evaluated (a bare hostname can be
invalid Nix syntax), quote the hostname in the docs comparison command,
record and assert nixos-rebuild's arguments individually instead of via
a flattened $*, and assert that the printed toplevel line precedes the
recorder invocation. Docs also scope the VM-script fallback sentences to
the VM scripts and state that rebuild-nexus has no fallback.

Refs allod/nexus#59
vnprc approved these changes 2026-09-19 21:55:43 +01:00
vnprc merged commit 050789f66b into master 2026-09-19 21:55:48 +01:00
vnprc deleted branch agent/rebuild-nexus 2026-09-19 21:55:49 +01:00
Sign in to join this conversation.
No description provided.