Gunnar

mikronova/draupnir

.nornir/iso-to-fleet.md

# The ISO → N machines path (appliance brief §4)

Status 2026-08-13, measured on henrik's T14s. The brief's ask: a documented,
tested path from "a per-site installer ISO built by the Build Thing" to
"N machines booted from it", and an honest answer on how it is proven without
real hardware. This is that document. Nothing here rebuilds the backend —
`backend-redfish` already does the three actions that matter, and
`plan_fleet()` already fans one spec into N.

## The path, end to end

```
1. BUILD   (product repo, e.g. holger)
   cargo xtask appliance-pack                       # the static app payload
   cargo xtask installer-iso \
       --param site=stockholm-7 \
       --param hub_conf=@site7-hub.conf \
       --sign dev                                   # Secure Boot arming (see below)
   → .iso-out/holger-installer.iso                  # the declared [[build.installer]] cell,
                                                    # applied params ride the attested descriptor

2. HOST    (operator step — draupnir does NOT serve files)
   Redfish VirtualMedia.InsertMedia takes an "Image" the BMC PULLS over the
   network (http/https/nfs URL) — never a local path. Serve .iso-out/ from a
   depot the BMCs can reach; the URL is what goes in the spec:
       https://depot.site7.example/holger-installer.iso

3. PLAN    (draupnir, pure)
   let spec = BootSpec::redfish_iso("site7-holger", iso_url, template_bmc);
   let mut fleet = plan_fleet(&spec, N);            # N members, names -1..-N
   for (i, m) in fleet.iter_mut().enumerate() {     # plan_fleet CLONES the
       m.bmc = Some(BmcEndpoint { .. });            # template BMC — binding
   }                                                # each member to ITS OWN
                                                    # BMC is the CALLER's move
4. BURN    (draupnir, backend-redfish)
   let rf = RedfishBoot::new().with_password(secret)   # secret at drive time,
       .pin_cert_pem(bmc_cert);                        # never in a BootSpec
   boot_fleet(&rf, &fleet)                             # per member:
   # InsertMedia {"Image": iso_url, "Inserted": true, "WriteProtected": true}
   # PATCH Boot {"BootSourceOverrideEnabled": "Once", "BootSourceOverrideTarget": "Cd"}
   # ComputerSystem.Reset {"ResetType": "On"}

5. INSTALL (each machine, unattended)
   korp-boot → pinned kernel → installer PID-1 → INSTALL-OK → reboot
   → first boot: korp-start mounts P3, load-or-MINTS the wg identity, prints
     "wg-appliance identity: product=<p> pubkey=<44-char-b64>"
   → each machine registers its OWN public key with the hub.
```

## What is proven, and where

* **Fleet request shapes** — `redfish.rs` test
  `a_fleet_of_three_shapes_the_same_burn_against_three_distinct_bmcs`:
  `plan_fleet` × per-member BMC × the three request bodies/URLs, byte-shaped
  against the same fixtures the per-request seam has always proven. RED arm:
  `a_member_without_its_own_bmc_refuses_instead_of_borrowing_one`.
* **The install + first-boot mint, per machine, no hardware** — holger's
  fleet KVM proof (`xtask/tests/installer_iso_fleet_kvm.rs`): ONE verb-built
  ISO fanned via `plan_fleet`, each member installed under QEMU/OVMF and then
  FIRST-BOOTED from its own disk; asserts N DISTINCT minted public keys. That
  test rides korp-installer's `kvmproof` harness, NOT draupnir's KVM backend —
  see the wall below.
* **Single-machine legs** (already green elsewhere): install to blank disk,
  real-chain boot, RAID mirror, Secure-Boot signed-boots/unsigned-refused,
  per-site params on the installed ESP, hub.conf byte-for-byte on P3.

## The walls, verbatim and dated (2026-08-13)

1. **No BMC and no Redfish HTTP mock is reachable on this box.** The live
   HTTP exchange (send, status handling, response parsing) is UNMEASURED —
   the wire-shape tests pin exactly the requests that would be sent, nothing
   more. We do not fake a green: a mock BMC server is new capability §4
   explicitly does not ask for. Open question for Rickard/Henrik: mannequin
   (holger's mock-world: PKI+OIDC+Nexus) is the natural home for a minimal
   Redfish mock if one is ever ruled in.
2. **Per-member credentials.** `Boot::boot` reads `spec.bmc` PER MEMBER
   (proven), but the BMC password lives on the `RedfishBoot` instance — one
   password per backend value. A fleet whose BMCs have DISTINCT passwords
   needs one `RedfishBoot` per member (cheap, clonable) — or a shared
   fleet-provisioning account. Deliberately not abstracted away: a credential
   never rides a `BootSpec`.
3. **draupnir's KVM backend cannot boot an ISO** (`ImageSource::suits`: Iso →
   Redfish only; the KVM arm boots kernel+rootfs or kernel+disk). The
   no-hardware install proof therefore stays on korp-installer's `kvmproof`
   harness — per §4's own clause ("where an existing harness already
   hand-rolls qemu … do not break a working proof"; the move is bigger than
   the task). NOTE: origin/main (this checkout is behind it) already carries
   `BootSpec::kvm_bootloader_disk` — a firmware/bootloader disk boot with no
   `-kernel` — which is the exact shape the INSTALLED-disk first-boot leg
   needs; adopting it here awaits the main sync, and would move the first-boot
   leg (not the ISO leg) onto draupnir's seam.
4. **SecureBoot/ResetKeys over Redfish: not driven, assessed SMALL.** Redfish
   standardises a `SecureBoot` resource (PK/KEK/db) and a `ResetKeys` action;
   draupnir's module is virtual media + power only. The request-shaping
   pattern extends directly (one PATCH + one POST action URL, same
   `post`/`patch` plumbing and fixtures) — deliberately not implemented per
   the brief's own instruction ("report whether adding that to draupnir is
   small; do not implement it unless the rest is done").

## Secure Boot on the burned ISO

The brief requires the shipped ISO SIGNED. The verb arms it: `--sign dev`
(throwaway dev identity) or `--sign <pkcs8-key>` beside
`$KORP_SECURE_BOOT_CERT` (production). The enforcing proof
(`installer_iso_secure_boot.rs`, green 2026-08-13): signed boots, unsigned
refused, foreign key refused. An unsigned ISO remains buildable for labs —
the per-site cell decides, and the build record carries the answer.