commit 9557ec7b9b78a8cbe5ad02889b4cb85aea8fb049 equwal <13551856+equwal@users.noreply.github.com> 2026-10-04 19:36:25 -0700 firmware.sh: install pinned linux-firmware files with sha256 check
.github/workflows/ci.yml | 2 +- answers.example | 2 ++ build-image.sh | 2 +- firmware.example | 3 +++ firmware.sh | 37 +++++++++++++++++++++++++++++++++++++ install.sh | 3 +++ test-qemu.py | 3 +++ 7 files changed, 50 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cbfa033..a834a0b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - run: shellcheck install.sh build-image.sh test.sh backends/*.sh + - run: shellcheck install.sh firmware.sh build-image.sh test.sh backends/*.sh - run: python3 -m py_compile test-qemu.py qemu-smoke: # Alpine + sway, LUKS: build the image, install, boot, check (about 20 min). diff --git a/answers.example b/answers.example index e8856f3..a9d839b 100644 --- a/answers.example +++ b/answers.example @@ -11,5 +11,7 @@ USER_PASS=change-me ROOT_PASS=change-me HOSTNAME_NEW=waybase #FIRMWARE=no # skip linux-firmware (VMs) +#FIRMWARE_TAG=20260916 # pinned linux-firmware files: tag and list (see firmware.example) +#FIRMWARE_LIST=$D/firmware.example #SERIAL=1 # console and login on ttyS0 (tests) #NIXPKGS=github:NixOS/nixpkgs/nixos-unstable diff --git a/build-image.sh b/build-image.sh index 04fa43c..7977768 100644 --- a/build-image.sh +++ b/build-image.sh @@ -13,7 +13,7 @@ build() { ISO=$OUT/alpine-standard-$R-x86_64.iso [ -s "$ISO" ] || wget -q -O "$ISO" "https://dl-cdn.alpinelinux.org/alpine/v$V/releases/x86_64/${ISO##*/}" W=$(mktemp -d); mkdir -p "$W/waybase" - cp -r "$SRC/install.sh" "$SRC/answers.example" "$SRC/backends" "$W/waybase/" + cp -r "$SRC/install.sh" "$SRC/firmware.sh" "$SRC/firmware.example" "$SRC/answers.example" "$SRC/backends" "$W/waybase/" # serial console as well as the screen, so the installer runs headless too xorriso -osirrox on -indev "$ISO" -extract /boot/grub/grub.cfg "$W/grub.cfg" \ -extract /boot/syslinux/syslinux.cfg "$W/syslinux.cfg" 2>/dev/null diff --git a/firmware.example b/firmware.example new file mode 100644 index 0000000..e8079e2 --- /dev/null +++ b/firmware.example @@ -0,0 +1,3 @@ +# SHA256 PATH of one linux-firmware file, pinned to the tag in FIRMWARE_TAG. +# firmware.sh -s TAG prints lines like these for the files dmesg reports missing. +2cde41c3e5ad181423bcc3e98ff9c49f743c88f18646af4d0b3c3a9664b831a1 i915/kbl_dmc_ver1_04.bin diff --git a/firmware.sh b/firmware.sh new file mode 100644 index 0000000..f022167 --- /dev/null +++ b/firmware.sh @@ -0,0 +1,37 @@ +#!/bin/sh +# firmware.sh - install pinned linux-firmware files into /lib/firmware. +# sh firmware.sh [-r ROOT] TAG LIST for each "SHA256 PATH" line of LIST: fetch PATH from +# linux-firmware tag TAG, check the hash, install ROOT/lib/firmware/PATH +# sh firmware.sh -s TAG print LIST lines for files the kernel failed to load (dmesg); +# the hashes come from the download: review them before you pin +set -eu +URL=https://gitlab.com/kernel-firmware/linux-firmware/-/raw +die() { echo "firmware.sh: $*" >&2; exit 1; } +ROOT=; SCAN= +[ "${1:-}" = -r ] && { ROOT=$2; shift 2; } +[ "${1:-}" = -s ] && { SCAN=1; shift; } +[ $# -ge 1 ] || die "usage: firmware.sh [-r ROOT] TAG LIST | -s TAG" +TAG=$1 +T=$(mktemp); trap 'rm -f "$T"' EXIT +fetch() { curl -fsSL --retry 5 --retry-all-errors --retry-delay 5 -o "$T" "$URL/$TAG/$1"; } +safe() { case $1 in /* | *..*) die "bad path: $1" ;; esac; } + +if [ -n "$SCAN" ]; then + dmesg | sed -n 's/.*Direct firmware load for \(.*\) failed with error.*/\1/p' | sort -u | + while read -r p; do + safe "$p" + [ -e "/lib/firmware/$p" ] && continue + if fetch "$p" 2>/dev/null; then echo "$(sha256sum "$T" | cut -d' ' -f1) $p"; else echo "# not in $TAG: $p"; fi + done + exit 0 +fi + +[ -r "${2:-}" ] || die "list not readable: ${2:-}" +while read -r sum p; do + case $sum in '' | '#'*) continue ;; esac + safe "$p" + fetch "$p" || die "download failed: $p" + echo "$sum $T" | sha256sum -c -s - || die "hash mismatch: $p" + install -D -m 644 "$T" "$ROOT/lib/firmware/$p" + echo "firmware: $p" +done < "$2" diff --git a/install.sh b/install.sh index cb95638..43aad78 100644 --- a/install.sh +++ b/install.sh @@ -15,6 +15,8 @@ # SERIAL=1 login getty on ttyS0 and kernel console there (for tests) # NIXPKGS flake ref for Nix-supplied packages (default: nixpkgs) # FIRMWARE=no skip linux-firmware (VMs; real hardware usually needs it) +# FIRMWARE_TAG, FIRMWARE_LIST install the pinned files of LIST (SHA256 PATH lines, see firmware.sh) +# from linux-firmware tag FIRMWARE_TAG into /lib/firmware set -eu D=$(cd "$(dirname "$0")" && pwd) M=/mnt/waybase @@ -289,6 +291,7 @@ esac; done ch "chown -R $USERNAME:\$(id -g $USERNAME) /home/$USERNAME" # --- kernel, initramfs, bootloader -------------------------------------- +[ -n "${FIRMWARE_LIST:-}" ] && { log "pinned firmware"; sh "$D/firmware.sh" -r $M "${FIRMWARE_TAG:?FIRMWARE_TAG is required with FIRMWARE_LIST}" "$FIRMWARE_LIST"; } log "kernel + initramfs" kernel if [ "$ENCRYPT" = yes ]; then ROOTARG="root=/dev/mapper/root $CRYPTARGS"; else ROOTARG="root=UUID=$UR"; fi diff --git a/test-qemu.py b/test-qemu.py index 43e09b0..65b1330 100644 --- a/test-qemu.py +++ b/test-qemu.py @@ -110,6 +110,7 @@ t0 = time.time() vm = VM(uefi=True, live=True) vm.expect("login:", 900); vm.send("root"); time.sleep(5) answers = (f"BASE={base}\nCOMPOSITOR='{comps}'\nDISK=/dev/vda\nYES=1\nSERIAL=1\nFIRMWARE=no\n" + f"FIRMWARE_TAG=20260916\nFIRMWARE_LIST=$D/firmware.example\n" f"ENCRYPT={'yes' if encrypt else 'no'}\nLUKS_PASS={PW['luks']}\nUSERNAME=tester\n" f"USER_PASS={PW['user']}\nROOT_PASS={PW['root']}\n") vm.run(f"cat > /tmp/answers <<'EOA'\n{answers}EOA") @@ -136,6 +137,7 @@ nix --version 2>&1 | head -1 | sed 's/^/NIXVER /' nix store info --store daemon >/tmp/ns.log 2>&1 && echo NIXDAEMON_OK || tail -3 /tmp/ns.log for b in foot fuzzel pipewire wireplumber waybase-session; do command -v $b >/dev/null || echo MISSING:$b; done echo FROMNIX $(cat /etc/waybase-from-nix) +test -s /lib/firmware/i915/kbl_dmc_ver1_04.bin && echo FW_OK for c in %s; do d=$(mktemp -d); chmod 700 $d XDG_RUNTIME_DIR=$d WLR_BACKENDS=headless WLR_RENDERER=pixman WLR_LIBINPUT_NO_DEVICES=1 $c >/tmp/$c.log 2>&1 & @@ -147,6 +149,7 @@ done """ % comps, 900) check("nix --version", "NIXVER nix (Nix)" in r, (re.search(r"NIXVER (.*)", r) or [None, ""])[1].strip()) check("nix daemon reachable", "NIXDAEMON_OK" in r) +check("pinned firmware installed", "FW_OK" in r) check("session programs", "MISSING:" not in r, " ".join(re.findall(r"MISSING:\S+", r))) note("supplied by Nix: " + (re.search(r"FROMNIX(.*)", r) or [None, ""])[1].strip()) for c in comps.split():