perf: cut the image 38% and turn GPU acceleration back on #112
1 changed files with 136 additions and 0 deletions
perf(deploy): prune linux-firmware to the hardware bitSpire runs on
hardware.enableRedistributableFirmware installed the entire linux-firmware tree: 752MB compressed, 16% of the image and its single largest component. The fleet is four fixed Intel boards. The rest is firmware for Qualcomm, Mellanox, NVIDIA, Marvell, AMD and MediaTek parts that will never be in one of these machines. Keep i915 for the GPU, intel/iwlwifi, rtl_nic, rtw88, rtw89 and brcm for whatever NIC a given box turns out to have. Turning the option off also drops the extras it bundles (sof-firmware, libreelec-dvb, alsa-firmware, intel2200BG, zd1211fw), none of which applies to a soundless kiosk on a wired Intel board. The regulatory database is normally implied by that same option so it is now requested explicitly; without it WiFi is pinned to the most restrictive channel set. Intel WiFi is 89MB and most of what survives. That is the deliberately conservative half of the trade: losing the network on a fielded ATM is not recoverable remotely, and 89MB is cheap next to a site visit. sintra 4604 -> 3940 MB tejo 4604 -> 3940 MB batm3 4582 -> 3918 MB douro 4544 -> 3894 MB VERIFIED ON HARDWARE. sintra was switched to this and rebooted. It came back with ethernet up (r8169, RTL8168g), the kiosk running, and no firmware load failures. Before the reboot, for every module these boards use, the firmware the kernel declares was confirmed present: i915 44 of 44, r8169 23 of 23, r8152 7 of 7. iwlwifi declares 67 and 28 are absent, but all 28 are absent from the full upstream tree too, so the module simply names more files than linux-firmware ships. The reboot is what earned the intel/fw_sst_* entries. The first boot after pruning logged intel_sst_acpi: Direct firmware load for intel/fw_sst_22a8.bin failed with error -2 the Intel Smart Sound DSP that Cherry Trail boards probe at startup. The audio stack is already gone so nothing was functionally broken, but a recurring error in a payment terminal's boot log is worth 420KB to remove: an error people learn to ignore is one they will ignore when it matters. No static check would have found this — the firmware a driver requests at probe time is not what modinfo reports. Two traps found while building it, both carrying comments where they bite: The symlink loop originally ended in `[ -e ... ] && ln ...`, which makes the loop's exit status depend on whether the LAST candidate matched. A non-match returns 1 and set -e fails the build, so whether it worked was a function of readdir order. It passed standalone and failed once spliced in. Kept directories contain symlinks pointing outside themselves: brcm's blobs are links into cypress/. Left dangling they fail nixpkgs' compression step, and deleting them would silently drop firmware a device needs, so the targets get pulled in instead and anything still dangling is a hard error. The tree is left uncompressed because NixOS compresses each hardware.firmware entry itself, zstd or xz depending on the kernel. Confirmed: sintra gets -zstd, douro's 5.15 gets -xz. system.forbiddenDependenciesRegexes rejects the upstream package by its versioned name, so a nixpkgs bump or a stray module re-enabling the option fails the build instead of quietly putting 750MB back.
commit
645fd57e5b
|
|
@ -3,6 +3,122 @@
|
||||||
|
|
||||||
{ config, lib, pkgs, pkgs-unstable, ... }:
|
{ config, lib, pkgs, pkgs-unstable, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
# ── Firmware pruning (bitspire#70 sizing) ────────────────────────────
|
||||||
|
# hardware.enableRedistributableFirmware installs the entire linux-firmware
|
||||||
|
# tree: 752MB compressed, 16% of the image and its single largest item. The
|
||||||
|
# fleet is four fixed Intel boards. The other ~640MB is firmware for
|
||||||
|
# Qualcomm, Mellanox, NVIDIA, Marvell, AMD and MediaTek parts that will
|
||||||
|
# never appear in one of these machines.
|
||||||
|
#
|
||||||
|
# Keep only what a bitSpire board can plausibly load. Entries are paths
|
||||||
|
# inside lib/firmware; nothing outside this list is copied.
|
||||||
|
firmwareKeep = [
|
||||||
|
# Intel GPU. Gen9 (Apollo Lake) loads DMC from here. Bay Trail and
|
||||||
|
# Haswell load nothing, but 9.6MB is cheap insurance against a board swap.
|
||||||
|
"i915"
|
||||||
|
# Intel WiFi, 89MB and the bulk of what survives, covering every Intel
|
||||||
|
# card since 2008. This is the conservative half of the trade: losing the
|
||||||
|
# network on a deployed ATM is not remotely recoverable. Narrow it to the
|
||||||
|
# specific generation once each machine's card is known, via
|
||||||
|
# `lspci -k | grep -A3 Network` on the box.
|
||||||
|
"intel/iwlwifi"
|
||||||
|
"rtl_nic" # Realtek GbE (r8169) — the UP Board's onboard NIC
|
||||||
|
"rtw88" # Realtek WiFi, the usual M.2 or USB retrofit
|
||||||
|
"rtw89"
|
||||||
|
"brcm" # Broadcom WiFi, the other usual retrofit
|
||||||
|
# Intel Smart Sound Technology DSP, 420KB. Cherry Trail boards (sintra,
|
||||||
|
# tejo) probe intel_sst_acpi at boot whether or not anything will use the
|
||||||
|
# audio, and without the blob every boot logs
|
||||||
|
# Direct firmware load for intel/fw_sst_22a8.bin failed with error -2
|
||||||
|
# Found by pruning, rebooting sintra and reading dmesg. The audio stack is
|
||||||
|
# gone so this changes no behaviour, but a recurring error in a payment
|
||||||
|
# terminal's boot log is worth 420KB to remove: an error people learn to
|
||||||
|
# ignore is one they will ignore when it matters.
|
||||||
|
"intel/fw_sst_0f28.bin"
|
||||||
|
"intel/fw_sst_0f28_ssp0.bin"
|
||||||
|
"intel/fw_sst_22a8.bin"
|
||||||
|
];
|
||||||
|
|
||||||
|
# Prune the tree rather than hand-pick files, so a firmware bump can't
|
||||||
|
# silently drop a blob we depend on. Left UNCOMPRESSED on purpose: NixOS
|
||||||
|
# compresses each hardware.firmware entry itself, zstd or xz depending on
|
||||||
|
# what the machine's kernel understands, and douro's 5.15 predates zstd
|
||||||
|
# firmware support. Pre-compressing here would hand douro a tree it cannot
|
||||||
|
# read.
|
||||||
|
bitspireFirmware = pkgs.runCommand "linux-firmware-bitspire"
|
||||||
|
{
|
||||||
|
inherit (pkgs.linux-firmware) version;
|
||||||
|
meta = pkgs.linux-firmware.meta // {
|
||||||
|
description = "linux-firmware pruned to the hardware bitSpire ships on";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
''
|
||||||
|
src=${pkgs.linux-firmware}/lib/firmware
|
||||||
|
dst=$out/lib/firmware
|
||||||
|
mkdir -p "$dst"
|
||||||
|
|
||||||
|
for p in ${lib.escapeShellArgs firmwareKeep}; do
|
||||||
|
if [ ! -e "$src/$p" ]; then
|
||||||
|
echo "ERROR: firmwareKeep entry '$p' is not in linux-firmware" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
mkdir -p "$dst/$(dirname "$p")"
|
||||||
|
cp -a "$src/$p" "$dst/$p"
|
||||||
|
done
|
||||||
|
|
||||||
|
# A kept directory can contain symlinks pointing at blobs OUTSIDE it:
|
||||||
|
# brcm/brcmfmac*.bin are links into cypress/, for instance. Left dangling
|
||||||
|
# they fail nixpkgs' firmware compression step, and silently deleting
|
||||||
|
# them would quietly drop firmware a device needs. So pull the targets in
|
||||||
|
# instead. Looped because a resolved target can itself be a link.
|
||||||
|
for _pass in 1 2 3; do
|
||||||
|
_pulled=0
|
||||||
|
while IFS= read -r link; do
|
||||||
|
tgt=$(readlink -m "$link")
|
||||||
|
case "$tgt" in
|
||||||
|
"$dst"/*) rel=''${tgt#"$dst"/} ;;
|
||||||
|
*) continue ;;
|
||||||
|
esac
|
||||||
|
if [ ! -e "$dst/$rel" ] && [ -e "$src/$rel" ]; then
|
||||||
|
mkdir -p "$dst/$(dirname "$rel")"
|
||||||
|
cp -a "$src/$rel" "$dst/$rel"
|
||||||
|
_pulled=1
|
||||||
|
fi
|
||||||
|
done < <(find "$dst" -xtype l)
|
||||||
|
[ "$_pulled" -eq 0 ] && break
|
||||||
|
done
|
||||||
|
|
||||||
|
# Anything still dangling is not in linux-firmware at all. Fail loudly
|
||||||
|
# rather than ship a tree with holes in it.
|
||||||
|
if find "$dst" -xtype l | grep -q .; then
|
||||||
|
echo "ERROR: dangling firmware symlinks after resolution:" >&2
|
||||||
|
find "$dst" -xtype l >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# linux-firmware stores many blobs under a vendor directory and leaves a
|
||||||
|
# flat top-level symlink pointing at them, e.g.
|
||||||
|
# iwlwifi-cc-a0-77.ucode -> intel/iwlwifi/iwlwifi-cc-a0-77.ucode. The
|
||||||
|
# kernel requests the flat name, so a kept blob is useless without its
|
||||||
|
# link. Recreate every top-level link whose target survived the prune.
|
||||||
|
( cd "$src"
|
||||||
|
find . -maxdepth 1 -type l -printf '%f\t%l\n' \
|
||||||
|
| while IFS="$(printf '\t')" read -r link target; do
|
||||||
|
# if/then, not `[ ... ] && ln`: the latter makes the loop's exit
|
||||||
|
# status depend on whether the LAST candidate matched, and a
|
||||||
|
# non-match returns 1, which set -e turns into a build failure.
|
||||||
|
# Whether it fails is then a function of readdir order.
|
||||||
|
if [ -e "$dst/$target" ]; then
|
||||||
|
ln -s "$target" "$dst/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
)
|
||||||
|
|
||||||
|
echo "firmware kept: $(find "$dst" -type f | wc -l) files, \
|
||||||
|
$(find "$dst" -type l | wc -l) links, $(du -sh "$dst" | cut -f1) uncompressed"
|
||||||
|
'';
|
||||||
|
in
|
||||||
{
|
{
|
||||||
# System basics
|
# System basics
|
||||||
system.stateVersion = "24.05";
|
system.stateVersion = "24.05";
|
||||||
|
|
@ -28,6 +144,26 @@
|
||||||
documentation.enable = false;
|
documentation.enable = false;
|
||||||
documentation.nixos.enable = false;
|
documentation.nixos.enable = false;
|
||||||
|
|
||||||
|
# Ship the pruned firmware tree instead of all of linux-firmware. mkForce
|
||||||
|
# because every hardware/*.nix sets enableRedistributableFirmware = true;
|
||||||
|
# overriding once here keeps the four machines in step. Turning that option
|
||||||
|
# off also drops the extras it bundles (sof-firmware, libreelec-dvb,
|
||||||
|
# alsa-firmware, intel2200BG, zd1211fw and friends), none of which applies to
|
||||||
|
# a soundless kiosk on a wired Intel board. The regulatory database is
|
||||||
|
# normally implied by the same option, so ask for it explicitly: without it
|
||||||
|
# WiFi is pinned to the most restrictive channel set.
|
||||||
|
hardware.enableRedistributableFirmware = lib.mkForce false;
|
||||||
|
hardware.wirelessRegulatoryDatabase = true;
|
||||||
|
hardware.firmware = [ bitspireFirmware ];
|
||||||
|
|
||||||
|
# Make the prune stick. Without this, a nixpkgs bump or a stray module
|
||||||
|
# setting enableRedistributableFirmware back to true silently re-adds 750MB
|
||||||
|
# and nobody notices until an eMMC runs out of room at 04:00. The regex
|
||||||
|
# matches the upstream package's versioned name (linux-firmware-20260519)
|
||||||
|
# and deliberately not ours (linux-firmware-bitspire), so the pruned tree
|
||||||
|
# passes and the full one fails the build with a readable error.
|
||||||
|
system.forbiddenDependenciesRegexes = [ "linux-firmware-[0-9]" ];
|
||||||
|
|
||||||
# Networking
|
# Networking
|
||||||
networking = {
|
networking = {
|
||||||
hostName = "bitspire";
|
hostName = "bitspire";
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue