Status: done (code-complete; real-cloud boot verification is the CL6 live run — see Known gaps)
Closes the OS half of the cloud#6 / CL6 convergence: #242. Surfaced by the first real-cloud boot of the production C2 image — a tenant box provisioned from MALMO_HETZNER_IMAGE (built by make build-cloud-image) booted on Hetzner completely unreachable: /etc/systemd/network/ empty → the NIC never got an IP, and none of the malmo runtime wiring was present. The cause: all the first-boot wiring lived only in the test lane (dev/cloud/test/, staged at boot-proof time), while the production lean config (dev/cloud/) was built, by its own comment, "with NO postinst/control-plane images." C1b (#203) only ever asserted leanness; C2 (#205) booted the test image; the production image that provisioning actually uploads had never been booted. This slice promotes the wiring into the production image so make build-cloud-image emits a self-bootstrapping tenant box.
Builds on C1a (#202, the profile marker), C1b (#203/#218, the lean image), C1c (#204, the slim host-agent), C2 (#205, the boot proof), C3a (#206/#220, the seed + /setup gate), C3b (#207, the wildcard cert), and the recommends/nftables lean-check fixes (#237/#241/#243).
Tooling-only (dev/cloud/, Makefile, .gitignore); no Go, no brain/UI change. A pure relocation: the boot-proof test image already booted + served (#205/C2, #209/C5); its wiring just lived in the wrong directory. The refactor moves the generic wiring down into the production config and leaves only the serial self-check in the test lane, so the resulting test image is byte-for-byte the same image it was before — what changes is that make build-cloud-image now produces that same wiring (minus the self-check).
- New
dev/cloud/stage-control-plane.sh— one sourced helper,stage_control_plane(), that builds + stages the runtime wiring intodev/cloud/mkosi.extra.wiring/(generated, gitignored): the slim host-agent (-tags hosted) + its unit + the cloud-brain drop-in, the PAM stack, the control-plane image bundle (brain/ui/proxy + the xcaddycaddy-dns/acmednsCaddy) + the first-boot loader, the control-plane compose +caddy.json, and the provisioning-seed materializer + its unit. Both lanes source it, so the wiring cannot drift between the production image and the image the test lane validates. Logic moved verbatim from the old test bootstrap. - New
dev/cloud/mkosi.postinst.chroot— the production first-boot enablement (moved from the test postinst, minus the self-check): enable docker/containerd/systemd-networkd/host-agent/malmo-load-images/malmo-seed, disablenetworkd-wait-online, write the05-docker-unmanaged+20-dhcp.networkfiles, provision themalmo/malmo-app/malmo-sharedidentities, and pre-commit machine-id + masksystemd-firstboot. dev/cloud/mkosi.conf— addsExtraTrees=mkosi.extra.wiring(the generated tree) alongside the committedmkosi.extra(the/etc/malmo/profile=hostedmarker). Header/scope comments updated (it no longer "deliberately does NOT bake host-agent/brain bring-up").dev/cloud/bootstrap.sh— now stages the wiring (via the shared helper) + the Docker apt repo beforemkosi build, then runs the unchanged lean check + marker source-sanity. Needs root (control-plane image build + mkosi disk ops) and resolves the caller for the unprivileged go/mkosi sub-builds — same machinery the test lane already used.dev/cloud/test/bootstrap.sh— slimmed: calls the shared staging, then stages only the assertions (cloud-assertions.sh+ its unit) intodev/cloud/test/mkosi.extra/.CANARY_VERSIONv15→v16 (structure changed → force a clean rebuild).dev/cloud/test/mkosi.postinst.chroot— slimmed to enabling themalmo-cloud-assertionsoneshot only. The production wiring postinst runs for this lane too:dev/cloud/test/mkosi.confdoesInclude=.., and mkosichdirs into the included directory and re-runs its auto-detection (mkosi/config.pyparse_config_one, the Include branch), sodev/cloud/mkosi.postinst.chroot+dev/cloud/mkosi.extra.wiring/are picked up for the boot-proof build as well.dev/cloud/malmo-seed-materialize.sh+malmo-seed.service—git mvd fromdev/cloud/test/todev/cloud/(production owns them; the test lane inherits via the wiring tree). The materializer's header notes the SMBIOS channel is the only one wired and the real-cloud channel is the #246 follow-up.Makefile—build-cloud-imagenow runs undersudo -E(it builds control-plane images + does mkosi disk ops); help text updated..gitignore— ignore the generateddev/cloud/mkosi.extra.wiring/.
ENVIRONMENT.md# Provisioning & first-boot — new "Production image first-boot wiring (realized, #242)" bullet under # Admin bootstrap — as built records the promotion and that the image stays lean by the package manifest. # Networking & discovery's "single interface brought up by the minimal cloud-native path" is now realized (networkd DHCP, no NetworkManager).TESTING.md# Hosted cloud variant — updated to state the test lane boots the exact production image plus a serial self-check, not a test-only superset.
- No local build/boot verification. mkosi 26 on Ubuntu 24.04 hits the
PR_CAPBSET_DROPEPERM blocker (#189), somake build-cloud-image/make test-cloud-qemucannot run on the maintainer box. Verification is by source review + the test-image-identity invariant (the wiring is the same, proven-booting content, just relocated) +bash -non every script. Real green-build + Hetzner-boot acceptance is the cloud#6 / CL6 live run, the same deferral C3a (#206) and C4 (#208) took. - Real-cloud seed channel is out of scope (split to #246). The promoted
malmo-seed.servicereads the seed only from the SMBIOS / systemd-credential channel. On Hetzner the seed arrives via the metadata service, not SMBIOS, so a real box comes up with the control plane but/setupstays 503 until #246 adds the metadata channel + the first-boot network ordering it needs. The SMBIOS seed service no-ops safely without a credential, so promoting it now is harmless; #246 is the last mile and is itself a CL6-verified, cross-repo (cloud#3 wire contract) change. make build-cloud-imagenow needs root + docker + go. A deliberate contract change (the image is no longer a lean base only): the cloud repo'sdeploy/hetzner-image/build-and-upload.shmust invoke it accordingly (it self-sudos via the Makefile). Flagged for cloud-side coordination.- nftables stays off the lean cut list (the temporary #243 state, owned by #241/#245) — untouched here.
- #246 — ingest the seed from the real-cloud (Hetzner) metadata/user-data channel + resolve the first-boot network ordering; verified at CL6.
- cloud#6 / CL6 live run — boot a real seeded Hetzner VM from a
make build-cloud-imageimage: confirm network, control plane up, seed ingested, and<box-id>.malmo.networkserved over HTTPS. Record the result. - Coordinate
deploy/hetzner-image/build-and-upload.sh(cloud repo) with the heavier, root-requiringmake build-cloud-image.