diff --git a/.gitea/workflows/e2e.yml b/.gitea/workflows/e2e.yml index 88a9bd9..7a54d71 100644 --- a/.gitea/workflows/e2e.yml +++ b/.gitea/workflows/e2e.yml @@ -39,7 +39,7 @@ jobs: debootstrap squashfs-tools \ systemd-ukify systemd-boot kmod bubblewrap \ debian-archive-keyring python3-pip git zstd \ - qemu-system-x86 qemu-utils \ + qemu-system-x86 qemu-utils cpio \ dnsmasq iperf3 ipxe-qemu # See release.yml for rationale — Ubuntu's apt mkosi is too old # to handle bookworm's non-free-firmware component correctly. diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index b3d23fd..3f016b3 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -35,7 +35,7 @@ jobs: sudo apt-get install -y --no-install-recommends \ debootstrap squashfs-tools dosfstools \ systemd-ukify systemd-boot kmod bubblewrap \ - debian-archive-keyring python3-pip git zstd + debian-archive-keyring python3-pip git zstd cpio # Ubuntu's apt-packaged mkosi is too old to wire # non-free-firmware shorthand through to debootstrap. # mkosi isn't published on PyPI under v24+ — install the diff --git a/live-image/Makefile b/live-image/Makefile index 6d7965d..a60fe44 100644 --- a/live-image/Makefile +++ b/live-image/Makefile @@ -12,38 +12,58 @@ REPO_ROOT := $(abspath ..) AGENT_BIN := $(REPO_ROOT)/bin/vetting-agent.linux-amd64 MKOSI_EXTRA_AGENT := mkosi.extra/usr/local/sbin/vetting-agent -.PHONY: all check-linux check-initrd agent clean +.PHONY: all check-linux check-initrd repack-initrd agent clean all: check-linux $(MKOSI_EXTRA_AGENT) mkosi --force build + $(MAKE) repack-initrd $(MAKE) check-initrd -# Fail the build if the initrd doesn't actually contain the firmware -# blobs we need. Catches two failure modes: -# 1. Packages didn't install (apt/bootstrap component misconfigured) — -# the size check trips. -# 2. Packages installed but update-initramfs didn't pack them -# (MODULES=dep regression, initramfs-tools default drift) — the -# blob presence check trips. -# Requires unmkinitramfs (from initramfs-tools on the build host). +# mkosi + initramfs-tools produce a *boot stub* initrd (~50 MB) that +# expects to mount a separate rootfs (squashfs / disk / NFS). Our PXE +# channel only delivers vmlinuz+initrd.img, so there's nothing for the +# stub to pivot to and boot wedges right after the kernel hands off. +# +# Fix: pack the entire rootfs as the initrd. Kernel unpacks the cpio.zst +# into tmpfs and runs /init from it — no pivot, no live-boot, no rootfs +# param. Size balloons to a few hundred MB but that's the real cost of +# shipping firmware + modules + userspace in a diskless boot. +repack-initrd: + @# Materialize symlinked kernel/initrd into real files so downstream + @# copies (release tarball) don't have to follow links. + @cp -fL build/vmlinuz build/vmlinuz.real && mv build/vmlinuz.real build/vmlinuz + @rm -f build/initrd.img build/initrd.img.old build/vmlinuz.old + @# Kernel execs /init from the unpacked initramfs. systemd-sysv puts + @# init at /sbin/init; add the top-level symlink the kernel looks for. + @[ -e build/init ] || ln -sf sbin/init build/init + @# Skip /boot: holds the old kernel + stub initrd, both superseded. + @# Write to a sibling temp path so find doesn't race with the + @# archive being written into the dir it's walking. + @cd build && \ + find . -path ./boot -prune -o -print0 \ + | cpio --null --create --format=newc --quiet \ + | zstd -T0 -10 -q > ../build.initrd.img.tmp + @mv build.initrd.img.tmp build/initrd.img + +# Fail the build if the repacked initrd doesn't actually contain the +# firmware we need. Two failure modes: +# 1. Packages didn't install (apt/bootstrap misconfigured) → size check. +# 2. Packages installed but repack-initrd missed /lib/firmware → blob +# presence check. +# cpio -t lists the archive; zstd -dc streams it without extracting. check-initrd: - @# build/initrd.img is a symlink into build/boot/; use wc -c (which - @# follows symlinks) to get the real byte count. `stat -c%s` without - @# -L returns the symlink's path length instead of the target size. + @# With repack-initrd the initrd is the full rootfs (cpio.zst), so + @# 200 MB is the floor — anything smaller means firmware/userspace + @# is missing or the repack step silently produced a stub. @size=$$(wc -c < build/initrd.img); \ - min=$$((150 * 1024 * 1024)); \ + min=$$((200 * 1024 * 1024)); \ if [ "$$size" -lt "$$min" ]; then \ - echo "ERROR: initrd.img is $$size bytes (< $$min) — firmware almost certainly missing."; \ - echo " Check mkosi build log for missing packages or apt failures."; \ + echo "ERROR: initrd.img is $$size bytes (< $$min) — firmware/rootfs almost certainly missing."; \ + echo " Check mkosi build log and repack-initrd output."; \ exit 1; \ fi - @tmp=$$(mktemp -d); \ - trap 'rm -rf "$$tmp"' EXIT; \ - unmkinitramfs build/initrd.img "$$tmp" >/dev/null 2>&1 || { \ - echo "ERROR: unmkinitramfs failed — initrd.img may be corrupt."; exit 1; }; \ - if ! find "$$tmp" -path '*lib/firmware/i915/tgl_guc*' -print -quit | grep -q .; then \ + @if ! zstd -dc build/initrd.img | cpio -t --quiet 2>/dev/null | grep -q 'lib/firmware/i915/tgl_guc'; then \ echo "ERROR: i915/tgl_guc firmware missing from initrd."; \ - echo " Package installed but update-initramfs didn't pack /lib/firmware."; \ - echo " Check MODULES= in /etc/initramfs-tools/initramfs.conf."; \ + echo " firmware-misc-nonfree likely didn't install during bootstrap."; \ exit 1; \ fi @echo "initrd.img OK ($$(du -hL build/initrd.img | cut -f1), i915 firmware present)"