build thing — target facts (firmware · consoles · buses · nic) — design
Class:
design· Function axis:f0thetargetsetting group ·f1where it is declared ·f2who consumes it and the typed consequence per typ ·f3the proof door Strict: states only what thetargetgroup IS. Refines build-thing-code-is-source-design.md (typed consequence, f2) and build-thing-model-design.md (settings tree, f1). Written 2026-08-29 after the UpCloud lanes: three platform facts were declared NOWHERE and each cost a blind lane against a SeaBIOS-only, UART-less, IDE-cdrom host.
An appliance medium is built FOR a machine shape, and that shape is a setting group on the installer variant, not folklore in a shell script. The facts are: which firmware starts the medium, which consoles exist, which buses carry the medium and the target disk, and which nic model the guest sees. Every one is a typed input to a build, and every one has a typed consequence.
The group (f0) — target.* on [[build.installer]]
| path | values | default | why the default |
target.firmware |
uefi · seabios · both |
both |
today's medium boots both (edda 39cb702); nothing built today changes |
target.consoles |
list of serial · vga (order = console= order, LAST is /dev/console) |
[vga, serial] |
today's cmdline console=tty0 console=ttyS0,115200 |
target.buses |
set of ide-cdrom · virtio-blk · virtio-scsi |
{ide-cdrom, virtio-blk} |
UpCloud attaches the ISO as IDE cdrom; the nuked kernel vetoes ATA, so the medium must ALSO ride virtio-blk |
target.nic |
virtio · e1000 |
virtio |
the only NIC an appliance initramfs can drive today (VIRTIO_NET=y) |
Where it is declared (f1)
One place: edda nornir-build-thing, installer.rs → InstallerBuild.target: Target (target.rs). #[serde(default)] and skipped when default, so a published 0.1.x reader loads an old recipe unchanged and an emitted toml only carries the row when something was chosen. Each repo's impl BuildThing sets it in Rust; the toml row [build.installer.target] firmware = "both" is the EMITTED description, never read back as authority. gunnar's mirror crate pins the published 0.1.10 and cannot spell the field until the pin advances — the same gap options/fixtures already live in — so until then gunnar's committed toml carries the row explicitly and recipe.rs names the gap.
Who consumes it, and the typed consequence (f2)
| consumer | toggle | consequence (typed) |
installer recipe (edda korp-installer::isocompose, the ONE composer) |
firmware=both |
El-Torito: EFI section (efiboot.img) and the real BIOS default entry (korp-boot-bios stage, patch table → vmlinuz + bios/initrd by LBA). Target disk gets MBR code + korpbios.bin on its ESP |
firmware=uefi |
EFI section only; the BIOS default entry is the refusal stub (BIOSREF.BIN: "This medium boots UEFI only …") — SeaBIOS prints one sentence and halts instead of running FAT bytes. No BIOS stage staged, no MBR code laid |
|
firmware=seabios |
BIOS stage laid; the ESP is still composed (the target disk's korpbios.bin lives there) but the medium's EFI El-Torito section is dropped — IsoFormat::Bios |
|
consoles |
the console= tokens on the installer AND installed cmdline, in declared order; a list without serial puts tty0 last (= /dev/console), without vga puts ttyS0 alone. Empty list is refused by name (a boot nobody can see) |
|
buses, nic |
the KVM install proof's attachment argv (kvm_disk_args + medium attachment): ide-cdrom → -cdrom; virtio-blk → the medium ALSO as a read-only virtio drive, target disk FIRST (vda); nic → the -device model |
|
tunnr kernel recipe (Kconfig closure, when tunnr gets its impl) |
consoles ∋ serial |
SERIAL_8250 SERIAL_8250_CONSOLE in; absent → out |
consoles ∋ vga |
VT VT_CONSOLE VGA_CONSOLE PRINTK in (VT selects INPUT); absent → out. kernel::PID1_BOOT_CONTRACT still refuses a .config with NEITHER console |
|
buses |
ide-cdrom → ATA ATA_PIIX BLK_DEV_SR ISO9660_FS; virtio-blk → VIRTIO_BLK VIRTIO_PCI VIRTIO_PCI_LEGACY; virtio-scsi → SCSI_VIRTIO SCSI BLK_DEV_SD |
|
nic |
virtio → VIRTIO_NET; e1000 → E1000 |
|
| installtest (gunnar, the proof door) | --firmware uefi|seabios |
the qemu MACHINE: q35 + OVMF pflash pair vs pc (SeaBIOS, no pflash). --serial none = no UART at all (UpCloud). Attachments follow buses: target virtio disk first, ISO as IDE cdrom AND virtio raw, nic count 2 (UpCloud: public + utility) |
The consequence is measured, not asserted: firmware → which El-Torito entries the writer laid (IsoLayout.describe()); consoles → the cmdline read back off the installed ESP (korp/cmdline); kernel → the bzImage blake3 delta per group.
The proof door (f3) — gunnar-installtest installer --firmware seabios [--serial none] --iso PATH
B1 install (-boot d, -no-reboot ⇒ qemu exits at the installer's reboot; gate = INSTALL-OK on serial OR, with --serial none, the protective MBR + korp/cmdline read back off the target) → B2 boot the installed disk alone, probe SSH-2.0-gunnar on hostfwd :2222 and a TCP accept on :50051 → B3 SIGKILL (power cut), boot again, same probe. Timings per leg are the arm's stages. The boot doors are korp_installer::kvmproof (Machine = firmware + serial + memory → argv), the SAME writer the UEFI installer proof uses — one harness, two firmware shapes. The hand-rolled seabios-proof.sh this replaced is the measurement this door reproduces.
What is left, named
- gunnar's recipe mirror spells
targetonly after thenornir-build-thingpin passes 0.1.10 (publish is Rickard's order). korp-installer::installlaying MBR code +korpbios.binon the target disk is unconditional today;target.firmwarereaches PID 1 askorp.target.firmware=and the laying follows it — akorp-installerchange, held back while another lane ownsinstall.rs.- tunnr's
impl BuildThingdoes not exist yet (the tillskott); the Kconfig rows above are the contract it implements.