Merge origin/main (trim-rpm) into optimise-ram
Combines the image trim (#18: langpack/firmware slim, kiosk weak-deps, nss-mdns KEEP+mask-avahi fix, final kiosk-stack build guard) with optimise-ram's overlay-RAM work (#17 fapolicyd db_max_size cap, .pyc unchecked-hash recompile). Conflict resolutions: - diskless.pkr.hcl (2d hygiene tail): kept BOTH additions — the .pyc recompile runs first (the 'must run last after every dnf' mutation), then the read-only FINAL GUARD (rpm -q kiosk stack) as the absolute last step. The nss-mdns fix (mask avahi, keep the package) auto-merged above the conflict — no regression. - docs/TODO.md: kept both #17 (fapolicyd db_max_size) and #18 (image trim), as the #18 stack-note predicted.
This commit is contained in:
@@ -296,6 +296,16 @@ build {
|
||||
# talks to the TPM directly via go-tpm. So this list stays at the baseline.)
|
||||
"dnf -y install wpa_supplicant tpm2-tools tpm2-tss nvme-cli cryptsetup dracut-network NetworkManager zstd openssl",
|
||||
|
||||
# Locale trim: @^minimal-environment pulls glibc-all-langpacks — ~228 MB of
|
||||
# locale data for EVERY language (measured 2026-07-03, 3rd-largest package in
|
||||
# the whole image). This fleet is English-only (we also strip
|
||||
# /usr/share/locale in 2d). Swap it for the English langpack + the C/POSIX
|
||||
# minimal set → reclaims ~220 MB of the read-only erofs. Install the small
|
||||
# replacements first, then drop the monolith (nothing Requires it; strict so
|
||||
# a surprise dep surfaces in CI rather than silently bloating the image).
|
||||
"dnf -y install glibc-minimal-langpack glibc-langpack-en",
|
||||
"dnf -y remove glibc-all-langpacks",
|
||||
|
||||
# EFI / Secure Boot management tools (efivar, mokutil, efibootmgr — all base
|
||||
# repos). Used over SSH on a workstation: `mokutil --sb-state/--pk/--kek/--db`
|
||||
# reads Secure Boot state + the enrolled PK/KEK/db; efibootmgr manages boot
|
||||
@@ -407,6 +417,39 @@ build {
|
||||
"install -D -m0755 /tmp/fleet-enrol /usr/sbin/fleet-enrol",
|
||||
"restorecon /usr/sbin/fleet-enrol 2>/dev/null || true",
|
||||
|
||||
# ---- Firmware slimming (BEFORE dracut so the initramfs shrinks too) ---
|
||||
# linux-firmware ships ~678 MB of blobs for hardware this fixed fleet will
|
||||
# never have. A live fleet audit (`modinfo -F firmware` over loaded modules +
|
||||
# sysfs/`lspci` driver bindings on the real 820-class hosts AND the VMs,
|
||||
# 2026-07-03) proved the ONLY loadable firmware any host requests is Intel
|
||||
# i915 (GPU); every NIC is firmware-free (e1000e's ethtool "firmware-version"
|
||||
# is on-chip NVM, not a /usr/lib/firmware file; virtio_net loads nothing), so
|
||||
# NO NIC needs firmware. This runs PRE-dracut deliberately: --no-hostonly +
|
||||
# plymouth's `drm` dep instmods a broad DRM set (i915/amdgpu/radeon/…) and
|
||||
# dracut bundles each module's firmware into the initramfs (amdgpu fw is the
|
||||
# classic UKI bloater), so trimming FIRST shrinks BOTH the rootfs erofs AND
|
||||
# the UKI's initramfs. Keep i915 (all Intel gens — the fleet's only
|
||||
# firmware-loading GPU) + intel/ice (the ice server-NIC DDP, the SOLE intel/
|
||||
# blob any host needs — ish/sof/ibt are sensor-hub/audio/bluetooth, none
|
||||
# loaded at boot) + intel-ucode; strip the rest (~600 MB). NOT mellanox/ —
|
||||
# those are mlxsw_spectrum SWITCH-ASIC blobs, not NIC firmware (mlx5 NIC fw is
|
||||
# on-card, never request_firmware'd from /usr/lib/firmware), so they're dead
|
||||
# weight even for an mlx5 build. Safe for the initramfs: the fleet
|
||||
# splash GPU (i915) fw is kept and the build-VM splash uses virtio-gpu/bochs
|
||||
# (no fw) — dracut just warns "firmware not found" for the removed drm modules
|
||||
# and continues. Risk asymmetry: missing NIC fw would brick the fleet (none
|
||||
# needed), missing GPU fw only degrades a display. BENCH-VERIFY the kiosk
|
||||
# renders on the 820. Strip-in-place (rm, not rpm -e): linux-firmware can't be
|
||||
# dropped as a package without also losing i915. See docs/TODO #18.
|
||||
"if [ -d /usr/lib/firmware ]; then find /usr/lib/firmware -mindepth 1 -maxdepth 1 ! -name i915 ! -name intel ! -name intel-ucode -exec rm -rf {} + ; fi",
|
||||
# Narrow intel/ to just ice (NIC DDP); drop ish (sensor hub), sof (audio DSP),
|
||||
# ibt (bluetooth) etc. — none are loaded on this fleet (audit 2026-07-03).
|
||||
"[ -d /usr/lib/firmware/intel ] && find /usr/lib/firmware/intel -mindepth 1 -maxdepth 1 ! -name ice -exec rm -rf {} + || true",
|
||||
# Wi-Fi firmware: wired-only fleet (no iwlwifi). Remove the packages cleanly
|
||||
# so the rpmdb stays honest (the find above dropped their iwlwifi-*.ucode).
|
||||
"dnf remove -y 'iwl*-firmware' 2>/dev/null || true",
|
||||
"echo \"==> firmware after strip: $(du -sh /usr/lib/firmware 2>/dev/null | cut -f1) (was ~678 MB)\"",
|
||||
|
||||
# ---- Build the fleet-generic initramfs ------------------------------
|
||||
# %%{ escapes the rpm query tags so Packer's HCL template engine treats
|
||||
# them as literals (a bare %{...} is parsed as a template directive).
|
||||
@@ -417,6 +460,22 @@ build {
|
||||
# under pipefail the pipeline reports failure even when the file is
|
||||
# present (a false negative that wrongly failed the build for many runs).
|
||||
"lsinitrd /boot/initramfs-generic.img > /tmp/lsinitrd.txt",
|
||||
# Initramfs footprint (this .img is embedded in the signed UKI and pulled
|
||||
# over PXE/HTTP on every boot). Report the built size + any firmware that
|
||||
# still made it in — post-strip this should be i915 only (pulled by
|
||||
# plymouth's drm dep); if amdgpu/radeon/etc. appear, the strip didn't run
|
||||
# before dracut. See docs/TODO #18.
|
||||
"echo \"==> initramfs size: $(du -h /boot/initramfs-generic.img | cut -f1)\"",
|
||||
"echo '==> firmware bundled in initramfs (expect i915 only):'; grep -i '/firmware/' /tmp/lsinitrd.txt || echo ' (none)'",
|
||||
# Largest files IN the initramfs (in-cpio size, before the outer zstd) — after
|
||||
# the firmware strip the .img is module-bound (the --no-hostonly DRM/net/crypto
|
||||
# set), so this surfaces the next trim targets (e.g. GPU driver kmods). See #18.
|
||||
# Sort into a FILE then head the file — NOT `sort | head`: head closes the pipe
|
||||
# after 25 lines, sort dies of SIGPIPE (exit 141), and step 2's
|
||||
# `set -euxo pipefail` turns that into a build failure (same trap as the
|
||||
# lsinitrd `| grep -q` note above). (The kiosk step's rpm-manifest `| head` is
|
||||
# safe only because that step runs plain `set -e`, no pipefail.)
|
||||
"echo '==> initramfs largest files:'; awk '/^-/{print $5, $NF}' /tmp/lsinitrd.txt | sort -rn > /tmp/initrd-sizes.txt; head -25 /tmp/initrd-sizes.txt; rm -f /tmp/initrd-sizes.txt",
|
||||
"grep -E 'macsec|wpa_supplicant|tpm2_nvread|nvme.tcp|erofs|veritysetup' /tmp/lsinitrd.txt",
|
||||
# Assert NetworkManager is in the initramfs — the network-manager dracut
|
||||
# module is what renews the DHCP lease post-switch-root. A missing/renamed
|
||||
@@ -924,6 +983,25 @@ build {
|
||||
# hard-needs it) and mask as a backstop against future weak-dep reinstalls.
|
||||
"dnf remove -y firewalld 2>/dev/null || true",
|
||||
"systemctl mask firewalld.service 2>/dev/null || true",
|
||||
# nss-mdns / avahi: DO NOT REMOVE — the EPEL `chromium` package hard-Requires
|
||||
# nss-mdns (Chromium resolves `.local` mDNS hostnames for WebRTC ICE
|
||||
# candidates, to avoid leaking local IPs in peer connections). `dnf remove
|
||||
# nss-mdns` therefore takes CHROMIUM with it as a dependent package, then
|
||||
# reaps its whole orphaned GUI stack (avahi, at-spi2, atk, adwaita,
|
||||
# chromium-common, ...) — a ~300 MB drop that ships a browserless, bricked
|
||||
# kiosk (flashing cursor; kiosk-session dies at `${BROWSER:?}`). This bit us
|
||||
# 2026-07-03: the earlier commit checked what nss-mdns NEEDS (avahi) but not
|
||||
# what NEEDS nss-mdns (chromium). The FINAL GUARD below now asserts the kiosk
|
||||
# stack survived so this can never ship silently again.
|
||||
#
|
||||
# We still don't want the avahi mDNS DAEMON listening on :5353 on a
|
||||
# locked-down kiosk (that was the real attack-surface concern). nss-mdns is
|
||||
# only an NSS module (libnss_mdns.so) — it needs no daemon; if avahi-daemon
|
||||
# isn't running, `.local` lookups just fall through. So keep the PACKAGES
|
||||
# (satisfy chromium's Requires) but mask the listener: no socket, no daemon,
|
||||
# dep satisfied, browser intact. ~1.5 MB kept — negligible vs a working kiosk.
|
||||
"systemctl mask avahi-daemon.service avahi-daemon.socket 2>/dev/null || true",
|
||||
"systemctl disable avahi-daemon.service avahi-daemon.socket 2>/dev/null || true",
|
||||
# (No Go toolchain to remove — fleet-hostapi + fleet-enrol are built OUT OF
|
||||
# the VM on the CI runner and file-provisioned in as static binaries.)
|
||||
# The build-time STIG reports must not ship inside the read-only rootfs.
|
||||
@@ -940,6 +1018,10 @@ build {
|
||||
"if [ -d /usr/lib64/chromium-browser/locales ]; then find /usr/lib64/chromium-browser/locales -type f -name '*.pak' ! -name 'en-US.pak' -delete; fi",
|
||||
# App translation catalogs (.mo) for locales this fleet never uses.
|
||||
"[ -d /usr/share/locale ] && find /usr/share/locale -mindepth 1 -maxdepth 1 -type d ! -name 'en*' ! -name 'C*' -exec rm -rf {} + || true",
|
||||
# (linux-firmware slimming moved to step 2, BEFORE the dracut build, so the
|
||||
# UKI initramfs shrinks too — see TODO #18. Nothing re-adds firmware after
|
||||
# that point, so the rootfs erofs is already trimmed by the time we get here.)
|
||||
#
|
||||
# .pyc invalidation vs reproducible mtimes: the erofs is built `-T 0`, which
|
||||
# zeroes every file's mtime. Python's DEFAULT .pyc invalidation is
|
||||
# timestamp-based (the .pyc header stores the source's mtime), so at runtime
|
||||
@@ -956,6 +1038,24 @@ build {
|
||||
# Version-agnostic loop so an EL point-release python bump doesn't silently
|
||||
# skip it. See docs/TODO.md #16.
|
||||
"for d in /usr/lib/python3.* /usr/lib64/python3.*; do [ -d \"$d\" ] && python3 -m compileall -f -q --invalidation-mode unchecked-hash \"$d\" 2>/dev/null || true; done",
|
||||
#
|
||||
# ---- FINAL GUARD: assert the kiosk stack survived the whole pipeline ----
|
||||
# 2026-07-03 incident: chromium installed cleanly in the 2b kiosk step (the
|
||||
# footprint audit showed chromium-149 + chromium-common, 523 pkgs), but by
|
||||
# the final image it was GONE (472 pkgs) — `dnf remove -y nss-mdns` in 2d
|
||||
# removed chromium AS A DEPENDENT PACKAGE (EPEL chromium hard-Requires
|
||||
# nss-mdns for WebRTC mDNS ICE candidates), then reaped its orphaned GUI
|
||||
# stack. Result: a ~500 MB image whose kiosk died at the `${BROWSER:?}` guard
|
||||
# in kiosk-session — a flashing-cursor boot with no weston, shipped green.
|
||||
# The nss-mdns removal is fixed above (kept + avahi listener masked), but a
|
||||
# missing browser must fail the BUILD, never brick the fleet: assert every
|
||||
# kiosk-critical package is still installed at the very end of the pipeline
|
||||
# (after remediation AND all 2d removes). This catches ANY future silent drop
|
||||
# — a new hard-Requires cascade, an oscap package_*_removed rule, autoremove.
|
||||
# Run AFTER the .pyc recompile (read-only rpm -q, lays down no bytecode, so it
|
||||
# doesn't violate the "recompile last" rule). enable_kiosk=false legitimately
|
||||
# ships no browser, so gate on it.
|
||||
"if [ \"${var.enable_kiosk}\" = \"true\" ] && [ -n \"${var.kiosk_url}\" ]; then for p in chromium chromium-common weston mesa-dri-drivers mesa-libEGL mesa-libgbm; do rpm -q \"$p\" >/dev/null 2>&1 || { echo \"ERROR: kiosk-critical package '$p' missing from the FINAL image — a later step removed it after the 2b kiosk install (e.g. a dnf remove whose dependency cascade took chromium, cf. the 2026-07-03 nss-mdns incident). Refusing to ship a bricked kiosk.\" >&2; exit 1; }; done; echo '==> guard: kiosk stack intact (chromium/weston/mesa present after remediation + hygiene)'; fi",
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -1561,3 +1561,73 @@ the kiosk weak-deps trim (commit `a193dc3`, ~100 fewer packages) shrinks the *ac
|
||||
is NOT a separate tmpfs, ties to #15's mount separation); `/var/log/{messages,audit}`
|
||||
grow unbounded in RAM (audit is local-only) — logrotate caps or SIEM offload on
|
||||
long-lived hosts.
|
||||
|
||||
## [~] 18. Slim the image — locale + firmware (biggest erofs-size wins)
|
||||
|
||||
**Finding (image-size audit via the 2b `rpm -qa --qf '%{SIZE}'` manifest, 2026-07-03):**
|
||||
the largest packages in the 534-package image are NOT the kiosk — they're base bloat:
|
||||
`linux-firmware` **678 MB**, `glibc-all-langpacks` **228 MB**, then chromium (370) +
|
||||
llvm-libs (129). The uncompressed erofs is read over NVMe/TCP on every boot of every
|
||||
host, so every dead byte is fleet-wide waste + attack surface.
|
||||
|
||||
**[x] DONE — glibc langpacks → en-only** (`diskless.pkr.hcl` step 2). `@^minimal-environment`
|
||||
pulls `glibc-all-langpacks` (all languages); swapped for `glibc-minimal-langpack` +
|
||||
`glibc-langpack-en`. ~220 MB reclaimed; image is English-only (we also strip
|
||||
`/usr/share/locale` in 2d). **BENCH-VERIFY:** locale still `en_US.UTF-8`; kiosk + login OK.
|
||||
|
||||
**[x] DONE — linux-firmware strip to a hardware-audited keep-list** (`diskless.pkr.hcl`
|
||||
step 2, run **BEFORE the dracut build** so the UKI initramfs shrinks too — not just the
|
||||
rootfs. `--no-hostonly` + plymouth's `drm` dep instmods a broad DRM set and dracut bundles
|
||||
each module's firmware into the initramfs, amdgpu being the classic UKI bloater; the strip
|
||||
runs first so dracut never sees it. A post-build diagnostic reports the `.img` size + any
|
||||
firmware still bundled — expected i915 only.)
|
||||
Live fleet audit (`modinfo -F firmware` over all loaded modules + sysfs/`lspci` driver
|
||||
bindings on the real 820-class hosts AND the VMs) proved the **only** loadable firmware
|
||||
any host requests is Intel **i915** (GPU). Every other driver is firmware-free —
|
||||
crucially **e1000e** (on-chip NVM, not a fw file) and **virtio_net**, so NO NIC needs
|
||||
firmware and the fleet-brick case is off the table. Keep-list: `i915` + `intel/` (ice
|
||||
DDP insurance for the `ice`/`mlx5` server-NIC build variant + small Intel blobs) +
|
||||
`mellanox` + `intel-ucode`; strip the rest (amdgpu/nvidia/radeon/ath/qcom/rtl/iwlwifi…
|
||||
~600 MB). Wi-Fi (`iwl*-firmware`, ~83 MB) removed as packages (wired-only fleet).
|
||||
Strip-in-place (`find … -exec rm -rf`, not `rpm -e` — can't drop the package without
|
||||
losing i915); a `du -sh /usr/lib/firmware` line reports the result in the build log.
|
||||
**BENCH-VERIFIED 2026-07-03 (commit `f59eb79`): built, booted, AND the kiosk SPA RENDERS
|
||||
on both the VM (virtio-gpu) and the 820 (i915).** This clears the firmware strip's one
|
||||
real risk — the i915 DMC/GuC blobs load on real hardware, so the keep-list
|
||||
(i915/intel/mellanox/intel-ucode, pre-dracut) is validated end-to-end (firmware 678→147 MB,
|
||||
initramfs bundles only the keep-list). Remaining caveat: if a future host has a non-Intel
|
||||
GPU or an `ice`/`mlx5` NIC, widen the keep-list before trusting fleet-wide. **NOT yet in
|
||||
`f59eb79`** (bench-verify separately — none touch i915, so render should be safe): the
|
||||
`nss-mdns`/avahi drop (`5cc72cc`) and the `omit_drivers` GPU-kmod trim + keep-list
|
||||
tightening — dropped `mellanox/`, narrowed `intel/` to `ice` only (`853ae20`). Audited
|
||||
models: HP-820-class (i915 + e1000e + iwlwifi-dropped) and virtio VMs.
|
||||
|
||||
**Build-verified 2026-07-03:** firmware **678 MB → 147 MB**; the initramfs diagnostic
|
||||
confirms only the keep-list is bundled (i915 all-gens + intel/ice + intel/ish + mellanox)
|
||||
— proving the pre-dracut ordering works. But the initramfs came out **97 MB**, and it's
|
||||
now **kernel-MODULE-bound, not firmware** (~10 MB fw): the `--no-hostonly` + plymouth
|
||||
`drm` set pulls every GPU driver (`amdgpu.ko` etc.) even though the fleet is i915-only.
|
||||
|
||||
**[x] DONE — follow-on trims (2026-07-03):**
|
||||
- **`omit_drivers+=" amdgpu radeon nouveau "`** in `90-diskless.conf` — drops the
|
||||
discrete-GPU kmods plymouth's `drm` dep would instmod (amdgpu.ko is the biggest single
|
||||
module); keeps i915 + virtio-gpu/bochs for the splash. Targets the 97 MB initramfs/UKI.
|
||||
- **Firmware keep-list tightened**: dropped `mellanox/` (those are `mlxsw_spectrum`
|
||||
SWITCH-ASIC blobs, NOT NIC fw — mlx5 NIC fw is on-card) and narrowed `intel/` to just
|
||||
`ice` (dropped ish/sof/ibt = sensor-hub/audio/bluetooth, none loaded at boot).
|
||||
- **Initramfs top-25-files diagnostic** added after the dracut build (sorts `lsinitrd.txt`
|
||||
by size) so the next build shows what dominates the `.img` — measure before the next cut.
|
||||
- **BENCH-VERIFY:** kiosk still renders on the 820 (i915 kept); check the new
|
||||
"initramfs largest files" log line for the post-omit size + next targets.
|
||||
|
||||
**Combined:** ~**800 MB** off the uncompressed erofs (langpacks + firmware), dwarfing the
|
||||
kiosk weak-deps trim (#4), plus a smaller UKI/initramfs from the DRM-driver omit. NEXT
|
||||
candidates from the manifest (measure, don't guess): `iso-codes` (19 MB),
|
||||
`samba-client-libs`/`intel-mediasdk`/`gstreamer` (chromium-common HARD deps — only
|
||||
removable by dropping HTML5 media, and samba/rabbitmq/rist/srt/zmq are libavformat
|
||||
DT_NEEDED so they can't go without breaking future media — confirmed via ldd 2026-07-03),
|
||||
`binutils` (25 MB, build-only?), `cracklib-dicts` (9.8 MB, needed for pam pwquality — keep).
|
||||
|
||||
**Stack note:** committed on `trim-rpm`. When `optimise-ram` rebases onto the new
|
||||
`trim-rpm`, expect a trivial `docs/TODO.md` tail conflict (both branches appended after
|
||||
#16 — `optimise-ram` added #17 fapolicyd, this added #18); resolve by keeping both.
|
||||
|
||||
@@ -25,5 +25,13 @@ add_dracutmodules+=" dot1x-macsec network-manager nvmf systemd-veritysetup "
|
||||
add_dracutmodules+=" plymouth "
|
||||
filesystems+=" erofs overlay "
|
||||
add_drivers+=" macsec nvme-tcp nvme-fabrics dm-verity @NIC_DRIVERS@ "
|
||||
# Omit the discrete-GPU DRM drivers plymouth's `drm` dep would otherwise instmod
|
||||
# in --no-hostonly mode. This fleet is Intel i915 ONLY (the build VM uses
|
||||
# virtio-gpu/bochs), so amdgpu/radeon/nouveau are dead weight — amdgpu.ko alone is
|
||||
# the single biggest module in the initramfs. i915 + virtio-gpu/bochs stay for the
|
||||
# splash. (Their FIRMWARE is already stripped pre-dracut in diskless.pkr.hcl; this
|
||||
# drops the MODULES too.) If a future host has a non-Intel GPU, remove it here.
|
||||
# See docs/TODO #18.
|
||||
omit_drivers+=" amdgpu radeon nouveau "
|
||||
install_items+=" /etc/dot1x/radius-ca.pem /etc/dot1x/wpa_supplicant-dot1x.conf "
|
||||
compress="zstd"
|
||||
|
||||
Reference in New Issue
Block a user