Skip to content

Commit 80c532c

Browse files
fix(iso/initramfs): apply build-pipeline audit findings (#220)
From the multi-agent ISO/initramfs audit vs upstream titanoboa/Bluefin/ Bazzite. Boot-relevant + hardening; none change the production kernel. - live-env/src/iso.yaml: bound the live writable overlay (rd.live.overlay.size=4096) on both entries [#1]; add a third 'verbose, no splash, no serial' GRUB entry for field-debugging — drops quiet/rhgb AND console=ttyS0 (the latter can stall every printk on a mini-PC with no real UART, observed on a Beelink SER8) [#4]. - custom-kernel/install.sh: refresh linux-firmware + amd-ucode after the CachyOS kernel swap so a newer kernel cannot reference amdgpu blobs the base lacks (black-screen before GDM) [#3]; zstd-guard + explicit --zstd on the initramfs regen so dracut cannot silently gzip. - 50-branding/install.sh: same zstd-guard + --zstd on its initramfs regen. - build-disk.yml: assert the ISO squashfs is zstd (guards the mksquashfs -e patch from a silent gzip fallback); warn-only 2 GiB constrained boot pass (surfaces live-overlay overflow on low usable RAM); correct the titanoboa pin comment (two -> three carried patches). - qemu-boot-wait.sh: --mem flag (enables the constrained pass). - check-upstream-pins.yml: note the third, fork-permanent titanoboa patch. - Stale-comment fixes: GRUB font (Liberation Mono -> Noto Sans Mono); README titanoboa pin (upstream SHA -> fork SHA + 3 patches).
1 parent 2363ce1 commit 80c532c

8 files changed

Lines changed: 93 additions & 15 deletions

File tree

.github/scripts/qemu-boot-wait.sh

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
# --gui-watch, gui=pass|fail|timeout|none (Layer C verdict, warn-only).
1717
set -euo pipefail
1818

19-
MODE="" IMAGE="" LOG="serial.log" TIMEOUT=1800 GUI_WATCH=0
19+
MODE="" IMAGE="" LOG="serial.log" TIMEOUT=1800 GUI_WATCH=0 MEM=4096
2020
OK_REGEX='Started.*gdm\.service|Reached target graphical\.target|margine login:'
2121
FAIL_REGEX=""
2222
while [[ $# -gt 0 ]]; do
@@ -27,6 +27,7 @@ while [[ $# -gt 0 ]]; do
2727
--timeout) TIMEOUT="$2"; shift 2 ;;
2828
--ok-regex) OK_REGEX="$2"; shift 2 ;;
2929
--fail-regex) FAIL_REGEX="$2"; shift 2 ;;
30+
--mem) MEM="$2"; shift 2 ;;
3031
--gui-watch) GUI_WATCH=1; shift ;;
3132
*) echo "unknown arg: $1" >&2; exit 2 ;;
3233
esac
@@ -63,7 +64,7 @@ fi
6364
rm -f qemu.pid "$LOG"
6465
qemu-system-x86_64 \
6566
-enable-kvm \
66-
-m 4096 -smp 4 \
67+
-m "$MEM" -smp 4 \
6768
-machine q35 \
6869
-drive "if=pflash,format=raw,readonly=on,file=$OVMF_CODE" \
6970
-drive if=pflash,format=raw,file=ovmf_vars.fd \

.github/workflows/build-disk.yml

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,18 @@ on:
6262
env:
6363
# Titanoboa pin — the official ISO builder since 2026-06-11
6464
# (ADR-0008 Phase 5). Pinned to OUR fork branch margine-pins =
65-
# upstream post-#138 HEAD (5c457c3d) + exactly two patches:
65+
# upstream post-#138 HEAD (5c457c3d) + THREE carried patches:
6666
# 1. mksquashfs -e ordering (upstream PR ublue-os/titanoboa#147)
6767
# — without it the squashfs silently falls back to gzip
68-
# 2. grub2.extra_cfg raw fragment (upstream PR pending) — used
69-
# for the Secure Boot "Enroll key (MokManager)" menu entry
70-
# Revert to the upstream pin once both PRs land. Renovate disabled.
68+
# 2. grub2.extra_cfg raw fragment (upstream PR ublue-os/titanoboa#148)
69+
# — the Secure Boot "Enroll key (MokManager)" menu entry
70+
# 3. grub.cfg trailing-slash: write grub.cfg only into EFI
71+
# SUBdirectories (cce73fc, NO upstream PR — fork-permanent).
72+
# Load-bearing for our plain-file EFI/MOK.der placement; without it
73+
# upstream's `for dir in /work/EFI/*` glob hits the MOK.der FILE and
74+
# hard-fails the ISO build with "Not a directory".
75+
# Reverting to an upstream pin is impossible until all three land.
76+
# Renovate disabled.
7177
TITANOBOA_REF: "daniel-g-carrasco/titanoboa@cce73fc476e97fed626283afb6c518e0882a12d7"
7278
IMAGE_REGISTRY: "ghcr.io/${{ github.repository_owner }}"
7379
DEFAULT_TAG: "stable"
@@ -363,6 +369,26 @@ jobs:
363369
echo "size_mb=$((SIZE_BYTES / 1024 / 1024))" >> "$GITHUB_OUTPUT"
364370
echo "Live ISO: $((SIZE_BYTES / 1024 / 1024)) MB"
365371
372+
- name: Assert squashfs is zstd (guards the mksquashfs -e patch)
373+
# The mksquashfs -e ordering patch on our titanoboa fork keeps the
374+
# squashfs at zstd-19; an accidental revert to an upstream pin
375+
# would silently produce a gzip squashfs (slow boot, bigger ISO)
376+
# that the boot-test below would NOT catch. Assert it explicitly.
377+
run: |
378+
set -euo pipefail
379+
sudo apt-get update -qq
380+
sudo apt-get install -y -qq squashfs-tools >/dev/null
381+
ISO="$(ls "${{ steps.iso.outputs.outdir }}"/*.iso | head -1)"
382+
MNT="$(mktemp -d)"
383+
sudo mount -o loop,ro "$ISO" "$MNT"
384+
SQ="$(sudo find "$MNT" -name 'squashfs.img' | head -1)"
385+
if [[ -z "$SQ" ]]; then sudo umount "$MNT"; echo "::error::no squashfs.img in ISO"; exit 1; fi
386+
COMP="$(sudo unsquashfs -s "$SQ" | grep -i 'Compression' || true)"
387+
echo "squashfs: $COMP"
388+
sudo umount "$MNT"
389+
echo "$COMP" | grep -qi 'zstd' \
390+
|| { echo "::error::squashfs is NOT zstd (mksquashfs -e patch regressed?)"; exit 1; }
391+
366392
# Gate (2026-06-12, review item #9): boot the freshly built ISO
367393
# in UEFI QEMU and require the live system to reach a usable
368394
# state. Catches Titanoboa/dracut/livesys regressions that static
@@ -389,6 +415,18 @@ jobs:
389415
--timeout 900 \
390416
--ok-regex 'Started.*gdm\.service|Reached target [Gg]raphical|livesys.*Finished|[Mm]argine login:' \
391417
--fail-regex 'dracut-emergency|Failed to start.*dmsquash|emergency shell'
418+
# Constrained-RAM pass (2 GiB), WARN-ONLY: surfaces a live-overlay
419+
# Overflow / read-only remount on low usable RAM (large iGPU UMA
420+
# carveouts) without blocking the ship. Flip to gating once proven.
421+
echo "=== constrained-RAM (2 GiB) boot pass (warn-only) ==="
422+
sudo -E .github/scripts/qemu-boot-wait.sh \
423+
--cdrom "$ISO" \
424+
--mem 2048 \
425+
--log iso-serial-2g.log \
426+
--timeout 900 \
427+
--ok-regex 'Started.*gdm\.service|Reached target [Gg]raphical|livesys.*Finished|[Mm]argine login:' \
428+
--fail-regex 'Overflow|Remounting.*read-only|dracut-emergency|emergency shell' \
429+
|| echo "::warning::constrained-RAM (2 GiB) boot did not reach a usable state — see iso-serial-2g.log (live-overlay overflow on low usable RAM?)"
392430
393431
- name: Upload ISO serial log
394432
# Always — a failed boot-test is debugged from this log (the
@@ -397,7 +435,7 @@ jobs:
397435
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
398436
with:
399437
name: iso-boot-serial-log
400-
path: iso-serial.log
438+
path: iso-serial*.log
401439
retention-days: 7
402440
if-no-files-found: ignore
403441

.github/workflows/check-upstream-pins.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ jobs:
4242
- name: Check carried patches against upstream
4343
run: |
4444
set -euo pipefail
45-
# PR -> short description of the carried patch
45+
# PR -> short description of the carried patch. NB: the fork
46+
# carries a THIRD patch (cce73fc, grub.cfg trailing-slash) that
47+
# has NO upstream PR — it is fork-permanent (load-bearing for our
48+
# plain-file EFI/MOK.der placement), so it is intentionally NOT
49+
# tracked here; only the upstreamable PRs are watched.
4650
declare -A PRS=(
4751
["147"]="mksquashfs -e ordering (zstd fallback fix)"
4852
["148"]="grub2.extra_cfg (Secure Boot enroll menu entry)"

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,9 +315,9 @@ full ~25-min image build. See the inline history comment at the
315315
top of [`.github/workflows/build.yml`](.github/workflows/build.yml)
316316
for the full rationale.
317317

318-
**Titanoboa migration:** Phase 0 scaffolding is in progress; see
318+
**Titanoboa migration:** complete since 2026-06-11 (ADR-0008 Phase 5); see
319319
[`margine-fedora-atomic` ADR-0008](https://github.com/daniel-g-carrasco/margine-fedora-atomic/blob/main/docs/adr/0008-titanoboa-migration-plan.md).
320-
Pin: `ublue-os/titanoboa@5c457c3d`.
320+
Pin: `daniel-g-carrasco/titanoboa@cce73fc` (our fork of upstream `ublue-os/titanoboa@5c457c3d` + 3 carried patches; see `.github/workflows/build-disk.yml`).
321321

322322
**Enabled GNOME extensions**: AppIndicator Support, Bazaar Integration,
323323
Dash to Dock, Gradia Integration, GSConnect (from Bluefin); o-tiling,

build_files/50-branding/install.sh

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,14 @@ fi
101101
# ostree dracut module (without which switch-root fails — see comment
102102
# in custom-kernel/install.sh).
103103
if command -v dracut >/dev/null 2>&1; then
104+
# zstd-compress explicitly (don't rely on inheriting Bluefin's
105+
# 10-compression.conf); fail loudly if zstd is missing rather than let
106+
# dracut silently gzip the initramfs.
107+
command -v zstd >/dev/null || { log "ERROR: zstd missing — dracut would silently gzip the initramfs"; exit 1; }
108+
export DRACUT_NO_XATTR=1
104109
for kver_dir in /usr/lib/modules/*/; do
105110
kver=$(basename "$kver_dir")
106-
dracut --force --no-hostonly --no-hostonly-cmdline \
111+
dracut --force --zstd --no-hostonly --no-hostonly-cmdline \
107112
--add "ostree" \
108113
--kver "$kver" \
109114
"${kver_dir}initramfs.img"

build_files/custom-kernel/install.sh

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,22 @@ restore_kernel_install_hooks
229229
log "Removing COPR repo file (kept only at build time)"
230230
rm -f /etc/yum.repos.d/*copr*
231231

232+
# Refresh GPU firmware + microcode to match the (newer) CachyOS kernel.
233+
# The CachyOS kernel can be newer than the base image's linux-firmware
234+
# snapshot (the CachyOS spec only Recommends: it), so a kernel that
235+
# references amdgpu blobs absent from the base would fail the GPU probe /
236+
# black-screen before GDM on bare metal — invisible to the virtio-gpu CI
237+
# gate. Upgrade ONLY the firmware packages actually installed (so an
238+
# unknown-package name can't abort the transaction), from the always-on
239+
# Fedora repos; never block the build on it.
240+
FW_PKGS="$(rpm -qa --qf '%{NAME}\n' 'linux-firmware*' 'amd-gpu-firmware*' 'amd-ucode*' 2>/dev/null | sort -u | tr '\n' ' ')"
241+
if [[ -n "${FW_PKGS// /}" ]]; then
242+
log "Refreshing firmware/microcode for $KERNEL_VERSION: $FW_PKGS"
243+
# shellcheck disable=SC2086 # intentional word-split of the package list
244+
dnf -y upgrade --refresh $FW_PKGS \
245+
|| log "WARN: firmware/microcode refresh non-fatal failure (continuing)"
246+
fi
247+
232248
# ---------------------------------------------------------------------------
233249
# v4l2loopback against the CachyOS kernel (OPTIONAL — skip on build error)
234250
# ---------------------------------------------------------------------------
@@ -562,9 +578,15 @@ log "Regenerating initramfs for all installed kernels (generic, bootc-path, ostr
562578
# Verified missing 2026-05-30: `lsinitrd <initramfs> | grep ostree`
563579
# returned ZERO lines on our published image. `--no-hostonly` alone
564580
# is insufficient; ostree dracut module must be EXPLICITLY requested.
581+
# The initramfs MUST be zstd-compressed (today this only works by
582+
# inheriting Bluefin's 10-compression.conf; nothing pins it). Pass --zstd
583+
# explicitly AND fail loudly if the tool is missing, so dracut can never
584+
# silently fall back to gzip (slower boot, bigger initramfs).
585+
command -v zstd >/dev/null || { err "zstd binary missing — dracut would silently gzip the initramfs"; exit 1; }
586+
export DRACUT_NO_XATTR=1
565587
for kver_dir in /usr/lib/modules/*/; do
566588
kver=$(basename "$kver_dir")
567-
dracut --force --no-hostonly --no-hostonly-cmdline \
589+
dracut --force --zstd --no-hostonly --no-hostonly-cmdline \
568590
--add "ostree" \
569591
--kver "$kver" \
570592
"${kver_dir}initramfs.img"

build_files/system_files/usr/lib/bootupd/grub2-static/configs.d/05_margine-gfxmode.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
# no-op. The resolution-INDEPENDENT lever is a bigger glyph bitmap = a large
99
# .pf2 font.
1010
#
11-
# margine.pf2 is a 36px Liberation Mono built by grub2-mkfont at image build
11+
# margine.pf2 is a 36px Noto Sans Mono built by grub2-mkfont at image build
1212
# (build_files/50-branding/install.sh) with family name "Margine", so its
1313
# embedded font name is exactly "Margine Regular 36" — that's what
1414
# gfxterm_font must select (gfxterm uses its tiny built-in font otherwise).

live-env/src/iso.yaml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,18 @@ grub2:
3434
timeout: 5
3535
entries:
3636
- name: "Install Margine"
37-
linux: "/images/pxeboot/vmlinuz quiet rhgb console=tty0 console=ttyS0,115200n8 systemd.show_status=1 root=live:CDLABEL=Margine-Live enforcing=0 rd.live.image"
37+
linux: "/images/pxeboot/vmlinuz quiet rhgb console=tty0 console=ttyS0,115200n8 systemd.show_status=1 root=live:CDLABEL=Margine-Live enforcing=0 rd.live.image rd.live.overlay.size=4096"
3838
initrd: "/images/pxeboot/initrd.img"
3939
- name: "Install Margine (Basic Graphics Mode)"
40-
linux: "/images/pxeboot/vmlinuz quiet rhgb console=tty0 console=ttyS0,115200n8 systemd.show_status=1 root=live:CDLABEL=Margine-Live enforcing=0 rd.live.image nomodeset"
40+
linux: "/images/pxeboot/vmlinuz quiet rhgb console=tty0 console=ttyS0,115200n8 systemd.show_status=1 root=live:CDLABEL=Margine-Live enforcing=0 rd.live.image rd.live.overlay.size=4096 nomodeset"
41+
initrd: "/images/pxeboot/initrd.img"
42+
- name: "Install Margine (verbose, no splash, no serial)"
43+
# Field-debugging entry (added after the 2026-06-24 Beelink SER8 /
44+
# Ryzen 8745H hang report). Drops `quiet rhgb` so the kernel + systemd
45+
# print to the SCREEN, and drops console=ttyS0 so a machine with no
46+
# real UART cannot stall every printk on serial TX. The bounded
47+
# overlay keeps writable-layer sizing predictable.
48+
linux: "/images/pxeboot/vmlinuz console=tty0 root=live:CDLABEL=Margine-Live enforcing=0 rd.live.image rd.live.overlay.size=4096"
4149
initrd: "/images/pxeboot/initrd.img"
4250
# Raw fragment appended after the generated entries (margine-pins /
4351
# upstream PR: grub2.extra_cfg). With Secure Boot ON the live CachyOS

0 commit comments

Comments
 (0)