Read VM target/username/host-key from deploy-flake outputs, dropping side checkouts #6

Closed
opened 2026-07-14 19:15:15 +01:00 by allod-agent · 0 comments
Member

Follow-up to #5. That change made a single DEPLOY_FLAKE the source of truth for a VM's target IP, username, and host-key pin, but the scripts still read those facts out of local INVENTORY_CHECKOUT and SECRETS_CHECKOUT clones at the revisions the deploy flake's flake.lock pins — so an operator still points at three things (DEPLOY_FLAKE plus two checkouts that must be clones able to fetch the pinned revs). Expose the derived facts as read-only outputs of the deploy flake and have rebuild-vm-from-host / provision-vm-from-host read them with nix eval <deploy>#…, so the only pointer is DEPLOY_FLAKE and the side checkouts go away. This is the "expose the target IP and the pinned host keys as flake outputs" alternative #5 deferred as a larger cross-repo change.

Context

rebuild-vm-from-host and provision-vm-from-host (via scripts/lib/rotation-common.sh) currently resolve the pinned inventory/secrets revisions from ${DEPLOY_FLAKE}/flake.lock, ensure those commits are present locally (fetching if needed), then read the target IP from git show <inv-rev>:scripts/vm-specs.json, the username from nix eval path:${SECRETS_CHECKOUT}#lib.vmUsernames.<vm>, and the host-key material from git show <secrets-rev>:machine-host-keys.json; provision also reads the age-encrypted host key at the pinned secrets rev. The checkouts exist only to supply file/eval content at the pinned commits, yet they are still two paths the operator must provide and keep as fetchable clones. Evaluating the deploy flake directly makes the pin inherent — the flake already composes the pinned inputs — and eliminates the checkout state entirely. That is the drift-free end state principle 8 points at (one source of truth per fact; consumers derive) and squares with principle 3 (build public, deploy private): the derivation logic lives in the public framework and any private deploy flake inherits it by composition rather than copying it.

Direction (for the dev plan to settle)

Preferred shape: put the output-generating logic in the shared framework (a lib or module in profiles or vm) so the public template flake and any deploy flake both expose the outputs by composing the framework and supplying only data — no per-fork duplication of logic. Candidate outputs, keyed by VM name, for example <deploy>#vmTarget.<vm> (IP, from inventory), <deploy>#vmUsername.<vm> (from secrets), and <deploy>#vmHostKeys.<vm> (the pinned active/staged public material the pin check compares against). The scripts then replace the flake-lock rev resolution, the ensure_git_commit_available fetches, and the git show reads for these facts with nix eval.

Security-sensitive decisions the plan must make and justify:

  • The fail-closed host-key pin (assert_any_vm_host_key_material_pinned / assert_vm_host_key_material_pinned) must be preserved at equal strength, now comparing against the material from the flake output instead of git show. A regression test that it fails closed on an unpinned or mismatched presented/derived key is required.
  • provision needs the age-encrypted host private key to inject at install. Decide whether to expose that ciphertext as a flake output too — age blobs are safe to surface, and doing so lets provision also drop the secrets checkout — or keep that single file read. Exposing it is the "one pointer" end state; pick and justify.
  • Whether to keep INVENTORY_CHECKOUT / SECRETS_CHECKOUT as optional fallbacks or remove them outright once the outputs exist.

Boundaries

Public framework and provisioning tooling only: allod/nexus scripts plus read-only outputs added in allod/profiles / allod/vm / allod/inventory / allod/secrets, all buildable and testable against the template machines (allod-dev, privacy-1). Private deployments inherit the pattern by composing the framework and supply their own data; their adoption and any data-contract sequencing is tracked privately and is out of scope here. Also out of scope: the deploy-flake composition model itself, machine-data schemas beyond adding outputs, and the separate smaller change of baking DEPLOY_FLAKE into the host environment so on-host invocations need no exports. Host-side live rebuild/provision remains a human gate.

Validation

Fixture-based, extending the existing harness and gated on nix flake check. Must include a genuine no-checkout invocation (only DEPLOY_FLAKE set) that derives IP, username, and host-key material via nix eval; the fail-closed pin regression on the output-fed path (refusal plus an assertion that no build ran); and, for provision, that host-key injection still works from whatever source the plan chooses. tests/rotate-token.sh and the out-of-scope scripts that share the current helpers must stay green.

Classification

Needs a dev plan (cross-repo: allod/nexus plus framework outputs in allod/profiles / vm / inventory / secrets). Residual risk likely R3, the same class as #5: provisioning tooling on the host-key security and auth boundary with a cross-repo interface, recoverable by revert. The final integration PR — the nexus scripts consuming the outputs — carries the closing keyword; earlier framework-output PRs use Refs.

Sequencing

Public-first and unblocked: this builds on #5 (the DEPLOY_FLAKE consumption already exists) and should land before any private adoption, so the private side copies a pattern that already builds and passes tests. Prove the output contract on the template machines first.

Follow-up to #5. That change made a single `DEPLOY_FLAKE` the source of truth for a VM's target IP, username, and host-key pin, but the scripts still read those facts out of local `INVENTORY_CHECKOUT` and `SECRETS_CHECKOUT` clones at the revisions the deploy flake's `flake.lock` pins — so an operator still points at three things (`DEPLOY_FLAKE` plus two checkouts that must be clones able to fetch the pinned revs). Expose the derived facts as read-only outputs of the deploy flake and have `rebuild-vm-from-host` / `provision-vm-from-host` read them with `nix eval <deploy>#…`, so the only pointer is `DEPLOY_FLAKE` and the side checkouts go away. This is the "expose the target IP and the pinned host keys as flake outputs" alternative #5 deferred as a larger cross-repo change. ## Context `rebuild-vm-from-host` and `provision-vm-from-host` (via `scripts/lib/rotation-common.sh`) currently resolve the pinned `inventory`/`secrets` revisions from `${DEPLOY_FLAKE}/flake.lock`, ensure those commits are present locally (fetching if needed), then read the target IP from `git show <inv-rev>:scripts/vm-specs.json`, the username from `nix eval path:${SECRETS_CHECKOUT}#lib.vmUsernames.<vm>`, and the host-key material from `git show <secrets-rev>:machine-host-keys.json`; `provision` also reads the age-encrypted host key at the pinned secrets rev. The checkouts exist only to supply file/eval content at the pinned commits, yet they are still two paths the operator must provide and keep as fetchable clones. Evaluating the deploy flake directly makes the pin inherent — the flake already composes the pinned inputs — and eliminates the checkout state entirely. That is the drift-free end state principle 8 points at (one source of truth per fact; consumers derive) and squares with principle 3 (build public, deploy private): the derivation logic lives in the public framework and any private deploy flake inherits it by composition rather than copying it. ## Direction (for the dev plan to settle) Preferred shape: put the output-generating logic in the shared framework (a lib or module in `profiles` or `vm`) so the public template flake and any deploy flake both expose the outputs by composing the framework and supplying only data — no per-fork duplication of logic. Candidate outputs, keyed by VM name, for example `<deploy>#vmTarget.<vm>` (IP, from inventory), `<deploy>#vmUsername.<vm>` (from secrets), and `<deploy>#vmHostKeys.<vm>` (the pinned active/staged public material the pin check compares against). The scripts then replace the flake-lock rev resolution, the `ensure_git_commit_available` fetches, and the `git show` reads for these facts with `nix eval`. Security-sensitive decisions the plan must make and justify: - The fail-closed host-key pin (`assert_any_vm_host_key_material_pinned` / `assert_vm_host_key_material_pinned`) must be preserved at equal strength, now comparing against the material from the flake output instead of `git show`. A regression test that it fails closed on an unpinned or mismatched presented/derived key is required. - `provision` needs the age-encrypted host private key to inject at install. Decide whether to expose that ciphertext as a flake output too — age blobs are safe to surface, and doing so lets provision also drop the secrets checkout — or keep that single file read. Exposing it is the "one pointer" end state; pick and justify. - Whether to keep `INVENTORY_CHECKOUT` / `SECRETS_CHECKOUT` as optional fallbacks or remove them outright once the outputs exist. ## Boundaries Public framework and provisioning tooling only: `allod/nexus` scripts plus read-only outputs added in `allod/profiles` / `allod/vm` / `allod/inventory` / `allod/secrets`, all buildable and testable against the template machines (allod-dev, privacy-1). Private deployments inherit the pattern by composing the framework and supply their own data; their adoption and any data-contract sequencing is tracked privately and is out of scope here. Also out of scope: the deploy-flake composition model itself, machine-data schemas beyond adding outputs, and the separate smaller change of baking `DEPLOY_FLAKE` into the host environment so on-host invocations need no exports. Host-side live rebuild/provision remains a human gate. ## Validation Fixture-based, extending the existing harness and gated on `nix flake check`. Must include a genuine no-checkout invocation (only `DEPLOY_FLAKE` set) that derives IP, username, and host-key material via `nix eval`; the fail-closed pin regression on the output-fed path (refusal plus an assertion that no build ran); and, for provision, that host-key injection still works from whatever source the plan chooses. `tests/rotate-token.sh` and the out-of-scope scripts that share the current helpers must stay green. ## Classification Needs a dev plan (cross-repo: `allod/nexus` plus framework outputs in `allod/profiles` / `vm` / `inventory` / `secrets`). Residual risk likely R3, the same class as #5: provisioning tooling on the host-key security and auth boundary with a cross-repo interface, recoverable by revert. The final integration PR — the nexus scripts consuming the outputs — carries the closing keyword; earlier framework-output PRs use `Refs`. ## Sequencing Public-first and unblocked: this builds on #5 (the `DEPLOY_FLAKE` consumption already exists) and should land before any private adoption, so the private side copies a pattern that already builds and passes tests. Prove the output contract on the template machines first.
vnprc closed this issue 2026-07-14 22:55:13 +01:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
allod/nexus#6
No description provided.