holger-recipe/src/packaging.rs
//! **The packaging rows** — the `[[build.container]]` appliance image and the
//! `[[build.installer]]` per-site installer ISO, as Rust.
//!
//! Neither row carries a suite tag (korp's 2026-08-03 ruling: the suite axis is
//! variants-only); the tagged POINTER variants in `variants.rs` carry it.
use std::collections::BTreeMap;
use nornir_build_thing::container::{ContainerBuild, ImageFormat, ImageLayer};
use nornir_build_thing::installer::InstallerBuild;
use nornir_build_thing::iso::{IsoBuild, IsoFormat};
use nornir_build_thing::kernel_requires::{clock_requires, hotplug_requires};
use nornir_build_thing::params::{FixtureKind, Landing, ParamKind, ParamSpec};
/// The appliance container row's name (and its pointer variant's `container_ref`).
pub const APPLIANCE: &str = "holger-mvp-appliance";
/// The installer row's name (and its pointer variant's name).
pub const INSTALLER: &str = "holger-installer";
/// The LIVE appliance medium's name (and its pointer variant's name).
pub const APPLIANCE_ISO: &str = "holger-appliance-iso";
/// The cargo variant both packaging rows chain: the static musl `holger-mvp`.
pub const PAYLOAD_VARIANT: &str = "mvp-server-musl";
/// The medium parameter of the per-estate live-update switch (every Holger medium declares it).
pub const LIVE_UPDATE_PARAM: &str = "live_app";
/// Where it lands: korp's cmdline key, read by korp-start and `holger-live`.
pub const LIVE_UPDATE_BOOT: &str = "korp.live_update";
/// The runtime ceremony gate. Declared on the artifact, never in a shell history:
/// the binary refuses to start as an appliance without `appliance` compiled in.
fn appliance_env() -> BTreeMap<String, String> {
BTreeMap::from([("HOLGER_APPLIANCE".to_string(), "1".to_string())])
}
/// holger's registry data root on an installed box: its OWN directory on the data
/// volume, beside the appliance's files (korp's log, the pairing seed, wg/, the
/// console bearer) that live directly in `/data`.
pub const BOX_HOLGER_DATA: &str = "/data/holger";
/// **The primary's console path** (C7): the front serves holger-ui's console on its
/// :443 with the login page at `/login`, the path Gunnar's console has (gunnar-front
/// `LOGIN_PAGES`). Set as `HOLGER_CONSOLE_PATH`, the primary's front page draws its
/// console button to it; unset, the page says there is no browser login.
pub const BOX_CONSOLE_PATH: &str = "/login";
/// The installed box's env: [`appliance_env`] plus `HOLGER_DATA` = [`BOX_HOLGER_DATA`]
/// and `HOLGER_CONSOLE_PATH` = [`BOX_CONSOLE_PATH`].
/// Without it the data root resolves to `.` (the parent of an unanchored `audit` dir)
/// — MEASURED on the mock 2026-09-28: the twin's election refused `[holger-twin-5] .
/// already holds state of its own (./boot/EFI/BOOT/BOOTX64.EFI)`. And it is NOT `/data`
/// itself: with `/data` the same refusal named `/data/holger-console-token`, the
/// bearer the pairing feed writes there before the election.
fn installer_env() -> BTreeMap<String, String> {
let mut e = appliance_env();
e.insert("HOLGER_DATA".to_string(), BOX_HOLGER_DATA.to_string());
e.insert(
"HOLGER_CONSOLE_PATH".to_string(),
BOX_CONSOLE_PATH.to_string(),
);
e
}
/// "How the product starts" — ONE argv tail for both shapes (container `cmd`,
/// metal `korp.args=`). Measured 2026-08-14: without it the installed holger
/// exec'd bare, printed its help and PID 1 died.
fn start_argv() -> Vec<String> {
["start", "--config", "/data/holger.ron"]
.map(str::to_string)
.to_vec()
}
/// **holger-mvp-appliance** — the MVP gateway as a container appliance.
///
/// `base = "scratch"` (Henrik 2026-08-13, the day the musl wall fell): the only
/// layer is the `mvp-server-musl` variant's statically linked binary — no libc, no
/// shell, no distro. ONE volume, `/data`, carries config + registry state + the
/// WireGuard identity (the hub key arrives as `/data/wg/hub.conf`; the appliance
/// mints its own keypair there on first start). Runtime contract (proved by
/// `build-space/tests/appliance_container_robot.rs`): `-v <vol>:/data` plus
/// `--cap-add NET_ADMIN --device /dev/net/tun` when a hub is declared; without
/// them a declared hub FAILS LOUDLY by name. Exported to `archive` so a release
/// carries a FILE both podman and docker load.
///
/// `distribution/Dockerfile` is deliberately NOT a file-mode row: its `COPY . .`
/// context cannot resolve holger's sibling path deps, so the row could not build
/// (measured 2026-08-13 on a sibling-free tree).
pub fn container() -> ContainerBuild {
ContainerBuild {
name: APPLIANCE.to_string(),
base: "scratch".to_string(),
layers: vec![ImageLayer {
executable: true,
..ImageLayer::from_variant(PAYLOAD_VARIANT, "/holger-mvp")
}],
entrypoint: vec!["/holger-mvp".to_string()],
cmd: start_argv(),
workdir: Some("/data".to_string()),
env: appliance_env(),
expose: vec![50051, 8443],
tags: vec![format!("{APPLIANCE}:0.6.9")],
format: ImageFormat::Oci,
archive: format!(".container-out/{APPLIANCE}.tar"),
..ContainerBuild::default()
}
}
/// **holger-installer** — the per-site installer ISO (2026-08-13; first mastered
/// and install-proven 2026-08-15).
///
/// Composed and mastered by edda's ONE implementation
/// (`korp_installer::isocompose`): the real korp-boot first stage, the static-musl
/// korp-installer PID 1 and korp-start, a blake3-pinned kernel, and the payloads
/// on the ISO-9660 volume (El Torito's boot-image ceiling forbids them in the
/// ESP). `installer_variant` is EMPTY on purpose: the composition builds the
/// PID 1 itself. The kernel is the DECLARED donor `tunnr/tunnr-kernel-sandbox`
/// ([`kernel_donor`]) — owner ruling 2026-09-27, "tunnr and 3 images just like
/// gunnar": every holger medium boots tunnr's kernel, built by the Build Thing
/// (`dwarves buildthing tunnr --variant tunnr-kernel-sandbox`, or `--build kernel`
/// on this row), never an operator file and never the build box's `/boot`.
///
/// The PARAMS are the per-site inputs (secrets refused by name); the applied
/// values ride the attested descriptor, so the build record answers which ISO
/// holds which hub key without booting it.
pub fn installer() -> InstallerBuild {
InstallerBuild {
name: INSTALLER.to_string(),
variant: PAYLOAD_VARIANT.to_string(),
volume_id: "HOLGER_INSTALL".to_string(),
target_disk_gib: 64,
// THE MARK ON THE BOOT SCREEN. Without it holger's installer boots wearing
// korp's raven (measured 2026-08-16: gunnar's, korp's and holger's boot
// framebuffers were byte-identical). The only image holger has — a
// portrait drawn for the znippy write-up; replace with a landscape mark
// when there is one.
boot_logo: Some(".nornir/assets/holger-znippy-logo.png".to_string()),
boot_params: ["console=ttyS0", "korp.appliance=lean", "korp.entry=holger"]
.map(str::to_string)
.to_vec(),
cmd: start_argv(),
// The metal twin of the container row's env: rides the installed cmdline
// as korp.env.* tokens. Without it the served metal appliance ran with its
// tunnel dark (measured 2026-08-14).
env: installer_env(),
// ★ /data IS THE DATA VOLUME, as xfs — gunnar's shape (`[build.installer.data]
// fs = "xfs"`). Absent, korp lays /data as FAT on the system disk's P3 and the
// estate's data volume is never used (MEASURED on the mock 2026-09-28:
// `data p3 formatted FAT (label KORP-DATA)`, vdb untouched). xfs also pulls
// CONFIG_XFS_FS into the donor through the data group's own kernel rows.
data: nornir_build_thing::data::Data { fs: nornir_build_thing::data::DataFs::Xfs, ..Default::default() },
params: vec![
// Lands on the INSTALLER's cmdline as korp.site (visible provenance).
ParamSpec {
name: "site".to_string(),
label: "Site name".to_string(),
kind: ParamKind::Text,
required: false,
boot_param: "korp.site".to_string(),
..ParamSpec::default()
},
// Lands on the ISO volume AND at /run/installer/hub.conf, laid onto P3
// as wg/hub.conf for the first boot's ceremony. `@fixture` mints it
// (see `fixtures` below).
ParamSpec {
name: "hub_conf".to_string(),
label: "Hub declaration (the ceremony hub.conf, public data)".to_string(),
kind: ParamKind::Text,
required: false,
staged_file: "hub.conf".to_string(),
..ParamSpec::default()
},
// REQUIRED: what this appliance serves is a claim no default may invent — a
// synthesized config reproduces "boots but does not serve" one level down.
//
// ★ IT LANDS ON THE RELEASE (lane B1), not on /data. It used to be a first-boot
// stage onto /data/holger.ron: a keep medium skips that stage by design, so no
// release could ever change a standing box's declaration, and R10's installed
// main panicked on a /data/holger.ron that never came. Now it rides the medium
// WITH the app, signed with it; every install (keep included) and every live
// update lays it, and korp-start hands it to the app under /run/korp/release —
// as Gunnar's release config rides its signed cmdline. A file at the path `cmd`
// names (/data/holger.ron) is the SITE's override (`server_lib::declaration`).
ParamSpec {
name: "holger_ron".to_string(),
label: "Server declaration (holger.ron)".to_string(),
kind: ParamKind::Text,
required: true,
staged_file: "holger.ron".to_string(),
lands: Landing::Release,
..ParamSpec::default()
},
// The korp root (PEM: a staged parameter is text) the estate's
// `ron_plugin_trust_roots` names (lane W2, `estate_ron::KORP_ROOT_STAGED`):
// every loaded module must chain to it. `cargo xtask estate-media` stages
// `estate_ron::KORP_ROOT_PEM`. Optional: a medium without it boots, and a
// box that then loads a meter guest is refused by name (`holger-plugin-8`).
// It rides the release with the declaration that names it (lane B1), so a
// release can rotate it — a /data stage could not.
ParamSpec {
name: "plugin_trust_root".to_string(),
label: "Plugin trust root (korp-root-ca, PEM)".to_string(),
kind: ParamKind::Text,
required: false,
staged_file: crate::estate_ron::KORP_ROOT_STAGED.to_string(),
lands: Landing::Release,
..ParamSpec::default()
},
// The estate's intel trust policy (lane P5): the update-bundle `TrustPolicy` (RON)
// pinning the estate's OWN intel signers, which `intel.trust_policy` names on the
// release (`estate_ron::INTEL_TRUST_STAGED`) and every carried `holger.intel/v1`
// bundle at `POST /-/intel/import` verifies against. The medium carries the slot;
// the operator supplies the policy (`cargo xtask estate-media --intel-trust`), since
// the signers are the estate's, never a constant of the medium. Optional: a medium
// without it boots, and an intel import is then refused by name (`holger-import-4`),
// so an airgap box never takes an unverified advisory database. It rides the release
// with the declaration that names it, so a release can rotate the signers.
ParamSpec {
name: "intel_trust_policy".to_string(),
label: "Intel trust policy (holger.intel/v1 signers, RON)".to_string(),
kind: ParamKind::Text,
required: false,
staged_file: crate::estate_ron::INTEL_TRUST_STAGED.to_string(),
lands: Landing::Release,
..ParamSpec::default()
},
// The bundles door's trust policy (lane HOL-BUNDLES-DOOR): the signers whose bundles
// the estate's `bundles` door stores for its downstreams
// (`promotion.intake.bundles_trust_policy`, `estate_ron::BUNDLES_TRUST_STAGED`). The
// operator supplies it (`cargo xtask estate-media --bundles-trust`). Optional: a medium
// without it boots, and every PUT into `bundles` is refused by name (`holger-bundles-1`).
ParamSpec {
name: "bundles_trust_policy".to_string(),
label: "Bundles trust policy (the signers of published bundles, RON)".to_string(),
kind: ParamKind::Text,
required: false,
staged_file: crate::estate_ron::BUNDLES_TRUST_STAGED.to_string(),
lands: Landing::Release,
..ParamSpec::default()
},
// Per-site UNDERLAY address (<addr>/<prefix>@<if>) for static-IP
// sites, applied through the shared addr writer before the tunnel
// initiates. Optional.
ParamSpec {
name: "underlay".to_string(),
label: "Underlay address (addr/prefix@interface)".to_string(),
kind: ParamKind::Text,
required: false,
boot_param: "korp.env.HOLGER_UNDERLAY".to_string(),
..ParamSpec::default()
},
// ★★ **THE TWIN IS A ROLE CONFERRED AT BOOTSTRAP — AND THERE IS NO
// ROLE PARAMETER ON THIS MEDIUM, DELIBERATELY.**
//
// Owner rulings 2026-09-21, verbatim and in order:
// "gunnar twin must be built EXACTLY like the main, it MUST use
// the SAME iso !!!" / "DO not build a special gunnar TWIN iso !"
// "the weakest machine must become the twin. PERIOD"
// "IT must not CHANGE role later ! so its a bootstrap thing from
// xtask"
//
// A role parameter HERE would satisfy the first ruling only in the
// weakest sense: one row, but TWO COMPOSES, so two ISOs — which is
// not what "the SAME iso" says. It was tried on this lane
// (`17e3f65`, `--param role=twin --param primary=…`) and is
// deliberately removed again.
//
// So this medium carries NO role. Both boxes install from one
// identical ISO and come up as PRIMARIES, because an absent role is
// `Primary`, always. The control plane then MEASURES them and calls
// `AdminService.BecomeTwin` on the weaker one, exactly once: the
// election is recorded in `<data_root>/holger-role.ron`
// (`server/lib/src/twin.rs`), a second election refuses by name
// (`holger-twin-4`), and a box that is not FRESH refuses by name
// (`holger-twin-5`).
//
// The role is not in `holger.ron` either: `[estate-twin-drift]`
// requires the twin's config to be byte-identical to the primary's,
// and writing the role into the file would mean writing an exception
// into the one rule that guards gunnar's un-metered-twin scar.
//
// ★ THE BOOT DISK IS THE LARGER ONE — and this is how a site SAYS so.
//
// MEASURED on gunnar's twin, 2026-09-17: two disks of the same size form
// no equal-size group, so `korp_installer::layout` falls through to
// class-then-LARGEST with no tie to break, elects the DATA volume as the
// boot disk, lays the ESP there and the machine answers `No bootable
// device`. private-holger-ops' `estate.rs` defends the same failure from
// the other side ([estate-boot-disk] refuses a system disk that is not
// strictly larger) and `estate.toml` says, verbatim, "the installer medium
// is still handed `korp.data.disks=<dev>` — holger's installer row
// declares a `data_disks` param for exactly this". IT DID NOT. The
// declaration and the estate disagreed by one row, and the disagreement
// was invisible until somebody passed `--param data_disks=vdb` and had it
// refused by name as an unknown parameter.
//
// It lands on `korp.data.disks` — `korp_installer::cmdline::DATA_DISKS_KEY`
// — which `hwdetect::pick_target_disk_pinned_at` and
// `layout::pick_boot_declared` read BEFORE the size heuristic runs. Not
// required: a site with one disk has nothing to declare and a required
// parameter it cannot answer is a door it cannot open. A site with TWO
// declares it, and the declaration survives a later resize that the size
// rule would not.
ParamSpec {
name: "data_disks".to_string(),
label: "Data disk(s) by device name, comma-separated (e.g. vdb) — \
everything NOT named here is the boot disk"
.to_string(),
kind: ParamKind::Text,
required: false,
boot_param: "korp.data.disks".to_string(),
..ParamSpec::default()
},
// ★ THE PAIRING SEED (owner plan 2a, 2026-09-27; gunnar-recipe be7f611 is
// the model): the estate's nordisk-pairing seed.toml — estate, front,
// installer CA, the medium's bootstrap certificate AND ITS KEY. A STAGED
// FILE, never a boot param (multi-line PEM; the cmdline is readable to
// anyone who sees the boot); lands at /data/pairing/seed.toml, where
// holger-server `paired` reads it and serves the installer's listener.
// One seed per MEDIUM, nothing per box — the role is still ELECTED by the
// installer (the weakest box is the twin) and recorded once, so this
// keeps the one-ISO ruling above. Not required: a medium without it
// composes and boots exactly as before, unpaired.
ParamSpec {
name: "pairing_seed".to_string(),
label: "Pairing seed (nordisk-pairing seed.toml: estate, front, installer CA, bootstrap cert+key)".to_string(),
kind: ParamKind::Text,
required: false,
staged_file: "pairing/seed.toml".to_string(),
..ParamSpec::default()
},
// ★ RE-IMAGE AND RE-MINT GO TOGETHER — and this is the word that chooses.
//
// MEASURED on gunnar's twin, 2026-09-17: keeping a data volume across a
// re-image that re-mints the machine's identity is a BRICK. Everything
// sealed on that volume is sealed under the OLD KEK,
// `ssh/host_ed25519` will not decrypt, and holger is PID 1 on this
// appliance, so it becomes a kernel panic with no console to read it on
// (`shell = false`, both machines).
//
// `korp.reinstall` (`korp_installer::cmdline::REINSTALL_KEY`) is the one
// opt-out of the data-survives rule: `keep` upgrades in place and `wipe`
// lays the set fresh. ABSENT OR GARBAGE READS AS `Keep`
// (`KorpCmdline::reinstall`) — which is exactly the brick path when the
// identity is re-minted, so the value was being chosen by default and by
// nobody. Declaring it does not change the default; it makes the choice
// SAYABLE at build time, on the medium, next to the site it was built
// for, instead of being typed at a boot prompt on a machine with no shell.
ParamSpec {
name: "reinstall".to_string(),
label: "Reinstall policy for a disk that already carries data: \
keep (upgrade, /data survives — only safe when the identity \
is NOT re-minted) | wipe (lay it fresh)"
.to_string(),
kind: ParamKind::Text,
required: false,
boot_param: "korp.reinstall".to_string(),
..ParamSpec::default()
},
// ★ WHAT HAPPENS AFTER INSTALL-OK — gunnar's answer to the reinstall loop
// (gunnar-recipe `install_then`, private-gunnar-ops 2026-09-26 "the install
// takes 5 minutes"). An estate lays every VM on a BLANK system disk with boot
// order `disk,cdrom`: the blank disk does not boot, the firmware falls through
// to this medium, korp-installer lays the disk and — with `reboot` — the laid
// disk now boots FIRST. No power-off, detach, boot-order flip or start from
// outside; the installer ejects the medium once it closed. `poweroff` is for a
// re-image tool that detaches the medium itself. Absent: korp-installer's
// default, unchanged. (`korp_installer::cmdline::INSTALL_THEN_KEY`.)
ParamSpec {
name: "install_then".to_string(),
label: "After INSTALL-OK: reboot (a person at the box) | poweroff (a cloud re-image tool detaches the medium)".to_string(),
kind: ParamKind::Text,
required: false,
boot_param: "korp.install.then".to_string(),
..ParamSpec::default()
},
// ★ THE LIVE-UPDATE SWITCH, per estate (the pattern's `live_app`): `--param
// live_app=off` lands `korp.live_update=off` on the cmdline, which korp-start
// reads (it ignores a staged slot) and holger-live reads
// (`Policy::live_update`): the box never takes a live app, every new app is a
// base update from the keep medium. Part of the BASE, never flipped live.
// Absent = on, exactly as before (nothing is baked).
ParamSpec {
name: LIVE_UPDATE_PARAM.to_string(),
label: "Live updates of the app: on (default) | off (every new app is a base update)".to_string(),
kind: ParamKind::Text,
required: false,
boot_param: LIVE_UPDATE_BOOT.to_string(),
..ParamSpec::default()
},
],
// The DECLARED FIXTURE for hub_conf (2026-08-22): `--param hub_conf=@fixture`
// makes the forge mint a userspace WireGuard hub; its private half stays
// 0600 in the forge's state dir, the record says the value came from `wg-hub`.
fixtures: BTreeMap::from([("hub_conf".to_string(), FixtureKind::WgHub)]),
// THE KERNEL — tunnr's, declared (see the doc above and [`kernel_donor`]).
kernel_donor: Some(kernel_donor()),
// ★ WHAT THE PAIRED BOX ASKS OF ITS KERNEL, beyond the appliance-boot contract
// `kernel_donor` implies. Every holger medium pairs (holger `paired`, the front's
// `frontside`): the installer's link and the estate's wg0 are userspace WireGuard
// (boringtun) over a TUN device. MEASURED on the mock 2026-09-28: the donor built
// for appliance-boot alone carried `# CONFIG_TUN is not set`, and the installed
// box answered `wg-installer: open /dev/net/tun … No such device (os error 19)`
// and waited for an installer it could never hear. Unconditional (no option):
// pairing is not a toggle on any holger payload.
// THE WALL CLOCK the kernel must carry: kvmclock (edda nornir-build-thing
// `kernel_requires::clock_requires`, moved AS IS out of gunnar-recipe). The
// donor's `nuked` tinyconfig base leaves `# CONFIG_HYPERVISOR_GUEST is not
// set` — MEASURED on this row's own build (bzImage 0b5205a2…, 2026-09-28) —
// and without it x86 reads the emulated CMOS, which UpCloud fills with
// local time: +2 h, measured on gunnar's appliance 2026-09-20, every
// credential outside its skew. A row-level requirement, so `--build kernel`
// MERGES it into the donor's fragment (and a donor that vetoes it is a
// refusal by name). Every clone of this row (metered, front) carries it.
// PCI HOT-PLUG the kernel must carry (edda `kernel_requires::hotplug_requires`,
// moved AS IS out of gunnar-recipe, Gunnar 35290f29 / b247bf64): UpCloud resizes
// only DETACHED volumes, so a runtime grow is detach → resize → re-attach, and a
// kernel with `# CONFIG_HOTPLUG_PCI is not set` never sees the member leave or come
// back — the grow answers 511 HOTPLUG_FAILED. Every installed medium (this row and
// its clones); not the live one, whose `/data` is RAM.
requires: {
let mut r = pairing_kernel_requires();
r.extend(clock_requires());
r.extend(hotplug_requires());
r.extend(container_kernel_requires());
r
},
// THE SCAN SANDBOX RIDES THE APP ARCHIVE (lane HOLGER-MEDIA, 2026-10-23): see
// [`sandbox_payload_entries`]. The front clears it (it scans nothing).
payload_entries: sandbox_payload_entries(),
..InstallerBuild::default()
}
}
/// **The pinned Sigstore public-good `trusted_root.json`** (lane HOL-OCI-EVIDENCE): the TUF
/// target the release build fetches (`https://tuf-repo-cdn.sigstore.dev/targets/<this>.trusted_root.json`)
/// and signs into the medium's trust bundle. Held equal to server-lib `sigstore_trust::PINNED_SHA256`.
pub const SIGSTORE_TRUSTED_ROOT_SHA256: &str =
"6494e21ea73fa7ee769f85f57d5a3e6a08725eae1e38c755fc3517c9e6bc0b66";
/// The signed trust bundle the release stages beside the sandbox (`cargo xtask sigstore-trust stage`).
pub const SIGSTORE_TRUST_BUNDLE: &str = "sigstore-trust.holger.znippy";
/// The bundle trust policy that pins the release's trust identities, staged beside it.
pub const SIGSTORE_TRUST_POLICY: &str = "bundle-trust.ron";
/// **The fetching medium's own worker** (lane cds4, HOL-CDS-4): `holger-fetch-worker`'s rootfs,
/// staged beside the sandbox by `cargo xtask scan-worker stage`, laid by the fetching
/// downstream's medium alone ([`fetch_payload_entries`]).
pub const FETCH_WORKER_ROOTFS: &str = "holger-fetch-worker.rootfs.tar";
/// Heimdall's shim on the medium (holger-sandbox `media::HEIMDALL_SHIM`).
pub const HEIMDALL_SHIM: &str = "heimdall-container-shim";
/// The released verify-bundle guests' release directory in the holger checkout.
pub const HEIMDALL_GUESTS_DIR: &str = "guests/heimdall/0.2.1";
/// Every file of the release the medium lays (holger-job-runner `guests::RELEASE`).
pub const HEIMDALL_GUESTS: [&str; 10] = [
"heimdall-verify-bundle-verifier.wasm",
"heimdall-verify-bundle-verifier.wasm.blake3",
"heimdall-verify-bundle-verifier.wasm.p7s",
"heimdall-verify-bundle-decoder.wasm",
"heimdall-verify-bundle-decoder.wasm.blake3",
"heimdall-verify-bundle-decoder.wasm.p7s",
"heimdall-verify-bundle.wasm",
"heimdall-verify-bundle.wasm.blake3",
"heimdall-verify-bundle.wasm.p7s",
"heimdall-guests-0.2.1.intoto.jsonl",
];
/// Where `cargo xtask scan-worker stage` writes the sandbox entries (xtask
/// `scan_worker::STAGE_OUT`, held equal by a test there).
pub const SANDBOX_STAGE_DIR: &str = ".sandbox-out";
/// **The scan sandbox the main and twin media carry**, as `payload_entries` of
/// the app archive: the static OCI runtime (`crun`), the scan worker's rootfs,
/// ClamAV's rootfs (the pinned image flattened, its signature database inside)
/// and the manifest with each entry's sha256 (`holger_sandbox::media`). Packed by
/// the forge into the SAME signed `.znippy` korp-start verifies at every boot,
/// so the installer, the metered installer, the keep medium and the live app all
/// carry it; the box lays it under `/data/holger/sandbox` before the intake
/// chain opens (`rules: (sandbox: required)` holds from the first scan). An OS-less
/// appliance has no podman and no image store; crun over a laid rootfs is the
/// engine (1.9 MB), with Holger writing the bundle.
#[must_use]
pub fn sandbox_payload_entries() -> BTreeMap<String, String> {
// HOL-OCI-EVIDENCE: the release's signed Sigstore trust root and its policy ride beside
// the sandbox; the box seeds them into <data_root>/holger-trust before the chain opens.
// HOL-HEIMDALL-03: Heimdall's static shim and the released verify-bundle guests (holger
// `guests/heimdall/0.2.1/`, all ten files), staged by `cargo xtask scan-worker stage`.
[
"manifest.toml",
"crun",
"holger-scan-worker.rootfs.tar",
"clamav.rootfs.tar",
SIGSTORE_TRUST_BUNDLE,
SIGSTORE_TRUST_POLICY,
HEIMDALL_SHIM,
]
.into_iter()
.chain(HEIMDALL_GUESTS)
.map(|n| (format!("sandbox/{n}"), format!("{SANDBOX_STAGE_DIR}/{n}")))
.collect()
}
/// **The fetching downstream's entries**: the scan sandbox's, and the fetch worker's rootfs
/// (lane cds4, HOL-CDS-4). Only [`downstream_installer`] lays them; the fetch code is never on
/// a one-way, main, twin, keep or front medium.
#[must_use]
pub fn fetch_payload_entries() -> BTreeMap<String, String> {
let mut e = sandbox_payload_entries();
e.insert(
format!("sandbox/{FETCH_WORKER_ROOTFS}"),
format!("{SANDBOX_STAGE_DIR}/{FETCH_WORKER_ROOTFS}"),
);
e
}
/// **What the scan sandbox asks of the kernel**, on every holger medium (the front
/// too: every holger medium boots ONE donor kernel, pinned per requirement
/// fragment, so the front asking less would be refused "the built kernel is not
/// the CHOSEN kernel", measured on the mock 2026-09-28). The `nuked` donor is a
/// one-user kernel: no namespaces, no cgroups, `MULTIUSER=n` (the appliance-boot
/// contract's default, lifted here by requiring it `=y`: edda `ApplianceBootContract`).
/// Each row is one limit of the sandbox (`holger_sandbox::crun`).
#[must_use]
pub fn container_kernel_requires() -> Vec<nornir_build_thing::requires::EdgeRequirement> {
use nornir_build_thing::kernel_requires::KconfigReq;
use nornir_build_thing::requires::{EdgeRequirement, Requirement};
[
KconfigReq::y("CONFIG_MULTIUSER", "the scan sandbox runs every scan as uid 65532 in a user namespace: NAMESPACES and CGROUPS depend on MULTIUSER (lifts the appliance-boot contract's one-user default by name)"),
KconfigReq::y("CONFIG_NAMESPACES", "the sandbox is a container: pid, mount, network, uts, ipc and user namespaces"),
KconfigReq::y("CONFIG_USER_NS", "the worker's uid 65532 maps to an unprivileged host range; host root is never inside (the sandbox's `require_rootless`)"),
KconfigReq::y("CONFIG_PID_NS", "the worker sees only itself; a kill at the deadline reaches every process of it"),
KconfigReq::y("CONFIG_NET_NS", "`--network none`: a new, empty network namespace, no interface at all"),
KconfigReq::y("CONFIG_UTS_NS", "its own hostname (holger-scan, holger-clamd)"),
KconfigReq::y("CONFIG_SYSVIPC", "IPC_NS depends on SYSVIPC or POSIX_MQUEUE"),
KconfigReq::y("CONFIG_IPC_NS", "no shared memory or message queue reaches the server from the worker"),
KconfigReq::y("CONFIG_CGROUPS", "the limits: cgroup v2 at /sys/fs/cgroup (memory.max, pids.max, cpu.max)"),
KconfigReq::y("CONFIG_MEMCG", "memory.max and memory.swap.max: the worker's 1 GiB, clamd's 2 GiB, no swap"),
KconfigReq::y("CONFIG_CGROUP_PIDS", "pids.max: 64 for a scan, 256 for clamd (a fork bomb in an artifact ends there)"),
KconfigReq::y("CONFIG_CGROUP_SCHED", "the cpu controller cpu.max hangs off"),
KconfigReq::y("CONFIG_FAIR_GROUP_SCHED", "the CFS group the cpu quota is accounted on"),
KconfigReq::y("CONFIG_CFS_BANDWIDTH", "cpu.max: one core for a scan, two for clamd"),
KconfigReq::y("CONFIG_SECCOMP", "the containers default seccomp profile is applied to every scan (Seccomp: 2 from inside)"),
KconfigReq::y("CONFIG_SECCOMP_FILTER", "the profile is a BPF filter (SECCOMP_SET_MODE_FILTER)"),
KconfigReq::y("CONFIG_TMPFS", "the capped noexec scratch space and clamd's /tmp"),
// HEIMDALL STRICT (lane hol-312fix, 2026-11-03; the worker's kernel-usage report,
// `.nornir/kernel-usage/holger-scan-worker/`): the three things the appliance
// kernel lacked. The shared `tunnr-kernel` vetoes the LSM stack for one-user
// boxes, so the donor is tunnr's sandbox row ([`kernel_donor`]).
KconfigReq::y("CONFIG_SECURITY", "Heimdall strict: the LSM menu Landlock hangs off (security/Kconfig; depends on MULTIUSER, lifted above)"),
KconfigReq::y("CONFIG_SECURITY_LANDLOCK", "Heimdall strict: the scan worker is restricted with Landlock (filesystem and network rules no root inside the sandbox can widen)"),
KconfigReq::text("CONFIG_LSM", "landlock,lockdown,yama", "Heimdall strict: Landlock in the LSM boot order by name, not by the kernel's default string (the donor's closure states the same order)"),
KconfigReq::y("CONFIG_CROSS_MEMORY_ATTACH", "Heimdall strict: process_vm_readv(2), which Heimdall reads the traced worker's memory with (tinyconfig leaves it off)"),
]
.into_iter()
.map(|r| EdgeRequirement { edge: "kernel".to_string(), what: Requirement::Kconfig(r) })
.collect()
}
/// The kernel symbols the paired box's link needs (see [`installer`]), addressed to
/// the `kernel` edge (the donor, [`kernel_donor`]).
#[must_use]
pub fn pairing_kernel_requires() -> Vec<nornir_build_thing::requires::EdgeRequirement> {
use nornir_build_thing::kernel_requires::KconfigReq;
use nornir_build_thing::requires::{EdgeRequirement, Requirement};
[
KconfigReq::y("CONFIG_NET", "the box serves gRPC/HTTP and dials its installer: a kernel with no network stack has no socket to give"),
KconfigReq::y("CONFIG_INET", "the WireGuard link's outer packets are UDP over IPv4, and CONFIG_TUN depends on INET (drivers/net/Kconfig)"),
KconfigReq::y("CONFIG_TUN", "boringtun, the userspace WireGuard of nordisk-pairing (the installer's link and wg0), opens /dev/net/tun — measured missing on the mock 2026-09-28 (ENODEV)"),
]
.into_iter()
.map(|r| EdgeRequirement { edge: "kernel".to_string(), what: Requirement::Kconfig(r) })
.collect()
}
/// **The kernel every installed holger medium boots**: `tunnr/tunnr-kernel-sandbox`.
/// Owner ruling 2026-09-27: "tunnr and 3 images just like gunnar" — tunnr's kernel,
/// declared once here. Since 2026-11-03 (lane hol-312fix) it is tunnr's SANDBOX row,
/// not the shared `tunnr-kernel` gunnar's media declare: the same `nuked` preset and
/// `hardened` profile, with the LSM stack the shared closure vetoes for one-user
/// boxes turned on (tunnr `variants/kernel/tunnr-sandbox-hardening.config`), because
/// Heimdall strict needs Landlock ([`container_kernel_requires`]).
///
/// The forge reads the donor's built, pin-verified bzImage (the dwarves ledger by
/// pin, else `tunnr/.kernel-out/tunnr-kernel-sandbox/`); NOT BUILT is a refusal
/// naming `dwarves buildthing tunnr --variant tunnr-kernel-sandbox`, never a
/// fall-through to the build box's own kernel.
#[must_use]
pub fn kernel_donor() -> nornir_build_thing::installer::KernelDonor {
nornir_build_thing::installer::KernelDonor {
repo: "tunnr".to_string(),
kernel: "tunnr-kernel-sandbox".to_string(),
}
}
/// **The live medium's kernel**: the shared `tunnr/tunnr-kernel`. The live medium
/// runs from RAM and asks its kernel for the clock only (no scan sandbox rows), so
/// it is a one-user consumer, and tunnr's sandbox row refuses one-user consumers by
/// name (its closure turns MULTIUSER on).
#[must_use]
pub fn live_kernel_donor() -> nornir_build_thing::installer::KernelDonor {
nornir_build_thing::installer::KernelDonor {
repo: "tunnr".to_string(),
kernel: "tunnr-kernel".to_string(),
}
}
/// The METERED medium's name (and its pointer variant's name).
pub const METERED_INSTALLER: &str = "holger-installer-metered";
/// Its volume id — its OWN, because it carries a different binary
/// (`holger-mvp-metered`, which holds the entitlement door and holger's own
/// signed monetize plugin), and a medium is resolved to its row by volume id
/// (gunnar's `GUNNAR_INSTALL_METERED`, for the same reason).
pub const METERED_VOLUME_ID: &str = "HOLGER_INSTALL_METERED";
/// The cargo variant the metered medium lays down: the static musl
/// `holger-mvp-metered` (see `variants.rs`).
pub const METERED_PAYLOAD_VARIANT: &str = "mvp-server-musl-metered";
/// The root feature under which the metered payload consumes holger's own plugin.
pub const OWN_PLUGIN_FEATURE: &str = "plugin";
/// The checkout that declares the row building it (monetize-impl, COMMERCIAL).
pub const OWN_PLUGIN_REPO: &str = "monetize-impl";
/// monetize-impl's row that builds AND SIGNS it from this checkout
/// (`manifest_dir = "../holger"`, package `holger-monetize-guest`).
pub const OWN_PLUGIN_ROW: &str = "holger-guest";
/// **holger's own signed monetize plugin, as a consumption EDGE** of the metered
/// musl payload: `plugin → monetize-impl/holger-guest : wasm`.
///
/// Declared here rather than known to a door (owner, 2026-09-27: "all must go
/// trough build thing"): the forge reads the edge, builds and signs the donor row
/// in its container, refuses a module whose `holger_build_ref` custom section is
/// not this checkout's commit, and stages module + `.p7s` into `.plugin-out/`,
/// where `server/lib/build.rs` (feature `metered`) embeds them. gunnar's door did
/// the same by hand (`gunnar/xtask/src/plugin_stage.rs`); here the declaration
/// says it and dwarves' media steps act on it.
#[must_use]
pub fn own_plugin_edge() -> (String, nornir_build_thing::tree::EdgeDecl) {
(
OWN_PLUGIN_FEATURE.to_string(),
nornir_build_thing::tree::EdgeDecl {
repo: OWN_PLUGIN_REPO.to_string(),
variant: OWN_PLUGIN_ROW.to_string(),
typ: nornir_build_thing::variant::ArtifactKind::Wasm,
// LAZY (owner rule: the open build never needs monetize-impl): the
// declaration loads, lists and builds its open rows without the
// commercial checkout; building the sold row refuses by name without it.
lazy: true,
},
)
}
/// **holger-installer-metered** — the medium the PAID estate installs from, built
/// exactly like gunnar's `gunnar-installer-metered` (`metered_twin` in
/// gunnar-recipe): the open row [`installer`], cloned, with only `name`,
/// `variant` and `volume_id` its own.
///
/// # ONE medium for vm2 AND vm3 — still no role parameter
///
/// The server (vm2) and the twin (vm3) both boot THIS ISO. Everything
/// [`installer`] says about the role holds verbatim: no `role` param, both boxes
/// install as primaries, and the installer's control plane elects the weaker one
/// twin exactly once. A metered estate must not have an un-metered twin (gunnar's
/// scar), and with one medium it cannot: the twin runs the same
/// `holger-mvp-metered` with the same plugin.
///
/// # Why a second ROW, and not an option on the open one
///
/// `metered` is a cargo feature, so it selects a different VARIANT and therefore
/// a different artifact; an `[[build.installer.options]]` toggle chooses features
/// WITHIN one variant's build and cannot swap the variant a medium is composed
/// from (gunnar's reasoning, verbatim).
///
/// # What else rides the metered binary
///
/// holger's OWN signed monetize plugin (`holger_monetize_guest.wasm` + `.p7s`),
/// embedded by `server/lib/build.rs` from `.plugin-out/` and refused there by
/// name when its `holger_build_ref` is another commit's. The door that composes
/// this medium stages it first (`cargo xtask installer-iso`, `plugin_stage`).
#[must_use]
pub fn metered_installer() -> InstallerBuild {
let mut m = installer();
m.name = METERED_INSTALLER.to_string();
m.variant = METERED_PAYLOAD_VARIANT.to_string();
m.volume_id = METERED_VOLUME_ID.to_string();
m.options.push(dev_narrate_option());
m
}
/// The `--opt` name of the DEV-NARRATE option (gunnar-recipe's spelling).
pub const DEV_NARRATE_OPTION: &str = "dev-narrate";
/// The symbol family that must be ABSENT from every OFF (release) payload: Skidbladnir's
/// `nordisk-narrate` crate, sender and all.
pub const DEV_NARRATE_FORBIDDEN: &str = "nordisk_narrate";
/// **DEV-NARRATE** (owner 2026-10-19: Holger's test media are DEV, "EXACTLY like gunnar"):
/// the box's boot log, sealed to the installer's key (the seed's `installer_pubkey`), over
/// udp/51822 from the moment the NIC has an address — before the installer's WireGuard link.
///
/// **Default OFF**: a medium that says nothing carries no sender, and the composer's audit
/// reads [`DEV_NARRATE_FORBIDDEN`] back out of the packed binary to prove it. ON is the DEV
/// medium (`cargo xtask estate-media`'s default kind, `--opt dev-narrate=on`), and resolves
/// to the payload's own `…-narrate` row (`variants::with_narrate_rows`). Either way the medium
/// bakes its kind, `korp.env.NORDISK_MEDIA_KIND=dev|release` (nornir-build-thing `media_gate`).
#[must_use]
pub fn dev_narrate_option() -> nornir_build_thing::options::OptionSpec {
nornir_build_thing::options::OptionSpec {
name: DEV_NARRATE_OPTION.to_string(),
label: "DEV-NARRATE (sealed early-boot log to the installer, udp/51822) — dev media only"
.to_string(),
default: false,
feature: crate::variants::DEV_NARRATE.to_string(),
params_when_on: Vec::new(),
params_when_off: Vec::new(),
param_kinds: BTreeMap::new(),
forbid_symbols: vec![DEV_NARRATE_FORBIDDEN.to_string()],
// The medium records its kind on its command line (lane B4, A9 row 10): ON is a DEV
// medium, OFF the release, read back before any upload (`estate-media`).
bakes: Some(nornir_build_thing::media_gate::kind_bake()),
kernel_requires: Vec::new(),
requires: Vec::new(),
}
}
/// **The keep media** (monetize lane L7, `lifecycle::Holger`): the ONE shop upgrades a sold
/// Holger IN PLACE by booting each box once from its keep medium — main and twin from
/// [`KEEP_MEDIUM`], the front from [`FRONT_KEEP_MEDIUM`]. Gunnar's `gunnar-keep` is the same
/// idea: the release's medium composed to keep the disk's data (`korp.reinstall=keep`) and to
/// power off after INSTALL-OK (`korp.install.then=poweroff`), so the shop starts it from its disk.
pub const KEEP_MEDIUM: &str = "holger-keep";
pub const KEEP_VOLUME_ID: &str = "HOLGER_KEEP";
pub const FRONT_KEEP_MEDIUM: &str = "holger-front-keep";
pub const FRONT_KEEP_VOLUME_ID: &str = "HOLGER_FRONT_KEEP";
/// What a keep medium boots with, beside its medium's own boot parameters.
pub const KEEP_BOOT: [&str; 2] = ["korp.reinstall=keep", "korp.install.then=poweroff"];
fn kept(mut m: InstallerBuild, name: &str, volume_id: &str) -> InstallerBuild {
m.name = name.to_string();
m.volume_id = volume_id.to_string();
m.boot_params.extend(KEEP_BOOT.map(str::to_string));
m
}
/// **holger-keep** — [`metered_installer`] upgrading main or twin in place.
#[must_use]
pub fn keep_installer() -> InstallerBuild {
kept(metered_installer(), KEEP_MEDIUM, KEEP_VOLUME_ID)
}
/// **holger-front-keep** — [`front_installer_metered`] upgrading the front in place.
#[must_use]
pub fn front_keep_installer() -> InstallerBuild {
kept(
front_installer_metered(),
FRONT_KEEP_MEDIUM,
FRONT_KEEP_VOLUME_ID,
)
}
/// The staging tree the live medium's contents are grafted from — the directory
/// [`appliance_iso`] declares, and where `build_iso_as` writes the applied
/// `staged_file` parameters before the masterer runs. Relative to the repo root.
pub const APPLIANCE_ISO_STAGING: &str = ".nornir/assets/appliance-root";
/// The initramfs path the live medium's server declaration is read from, and the
/// path [`live_argv`] names on the argv. See [`appliance_iso`] for why it is NOT
/// under `/data`.
pub const LIVE_CONFIG_PATH: &str = "/holger.ron";
/// "How the product starts" on the LIVE medium — the RAM twin of [`start_argv`].
///
/// One difference and it is deliberate: the config is read from `/holger.ron`,
/// the initramfs root, not `/data/holger.ron`. See [`appliance_iso`].
fn live_argv() -> Vec<String> {
vec![
"start".to_string(),
"--config".to_string(),
LIVE_CONFIG_PATH.to_string(),
]
}
/// **holger-appliance-iso** — the LIVE appliance medium: holger-mvp as PID 1,
/// off a cdrom, touching no disk.
///
/// # What it is, beside `holger-installer`
///
/// Two media, two jobs, ONE payload. `holger-installer` boots the unattended
/// `korp-installer` PID 1, lays a GPT + ESP and streams the payload onto a disk;
/// this row IS the appliance — the same static `mvp-server-musl` binary, packed
/// into the same `app.znippy`, verified by the same blake3 root and exec'd by the
/// same `korp-start`, with nothing written anywhere. gunnar carries exactly this
/// pair (`gunnar-appliance-iso` beside its eight `gunnar-installer-*`) and korp
/// carries it too (`korp-appliance-iso` beside five installers), for the reason
/// this row exists: the live medium is how the payload is proved to boot METAL
/// before a disk is committed to it. On a machine declared `shell = false` there
/// is no second way to find out.
///
/// The payload is [`PAYLOAD_VARIANT`] and not a cargo line written out here a
/// second time: `from_variant` is the chain, and the door
/// (`cargo xtask appliance-iso`) resolves it by building THAT variant through the
/// declaration. A medium whose binary is spelled anywhere but the build space is a
/// medium that can disagree with the build space.
///
/// # ★ ONE MEDIUM SERVES BOTH MACHINES — server AND twin
///
/// gunnar's hard-won rule is THE TWIN MUST BE THE SAME BUILD AS THE PRIMARY: its
/// metered appliance had an un-metered twin, and promoted it would have stopped
/// enforcing entitlements in silence. gunnar needs a SECOND row (`gunnar-twin`) to
/// obey it, because its media bake a role into the installed cmdline
/// (`TWIN_KORP_ARGS` = `korp.args=serve,…,--role,twin`), so "same build" has to be
/// asserted across two declarations that differ on purpose.
///
/// holger bakes no role. `holger-server` has NO `--role` and no twin flag: the
/// twin runs the same binary with the same argv and the same `holger.ron`, and
/// replication is the TWIN following the primary's replication door (`:50052`,
/// mutual TLS under the primary's own issuer, `server/lib/src/twin_follow.rs`) —
/// on an estate over the WireGuard overlay only (`server/lib/src/tunnel_addr.rs`:
/// every estate door binds on wg0). private-holger-ops' `estate.rs` enforces
/// the same thing from the other end — `[estate-twin-drift]` REFUSES a spec whose
/// twin differs in services, doors, data layout or mount, and a test asserts the
/// two emitted directories are byte-identical.
///
/// So the twin gets NO row of its own, and that is the strong answer rather than
/// the lazy one: with one row there is no second artifact that CAN drift. What
/// differs between the two machines is the BOX — one core against two, `hdd`
/// against `maxiops`, a 25 GB system disk against 40 — and a box is terraform's
/// fact, not a medium's. A second row here would be a second name over one
/// artifact, which is precisely what korp's `installer_matrix.rs` refuses for the
/// five media that DO differ.
///
/// # The config is `/holger.ron`, not `/data/holger.ron`
///
/// The installer row stages `holger.ron` onto P3 and the installed appliance reads
/// it at `/data/holger.ron` ([`start_argv`]). A live medium has no P3, and korp
/// REFUSES to seed `data/` on a release medium at all
/// (`korp_installer::forge::compose_appliance_iso`, gated behind
/// `APPLIANCE_SEED_FIXTURE_ENV`): a live appliance that shipped with a control
/// store in it would be a store nobody could tell from a real one. A per-site
/// server DECLARATION is not a control store — it is read-only, it is the same
/// file the installer stages, and it dies with the boot — so it rides the
/// initramfs ROOT as `/holger.ron` and the argv says so. Required, for the reason
/// the installer's copy is required: what this appliance serves is a site claim no
/// default may invent.
///
/// # No `hub_conf`, deliberately
///
/// The installer stages one because the identity it mints outlives the install.
/// A live medium's WireGuard identity is RAM and dies at poweroff, so enrolling it
/// with a hub would leave a peer entry for a machine that no longer exists and
/// that nobody knows to remove. gunnar's live medium takes the same decision (and
/// its 2026-08-27 measurement is the reason: the row before it shipped
/// `--no-grpc --admin-key @/data/admin.pub` against a key file that was never
/// staged, and what shipped was a brick).
///
/// # `kernel_donor` = `tunnr/tunnr-kernel`
///
/// Until 2026-09-27 this row declared NO donor, deliberately: holger had not
/// chosen a kernel and the operator handed one in. The owner chose it that day —
/// "tunnr and 3 images just like gunnar" — so this medium boots tunnr's declared
/// kernel, read by the forge off the donor's pin, never off the build box's `/boot`.
/// It is the shared row ([`live_kernel_donor`]), not the installers' sandbox row:
/// this medium asks for the clock only, and the sandbox row refuses one-user
/// consumers by name.
pub fn appliance_iso() -> IsoBuild {
IsoBuild {
name: APPLIANCE_ISO.to_string(),
// Empty: the medium's kernel and initramfs are COMPOSED by
// `korp_installer::forge::ApplianceIsoBuilder` into the ESP it writes, not
// named as pre-existing files on the volume. Spelled rather than defaulted,
// because a path here would master a volume whose El Torito entry names a
// file this build did not produce.
kernel: String::new(),
rootfs: String::new(),
staging: APPLIANCE_ISO_STAGING.to_string(),
volume_id: "HOLGER_APPLIANCE".to_string(),
format: IsoFormat::Uefi,
boot_params: live_boot_params(),
from_variant: Some(PAYLOAD_VARIANT.to_string()),
// THE MARK ON THE BOOT SCREEN — the same one the installer row carries.
// Without it this medium boots wearing korp's raven (measured 2026-08-16:
// gunnar's, korp's and holger's boot framebuffers were byte-identical).
boot_logo: Some(".nornir/assets/holger-znippy-logo.png".to_string()),
params: vec![
// Visible provenance on the medium's own cmdline: which site this ISO
// was mastered for, readable off a serial console without booting the
// app.
ParamSpec {
name: "site".to_string(),
label: "Site name".to_string(),
kind: ParamKind::Text,
required: false,
boot_param: "korp.site".to_string(),
..ParamSpec::default()
},
// REQUIRED — see the doc above. Lands in the staging tree as
// `holger.ron` and is grafted at the initramfs root as
// `LIVE_CONFIG_PATH`.
ParamSpec {
name: "holger_ron".to_string(),
label: "Server declaration (holger.ron)".to_string(),
kind: ParamKind::Text,
required: true,
staged_file: "holger.ron".to_string(),
..ParamSpec::default()
},
],
// Nothing to mint: the one fixture holger declares is the WireGuard hub,
// and this medium stages no hub.conf (see the doc above).
fixtures: BTreeMap::new(),
applied_params: Vec::new(),
options: Vec::new(),
applied_options: Vec::new(),
// Named, not defaulted — every field of this mirror gets a decision here,
// so a field added upstream cannot slide in silently. tunnr's shared kernel
// (see the doc above).
kernel_donor: Some(live_kernel_donor()),
edges: BTreeMap::new(),
// The live medium runs the same holger on the same boot clock: kvmclock,
// as on [`installer`] (edda `kernel_requires::clock_requires`).
requires: clock_requires(),
presets: BTreeMap::new(),
applied_preset: None,
}
}
/// The live medium's kernel command line.
///
/// ORDER IS LOAD-BEARING (gunnar, measured 2026-08-12): `tty0` FIRST so a KVM
/// graphics console gets the printk stream, `ttyS0` LAST so it stays
/// `/dev/console` — the serial line a boot proof greps korp-start and holger off.
///
/// `korp.mode=live` is what says this medium never touches a disk, and it is the
/// one token that distinguishes this cmdline from the installer's. It is ALSO the
/// medium's answer to the estate's re-image warning: a live boot writes nothing,
/// so it cannot keep a data volume across a re-mint and cannot lay an ESP on the
/// wrong disk. Neither failure has a path through this row — see [`installer`]'s
/// `data_disks` and `reinstall` parameters, which are where they are answered for
/// the medium that DOES write.
fn live_boot_params() -> Vec<String> {
let mut p: Vec<String> = [
"console=tty0",
"console=ttyS0,115200",
"panic=10",
// The runtime ceremony gate, as a bare cmdline assignment — the live twin
// of the container row's `env` and the installer's `korp.env.*`.
"HOLGER_APPLIANCE=1",
"korp.entry=holger",
"korp.znippy=/app.znippy",
// No container runtime on this medium: holger IS PID 1.
"korp.appliance=lean",
// RAM, and nothing outlives the boot.
"korp.mode=live",
"--",
]
.map(str::to_string)
.to_vec();
p.extend(live_argv());
p
}
// ══ vm1: holger's FRONT medium ═══════════════════════════════════════════════
/// The FRONT medium's name (and its pointer variant's name).
pub const FRONT_INSTALLER_METERED: &str = "holger-front-installer-metered";
/// Its volume id — its OWN: it carries another program (`holger-estate-front`),
/// and a medium is resolved to its row by volume id.
pub const FRONT_VOLUME_ID: &str = "HOLGER_FRONT_INSTALL_METERED";
/// The cargo variant the front medium lays down: the static musl
/// `holger-estate-front` with `metered` (see `variants.rs`).
pub const FRONT_PAYLOAD_VARIANT: &str = "estate-front-musl-metered";
/// The front's crate is its OWN workspace (it links monetize and holger-ui,
/// which the open holger must not resolve): the row builds it by manifest dir.
pub const FRONT_MANIFEST_DIR: &str = "holger-estate-front";
/// The program korp-start execs out of the medium's archive.
pub const FRONT_ENTRY: &str = "holger-estate-front";
/// What the front's PID 1 reads from the installed cmdline (`korp.env.*`): its
/// data root (where the medium stages the pairing seed and it keeps its keys,
/// its certificate, monetize). Its two NICs need no token: with no `HOLGER_NET`
/// the program brings up `eth0:dhcp,eth1:dhcp` (a comma cannot ride a
/// whitespace-split cmdline token) — the public one (the web face,
/// the relay, the plugin door) and the utility one (the boxes' control planes).
fn front_env() -> BTreeMap<String, String> {
BTreeMap::from([("HOLGER_APPLIANCE_DATA".to_string(), "/data".to_string())])
}
/// **holger-front-installer-metered** — vm1's medium in holger's paid estate:
/// holger's OWN front program as PID 1 (`holger-estate-front`, feature
/// `metered`: the bare monetize, the plugin door, holger's console, the web
/// face, the relay, the twin's certification). gunnar's estate has the same
/// SHAPE (a front medium beside the product medium); nothing of gunnar's is in
/// it (owner 2026-09-27).
///
/// Built like [`metered_installer`] — [`installer`] cloned, the same composer,
/// the same declared kernel donor `tunnr/tunnr-kernel-sandbox`, the same per-site
/// parameters that mean something on a front: `site`, `data_disks`,
/// `pairing_seed` (a seed of kind `front`), `reinstall`, `install_then` — and its own
/// `clock` ([`front_clock_param`]), the front's time sources. Left
/// out, by name: `holger_ron`, `plugin_trust_root`, `intel_trust_policy` and `bundles_trust_policy` (the front serves no repository — its
/// configuration is the installer's feed) and the old tunnel ceremony's
/// `hub_conf`/`underlay` (the front IS the estate's relay, keyed by its own
/// presented tunnel key).
#[must_use]
pub fn front_installer_metered() -> InstallerBuild {
let mut m = installer();
m.name = FRONT_INSTALLER_METERED.to_string();
m.variant = FRONT_PAYLOAD_VARIANT.to_string();
m.volume_id = FRONT_VOLUME_ID.to_string();
m.boot_params = [
"console=ttyS0",
"korp.appliance=lean",
&format!("korp.entry={FRONT_ENTRY}"),
]
.map(str::to_string)
.to_vec();
m.cmd = Vec::new();
m.env = front_env();
// The front keeps `data.fs = xfs` although it has no data volume (its /data is
// the system disk's P3): every holger medium boots ONE donor kernel, and the
// donor's pin is per requirement fragment — a front asking less than the boxes
// was refused "the built kernel is not the CHOSEN kernel" (mock, 2026-09-28).
m.params.retain(|p| {
![
"holger_ron",
"plugin_trust_root",
"intel_trust_policy",
"bundles_trust_policy",
"hub_conf",
"underlay",
]
.contains(&p.name.as_str())
});
m.params.push(front_clock_param());
m.fixtures.clear();
m.options.push(dev_narrate_option());
// The front scans nothing: no scan sandbox on its medium (lane HOLGER-MEDIA). Its
// kernel keeps the container rows all the same — one donor kernel, see above.
m.payload_entries.clear();
m
}
/// **The front's own time sources** — `clock`, on the FRONT medium only (edda
/// `estate-clock`, 2026-09-28). The front is the estate's root of time: it keeps
/// its own clock from these sources and serves signed time to the boxes on
/// `<utility>:50123`, which every box takes from the front's offer
/// (`clock_peer` + `clock_key`), so the box medium declares no clock parameter.
/// A site fact, so a parameter and never a default: `https://…` (comma-separated;
/// the mock's LOCAL clock is one `https://` vouched for by the medium's installer
/// CA) or `ntp://…` in an airgap. Absent: the front syncs nothing and REFUSES to
/// sign, and each box reports that sentence instead of stepping onto an unchecked
/// clock. Parsed and refused by name in `estate_clock::config`.
#[must_use]
pub fn front_clock_param() -> ParamSpec {
ParamSpec {
name: "clock".to_string(),
label: "The front's own clock sources (https://… or ntp://…, comma-separated): the estate's root of time".to_string(),
kind: ParamKind::Text,
required: false,
boot_param: "korp.env.ESTATE_CLOCK".to_string(),
..ParamSpec::default()
}
}
// ══ a DOWNSTREAM's medium (lane OX-WGDS3) ═════════════════════════════════════
/// The DOWNSTREAM medium's name (and its pointer variant's name): an on-prem
/// `holger-downstream` laid as its own korp appliance, the box a site enrols at its
/// upstream estate `via: Front` or `via: Wireguard` (lane HOL-WGDS).
pub const DOWNSTREAM_INSTALLER: &str = "holger-downstream-installer";
/// Its volume id — its OWN: it carries another binary (`holger-downstream`).
pub const DOWNSTREAM_VOLUME_ID: &str = "HOLGER_DOWNSTREAM_INSTALL";
/// The cargo variant the (fetching) downstream medium lays down (see `variants.rs`).
pub const DOWNSTREAM_PAYLOAD_VARIANT: &str = "downstream-fetch-musl";
/// **The ONE-WAY downstream medium** (lane HOL-CDS-5): `holger-downstream` + `pid1`, its own
/// init without pairing; packages only through its inbox (a HOLGERINBOX stick).
pub const ONEWAY_INSTALLER: &str = "holger-downstream-oneway-installer";
/// Its volume id — its own.
pub const ONEWAY_VOLUME_ID: &str = "HOLGER_DOWNSTREAM_ONEWAY";
/// The cargo variant it lays down (see `variants.rs`).
pub const ONEWAY_PAYLOAD_VARIANT: &str = "downstream-musl";
/// The medium's network (the admin's, `holger_net`), staged on the release.
pub const ONEWAY_NET_STAGED: &str = "holger-net";
/// The bundle trust policy the inbox verifies with, staged on the release.
pub const ONEWAY_TRUST_POLICY_STAGED: &str = "trust-policy.ron";
/// The upstream's attestation root (PEM), staged on the release.
pub const ONEWAY_ROOT_STAGED: &str = "upstream-root.pem";
/// The upstream's evidence signer (PEM), staged on the release.
pub const ONEWAY_SIGNER_STAGED: &str = "upstream-signer.pem";
/// The program korp-start execs out of the one-way medium's archive.
pub const DOWNSTREAM_ENTRY: &str = "holger-downstream";
/// The program korp-start execs out of the fetching medium's archive (the crate's
/// `holger-downstream-fetch` bin, `fetch` built in: its own file).
pub const FETCH_ENTRY: &str = "holger-downstream-fetch";
/// The NIC a downstream brings up: ONE, by DHCP (an on-prem box has no utility net).
pub const DOWNSTREAM_NET: &str = "eth0:dhcp";
/// The CA a `via: Front` downstream verifies its upstream's front with
/// (`MirrorCredential::Tls`), staged on the release beside its declaration.
pub const DOWNSTREAM_CA_STAGED: &str = "downstream-ca.pem";
/// The downstream's env: [`installer_env`] (the appliance gate, `HOLGER_DATA`) with ONE NIC
/// ([`DOWNSTREAM_NET`]) instead of the cloud's two, and no console button (it has no front).
fn downstream_env() -> BTreeMap<String, String> {
let mut e = installer_env();
e.remove("HOLGER_CONSOLE_PATH");
e.insert("HOLGER_NET".to_string(), DOWNSTREAM_NET.to_string());
e
}
/// **holger-downstream-installer** — a downstream Holger as its own appliance medium:
/// [`installer`] cloned (the same composer, the same donor kernel and its rows — every holger
/// medium boots ONE donor kernel), laying `holger-downstream` (`downstream` + `downstream-wg`,
/// [`DOWNSTREAM_PAYLOAD_VARIANT`]) with its declaration (`holger_ron`, rendered by
/// [`crate::downstream_ron`]) and, for a `via: Front` downstream, the CA of its upstream's
/// front (`downstream_ca`). Left out, by name: the estate's pairing seed, the old tunnel
/// ceremony's `hub_conf`/`underlay`, and the scan sandbox (a downstream scans nothing: it
/// trusts its upstream's signed scan evidence).
#[must_use]
pub fn downstream_installer() -> InstallerBuild {
let mut m = installer();
m.name = DOWNSTREAM_INSTALLER.to_string();
m.variant = DOWNSTREAM_PAYLOAD_VARIANT.to_string();
m.volume_id = DOWNSTREAM_VOLUME_ID.to_string();
m.boot_params = [
"console=ttyS0",
"korp.appliance=lean",
&format!("korp.entry={FETCH_ENTRY}"),
]
.map(str::to_string)
.to_vec();
m.env = downstream_env();
m.params
.retain(|p| !["hub_conf", "underlay", "pairing_seed"].contains(&p.name.as_str()));
m.params.push(ParamSpec {
name: "downstream_ca".to_string(),
label: "The upstream front's CA (PEM) a via: Front downstream verifies it with".to_string(),
kind: ParamKind::Text,
required: false,
staged_file: DOWNSTREAM_CA_STAGED.to_string(),
lands: Landing::Release,
..ParamSpec::default()
});
m.fixtures.clear();
// A downstream scans nothing, yet verifies every bundle in a job (lane HOL-SANDBOX-TRAIT):
// its medium carries the sandbox (crun, the worker), laid at boot (lane HOL-CDS-5). The
// fetching downstream also carries its fetch worker (lane cds4); the one-way one does not.
m.payload_entries = fetch_payload_entries();
m
}
/// **holger-downstream-oneway-installer** (lane HOL-CDS-5, owner 2026-10-30): the ONE-WAY
/// downstream as its own appliance medium. [`downstream_installer`]'s shape (one NIC, no
/// sandbox, no pairing seed), laying [`ONEWAY_PAYLOAD_VARIANT`] (`holger-downstream` +
/// `pid1`: its own init, no pairing, it dials nothing). Everything it trusts rides THIS medium,
/// provisioned by the admin who composes it, on the release (never the data disk a stick
/// reaches): the declaration (`holger_ron`, rendered by [`crate::downstream_ron::render_one_way`]),
/// the network (`holger_net`, static or DHCP; default `HOLGER_NET` = [`DOWNSTREAM_NET`]), the
/// bundle trust policy, and the upstream's attestation root and signer (PEM).
#[must_use]
pub fn downstream_oneway_installer() -> InstallerBuild {
let mut m = downstream_installer();
// The scan sandbox only: a one-way appliance dials nothing, so it never ships the fetch
// worker (lane cds4).
m.payload_entries = sandbox_payload_entries();
m.name = ONEWAY_INSTALLER.to_string();
m.variant = ONEWAY_PAYLOAD_VARIANT.to_string();
m.volume_id = ONEWAY_VOLUME_ID.to_string();
m.boot_params = [
"console=ttyS0",
"korp.appliance=lean",
&format!("korp.entry={DOWNSTREAM_ENTRY}"),
]
.map(str::to_string)
.to_vec();
// No WireGuard identity ceremony (`HOLGER_APPLIANCE=1` asks for one): a one-way box builds
// without `appliance` and dials nothing.
m.env.remove("HOLGER_APPLIANCE");
m.params.retain(|p| p.name != "downstream_ca");
for (name, label, required, file) in [
(
"holger_net",
"The box's network: `eth0:dhcp` or `eth0:<addr>/<prefix>:<gateway>` (static)",
false,
ONEWAY_NET_STAGED,
),
(
"trust_policy",
"The bundle trust policy (RON) the inbox verifies every bundle with",
true,
ONEWAY_TRUST_POLICY_STAGED,
),
(
"upstream_root",
"The upstream's attestation root (PEM)",
true,
ONEWAY_ROOT_STAGED,
),
(
"upstream_signer",
"The upstream's evidence signer certificate (PEM)",
true,
ONEWAY_SIGNER_STAGED,
),
] {
m.params.push(ParamSpec {
name: name.to_string(),
label: label.to_string(),
kind: ParamKind::Text,
required,
staged_file: file.to_string(),
lands: Landing::Release,
..ParamSpec::default()
});
}
m
}
#[cfg(test)]
mod tests {
use super::*;
/// The scan sandbox rides the main, twin and downstream media's app archive (a downstream
/// verifies its bundles in jobs), never the front's; every medium asks the kernel for what
/// it needs.
#[test]
fn the_sandbox_entries_ride_every_box_medium_and_the_kernel_rows_every_medium() {
let want = sandbox_payload_entries();
// 6 + Heimdall's shim and the 10 files of its released guests (HOL-HEIMDALL-03).
assert_eq!(want.len(), 17);
assert_eq!(want["sandbox/crun"], ".sandbox-out/crun");
// HOL-OCI-EVIDENCE: the release's signed Sigstore trust root and the policy that pins
// its signer ride beside the sandbox; the box seeds them into <data_root>/holger-trust.
assert_eq!(
want["sandbox/sigstore-trust.holger.znippy"],
".sandbox-out/sigstore-trust.holger.znippy"
);
assert_eq!(
want["sandbox/bundle-trust.ron"],
".sandbox-out/bundle-trust.ron"
);
assert_eq!(SIGSTORE_TRUSTED_ROOT_SHA256.len(), 64);
for row in [
installer(),
metered_installer(),
keep_installer(),
downstream_oneway_installer(),
] {
assert_eq!(row.payload_entries, want, "{}", row.name);
row.check_payload_entries().unwrap();
}
// The fetching downstream: the sandbox and its fetch worker (lane cds4).
let fetching = downstream_installer();
assert_eq!(fetching.payload_entries, fetch_payload_entries());
assert!(want
.iter()
.all(|(k, v)| fetching.payload_entries.get(k) == Some(v)));
fetching.check_payload_entries().unwrap();
for row in [front_installer_metered(), front_keep_installer()] {
assert!(
row.payload_entries.is_empty(),
"{} carries no sandbox",
row.name
);
}
let symbols = |row: &InstallerBuild| -> Vec<String> {
row.requires
.iter()
.filter_map(|r| match &r.what {
nornir_build_thing::requires::Requirement::Kconfig(k) => Some(k.symbol.clone()),
_ => None,
})
.collect()
};
for row in [
installer(),
metered_installer(),
keep_installer(),
front_installer_metered(),
front_keep_installer(),
downstream_installer(),
downstream_oneway_installer(),
] {
let s = symbols(&row);
for sym in [
"CONFIG_MULTIUSER",
"CONFIG_USER_NS",
"CONFIG_CGROUPS",
"CONFIG_MEMCG",
"CONFIG_CGROUP_PIDS",
"CONFIG_CFS_BANDWIDTH",
"CONFIG_SECCOMP_FILTER",
"CONFIG_NET_NS",
] {
assert!(s.contains(&sym.to_string()), "{} asks for {sym}", row.name);
}
}
}
}