- Status: done (build+boot proof deferred — see Verification)
- Date: 2026-06-19
- Specs realized (DECISIONS entry added):
ENVIRONMENT.md# How the profile is realized ("A lean cloud image profile", "A runtime marker"), # Assumption inventory (the cut list), # Networking & discovery (no NM/Avahi), # Storage (no LUKS/TPM), # Public-by-default;BUILD.md# 1 (Debian Trixie base), # 2 (mkosi, disk images) - Closes: #203 (C1b — second of the C1 split, after #202/C1a and alongside #204/C1c; part of #196). Builds on #202/C1a (the
/etc/malmo/profilemarker the brain reads) and is the image the slim cloudhost-agent(#204/C1c) gets compiled into; the boot proof is #205/C2.
The repo's first product image definition. Until now dev/test-qemu/ was the only mkosi tree, and it is a test lane (bookworm, LUKS+TPM, SSH, the full control-plane bundle baked in for an air-gapped QEMU boot). dev/cloud/ is a new, separate mkosi image — not the appliance rootfs with services disabled, but its own definition that installs only what a cloud VM needs (ENVIRONMENT.md # How the profile is realized — "absent, not disabled"). mkosi stays the single builder (BUILD.md # 2, DECISIONS.md 2026-06-16); this is a second image definition, not a second builder.
dev/cloud/mkosi.conf—Distribution=debian/Release=trixie(the locked product base,BUILD.md# 1 — not the test lane's bookworm),Format=disk,ImageId=malmo-cloud,ManifestFormat=json,ToolsTree=default(reused from the test lane so the build works on hosts with old systemd).Bootable=yes+Bootloader=systemd-boot.KernelCommandLine=console=tty0 console=ttyS0 psi=1 rw—psi=1is required on the product image (BUILD.md# 1): Debian buildsCONFIG_PSI=ybutCONFIG_PSI_DEFAULT_DISABLED=y, so without it theram-pressurehealth detector reads zeros = a false all-clear (the test lane omits it; the product image must not). A virtio+ext4+crc32c modules-initrd so the image can actually find a virtio root disk on a cloud hypervisor — with the test lane's LUKS/dm-crypt cipher modules deliberately absent.- Lean package set —
linux-image-amd64,systemd/systemd-sysv/systemd-boot, thedocker-cestack,ca-certificates,dbus,util-linux,kmod,udev,iproute2,sudo, and the base shell utils. Explicitly NOT installed (the cuts):network-manager,avahi-daemon/avahi-utils,samba,mergerfs,cryptsetup,tpm2-tools, and the appliance SSH posture (openssh-server+ nftables). - No LUKS/TPM repart.
dev/cloud/ships nomkosi.repart/— providing it at all takes over partitioning, so omitting it uses mkosi's default plain ESP + root layout (exactly the unencrypted ESP+root this profile wants; the test lane providesmkosi.repart/only because it needs a LUKS root). Nord.luks.*cmdline, no first-boot TPM-enroll units, nomalmo-storage-verify/malmo-network-verify/malmo-tpm-enrollbaking. - The marker —
dev/cloud/mkosi.extra/etc/malmo/profilecontainshosted, baked viaExtraTrees. This is exactly the fileinternal/profile.Read(#202/C1a) reads at brain startup. Unlike the test lane's generatedmkosi.extra/, this one is a single static committed file, so it needs no staging script. dev/cloud/bootstrap.sh— the thin build harness (analog of the test lane'sbootstrap.sh, minus the QEMU/swtpm/LUKS machinery): preflight (mkosi>=22,curl,python3), stage Docker's apt repo + signing key into the auto-detectedmkosi.pkgmngr/tree (trixie pocket),mkosi build, then assert the lean cut list is absent from the JSON package manifest and the marker readshosted. Runs unprivileged (mkosi self-escalates).make build-cloud-imagerunsdev/cloud/bootstrap.sh; wired into.PHONY+make help.
Firewall posture — decided, not omitted (DECISIONS.md 2026-06-19). Dropping nftables means Docker publishes container ports to 0.0.0.0 by default, which on a public-by-default cloud VM could expose app ports with nothing in front. The call: hosted v1 relies solely on the cloud provider's security groups / VPC firewall, recorded as an explicit operator requirement (provision every tenant behind a firewall admitting only 443 + the ACME/redirect 80). The appliance's nftables exists only to LAN-scope SSH/SMB — hosted ships neither, so there is no ruleset to port. A minimal in-guest nftables default-deny backstop (for provider postures lacking security groups) is deferred to NEXT.md. ENVIRONMENT.md # Public-by-default now states the requirement explicitly.
- Config validity (done on this box).
mkosi summaryunder the installed mkosi 26 parsesdev/cloud/mkosi.confcleanly and resolves the intended settings on the main image:debian/trixie,Format=disk,ManifestFormat=json,ImageId=malmo-cloud,Bootable=enabled,Bootloader=systemd-boot, thepsi=1cmdline, the committedExtraTrees,Repart Directories: none(confirming the mkosi-default plain ESP+root, no LUKS), and the lean package set (linux-image-amd64,docker-ce…). The cut packages are absent from the resolvedPackages. - Lean assertion logic (done).
bootstrap.sh's manifest check matches exact package names, so a transitivelibcryptsetup12/systemd-cryptsetuppulled by systemd is not a false positive — only the appliance packages themselves (cryptsetup,network-manager,avahi-daemon,avahi-utils,samba,mergerfs,tpm2-tools,openssh-server) fail the build. The marker check trims whitespace and compares tohosted. - Full
mkosi buildis NOT exercised on this box. It is blocked by the same mkosi-26/Ubuntu-24.04 sandbox limitation that blocks the medium QEMU lane (PR_CAPBSET_DROP EPERM, tracked as #189) — a host limitation, not a config error (config parsing, above, succeeds). The build+lean-assertion runs on any working mkosi host viamake build-cloud-image; it is the same path #205/C2 consumes to bake + boot the image.
- No boot proof here. This slice is the image definition + lean package set + marker + build target only. Emitting the qcow2, booting the VM under QEMU, bringing the control plane up, and asserting
psi=1is live + the marker is read are all #205/C2's job. C2 builds this image with the slimhost-agent(#204/C1c,go build -tags hosted). - No host-agent/brain bring-up baked. Deliberately out of scope (avoids a circular dependency on C1c and keeps the boot assertions in C2): no systemd units, no control-plane image tarballs, no PAM/malmo-group provisioning, no first-boot networking. C2/C3a add those.
- Trixie vs the test lane's bookworm. The cloud image targets Trixie (the locked product base); the medium QEMU lane is still bookworm. If Docker's trixie apt pocket or a Trixie/mkosi sharp edge blocks the build,
bootstrap.shnotes the bookworm-pocket fallback and #203's escape hatch — file a follow-up rather than expanding this slice. Aligning the test lane to Trixie once the cloud image validates it is tracked inNEXT.md(Tier 3). - Shared base with a future appliance product image (so the appliance ISO/USB image and this cloud image don't duplicate the package/repo boilerplate) is a deliberate later refactor, not this slice.
- The lean cloud image completes the C1 split alongside #202 (marker + brain read) and #204 (slim agent). C2 (#205) emits and boots the assembled image.