Skip to main content

max / alloy

Name the firmware boot entry at install time, where it is actually made MEASURED on a fresh fw12 install 2026-09-07: the installed ESP's EFI/fedora/BOOTX64.CSV reads `shimx64.efi,Alloy,,...` and efibootmgr reports two entries named Fedora. So alloy@cd44fec's payload is correct and reaches the disk, and the machine is still named Fedora. The reason is that the CSV is read by fbx64.efi, and fbx64 runs only when no entry points at shim. Something on the install path makes them, so the fallback never runs. The Containerfile asserted the opposite as fact -- "bootupd writes the ESP and calls no efibootmgr" -- and that comment is corrected here rather than left to mislead the next reader. The CSV is still worth having: it is what a machine whose NVRAM is cleared rebuilds from. It just never covered the ordinary install. usr/bin/alloy-boot-entry does the rename, and install_plan runs it last. LAST, not merely after the deploy. The helper maps the entry's PARTUUID to a device with lsblk, which is the reading `udevadm settle` already exists to make trustworthy: for a moment after bootc returns, lsblk reports null for every partition. Queued at the end it is behind that settle and behind everything the resolvers push in front of it. Placed after the deploy instead, it would have found no device and skipped the rename while exiting 0. Each entry is recreated and verified before the old one is deleted, so there is no moment with no entry pointing at shim, and a failure leaves either the old one or both. Both boot. Every reason to do nothing exits 0; a non-zero status means NVRAM holds something the script could not account for, which is worth failing an install for where a name in a menu is not. efibootmgr is now named in the package list. It arrived as someone else's dependency, and losing it would have shown up as machines quietly reverting to Fedora rather than as a build failure. Also fixes a test that was already red before this work: answer_sheet skipped ARCH alone while host-recipe.sh had grown UPDATE_TARGET and NVIDIA, so a correct recipe failed the suite. The reserved keys are read from the script now rather than restated, for the reason the language whitelist just taught.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session
https://claude.ai/code/session_01WFBzMprSmNCfvdj2cGZyka
Author: Max Johnson <me@maxj.phd> · 2026-09-07 20:16 UTC
Signed with PGP, not checked
Commit: 768d587906771a66a0d07a2d0fd8b5bfd070572e
Parent: 1545056
5 files changed, +187 insertions, -10 deletions
M Containerfile +24 -6
@@ -1525,6 +1525,12 @@
1525 1525 # signing is a build-host act and has nothing to do with a desktop
1526 1526 # session.
1527 1527 minisign \
1528 + # efibootmgr, for usr/bin/alloy-boot-entry, which names the firmware boot
1529 + # entry after `bootc install` has made one. It arrives anyway as something
1530 + # else's dependency; named here because the install step that needs it
1531 + # exits 0 when it is missing, so losing it would show up as machines
1532 + # quietly going back to saying Fedora rather than as a build failure.
1533 + efibootmgr \
1528 1534 # podman, the runtime behind two of `alloy pkg box`'s three isolation
1529 1535 # levels: `workspace` calls it directly and distrobox wraps it for
1530 1536 # `host`. flatpak is the third and is client-only, since `sandboxed`
@@ -4157,12 +4163,24 @@
4157 4163 # is one layer earlier than either, in the firmware's boot menu.
4158 4164 #
4159 4165 # HOW THE NAME GETS THERE. shim ships BOOTX64.CSV beside itself. When the
4160 - # machine has no NVRAM entry pointing at shim -- which is every freshly
4161 - # installed machine, because bootupd writes the ESP and calls no efibootmgr --
4162 - # the firmware runs \EFI\BOOT\BOOTX64.EFI, which is shim, which chain-loads
4163 - # fbx64.efi, which reads this CSV and creates the entry. Field 1 is the binary
4164 - # to point at and field 2 is the description the boot menu shows. Stock, it
4165 - # reads `shimx64.efi,Fedora,,This is the boot entry for Fedora`.
4166 + # machine has no NVRAM entry pointing at shim, the firmware runs
4167 + # \EFI\BOOT\BOOTX64.EFI, which is shim, which chain-loads fbx64.efi, which reads
4168 + # this CSV and creates the entry. Field 1 is the binary to point at and field 2
4169 + # is the description the boot menu shows. Stock, it reads
4170 + # `shimx64.efi,Fedora,,This is the boot entry for Fedora`.
4171 + #
4172 + # THIS IS NOT SUFFICIENT ON ITS OWN, and this comment said it was until
4173 + # 2026-09-07. It claimed the no-entry case was "every freshly installed machine,
4174 + # because bootupd writes the ESP and calls no efibootmgr". Measured on a fresh
4175 + # fw12 install that day: the installed ESP's CSV reads `shimx64.efi,Alloy,,...`
4176 + # and `efibootmgr` reports two entries named Fedora. Something on the install
4177 + # path makes them, so fbx64 never runs and this file is never read.
4178 + #
4179 + # What this RUN is still for: the machine whose NVRAM is cleared, which then
4180 + # rebuilds its entry from the right CSV. The ordinary install is named by
4181 + # usr/bin/alloy-boot-entry, which crates/alloy/src/install/plan.rs runs after
4182 + # the deploy. The two are not redundant -- they cover different moments -- and
4183 + # neither covers the other's.
4166 4184 #
4167 4185 # WHAT IS NOT TOUCHED, deliberately. The vendor directory stays EFI/fedora: the
4168 4186 # path is baked into the signed shim, and renaming it is a Secure Boot question
@@ -109,9 +109,12 @@
109 109 continue;
110 110 }
111 111 let key = line.split('=').next().unwrap_or("").trim();
112 - // ARCH is build/host-recipe.sh's own reserved key and never a build
113 - // argument; see that file's header.
114 - if key == "ARCH" {
112 + // The reserved keys are build/host-recipe.sh's own and are never build
113 + // arguments; see that file's header. Read from it rather than listed
114 + // here, because listing them here is what broke this test: UPDATE_TARGET
115 + // became reserved and this skipped ARCH alone, so the suite went red on
116 + // a recipe that was correct.
117 + if reserved_keys().iter().any(|k| k == key) {
115 118 continue;
116 119 }
117 120 assert!(
@@ -120,3 +123,18 @@
120 123 );
121 124 }
122 125 }
126 +
127 + /// The keys `build/host-recipe.sh` consumes itself instead of passing to podman.
128 + ///
129 + /// Derived from the script rather than restated, on the same reasoning as the
130 + /// language whitelist: a value that lives in two files agrees in both only
131 + /// until someone edits one.
132 + fn reserved_keys() -> Vec<String> {
133 + tree("build/host-recipe.sh")
134 + .lines()
135 + .filter_map(|line| {
136 + let rest = line.trim().strip_prefix(r#"if [ "$key" = ""#)?;
137 + rest.split('"').next().map(str::to_string)
138 + })
139 + .collect()
140 + }
@@ -503,6 +503,28 @@
503 503 )
504 504 }),
505 505 ));
506 + // Name the firmware boot entry, last.
507 + //
508 + // The image ships a patched BOOTX64.CSV and it reaches the ESP intact, but
509 + // that file is read by fbx64.efi, and fbx64 runs only when no entry points
510 + // at shim. `bootc install` leaves entries of its own, so the fallback never
511 + // runs and a machine comes up saying Fedora. Measured on a fresh fw12
512 + // install 2026-09-07: the installed ESP's CSV says Alloy and `efibootmgr`
513 + // says Fedora, twice.
514 + //
515 + // LAST, AND NOT MERELY AFTER THE DEPLOY. The helper maps the entry's
516 + // PARTUUID to a device with `lsblk`, which is the same reading `udevadm
517 + // settle` above exists to make trustworthy: for a moment after bootc
518 + // returns, lsblk reports null for every partition. Queued here it is behind
519 + // that settle and behind everything a resolver pushes in front of it, so
520 + // the disk it reads is one that has already been mounted, configured and
521 + // checked.
522 + //
523 + // `Run` rather than `Attempt`: the helper exits 0 for every reason to do
524 + // nothing at all, so a non-zero status means it left NVRAM holding
525 + // something it could not account for. That is worth stopping for; a name in
526 + // a firmware menu is not.
527 + stages.push(Stage::Run(Invocation::new("alloy-boot-entry")));
506 528 stages
507 529 }
508 530
@@ -1398,7 +1398,7 @@
1398 1398 let (view, _log) = at_summary();
1399 1399 let shown = view.plan_display();
1400 1400
1401 - assert_eq!(shown.len(), 4, "{shown:#?}");
1401 + assert_eq!(shown.len(), 5, "{shown:#?}");
1402 1402 // The update stream is on the line the confirm screen shows, which is
1403 1403 // where a flag that decides how the machine behaves for its whole life
1404 1404 // belongs.
@@ -1420,6 +1420,10 @@
1420 1420 assert_eq!(shown[1], "udevadm settle");
1421 1421 assert_eq!(shown[2], "mkdir -p /run/alloy-target");
1422 1422 assert!(shown[3].starts_with("lsblk"), "{}", shown[3]);
1423 + // Naming the firmware boot entry is the last line the summary can show. It
1424 + // is queued behind everything the discovery resolves, because it reads the
1425 + // disk with lsblk and wants the settle above to have happened.
1426 + assert_eq!(shown[4], "alloy-boot-entry");
1423 1427 }
1424 1428
1425 1429 // The summary can only show what is known before anything runs. Everything
@@ -1,0 +1,115 @@
1 + #!/bin/sh
2 + # alloy-boot-entry — make the firmware boot menu say Alloy, after the install.
3 + #
4 + # WHY THIS EXISTS AND WHY THE CONTAINERFILE'S CSV IS NOT ENOUGH. The image ships
5 + # a patched BOOTX64.CSV in the bootupd payload, and it reaches the ESP intact:
6 + # measured on a fresh fw12 install 2026-09-07, the installed ESP's
7 + # EFI/fedora/BOOTX64.CSV reads `shimx64.efi,Alloy,,...`. The machine still came
8 + # up with two entries named Fedora.
9 + #
10 + # The reason is that the CSV is read by fbx64.efi, and fbx64 runs only when the
11 + # firmware has no entry pointing at shim. Something on the install path creates
12 + # those entries itself, so fbx64's fallback never runs and the CSV is never
13 + # read. The Containerfile comment beside that RUN says the opposite -- "bootupd
14 + # writes the ESP and calls no efibootmgr" -- and that is measured to be false.
15 + #
16 + # So the CSV is necessary and not sufficient. It is what a machine whose NVRAM
17 + # is cleared will use; this is what every ordinary install needs.
18 + #
19 + # WHAT IT DOES NOT DO, matching the Containerfile: the vendor directory stays
20 + # EFI/fedora and the loader stays shimx64.efi. Both are baked into the signed
21 + # shim, so changing either is a Secure Boot question rather than a branding one.
22 + # Only the description changes.
23 + #
24 + # ORDERING IS THE SAFETY PROPERTY. efibootmgr has no rename verb, so each entry
25 + # is recreated and verified BEFORE the old one is deleted. There is no moment at
26 + # which the machine has no entry pointing at shim, and a failure anywhere leaves
27 + # either the old entry or both. Both boot.
28 + #
29 + # EXIT STATUS IS A CONTRACT. This runs as the last thing in an install that has
30 + # otherwise succeeded, and the name in a firmware menu is not worth failing a
31 + # good install over. So every reason to do nothing -- not an EFI boot, no
32 + # efibootmgr, nothing left to rename -- exits 0. A non-zero exit means NVRAM was
33 + # left worse than it was found, which is the only outcome worth stopping for.
34 +
35 + set -eu
36 +
37 + NAME="Alloy"
38 +
39 + [ -d /sys/firmware/efi ] || { echo "not an EFI boot; no entry to name"; exit 0; }
40 + command -v efibootmgr >/dev/null 2>&1 || { echo "no efibootmgr; leaving the boot entry alone"; exit 0; }
41 +
42 + dump="$(efibootmgr -v)" || { echo "efibootmgr could not read NVRAM; leaving it alone"; exit 0; }
43 +
44 + # Entries pointing at a shim on an ESP whose description is not already ours.
45 + stale="$(printf '%s\n' "$dump" \
46 + | grep -E '^Boot[0-9A-Fa-f]{4}\*?[[:space:]]' \
47 + | grep -iE '\\EFI\\[^\\]+\\shim' \
48 + | grep -viE "[[:space:]]${NAME}[[:space:]]" || true)"
49 +
50 + if [ -z "$stale" ]; then
51 + echo "the firmware boot entry already says $NAME"
52 + exit 0
53 + fi
54 +
55 + order="$(printf '%s\n' "$dump" | sed -nE 's/^BootOrder: (.*)$/\1/p')"
56 + new_order="$order"
57 +
58 + # Looped from a file rather than from a pipe. A `while read` on the right of a
59 + # pipe runs in a subshell, where the order this builds would be discarded at the
60 + # end of the loop and an `exit 1` would end the subshell rather than the script.
61 + # Both halves of this loop's job are exactly those two things.
62 + entries="$(mktemp)"
63 + trap 'rm -f "$entries"' EXIT
64 + printf '%s\n' "$stale" > "$entries"
65 +
66 + while read -r entry; do
67 + [ -n "$entry" ] || continue
68 + num="$(printf '%s\n' "$entry" | sed -nE 's/^Boot([0-9A-Fa-f]{4})\*?[[:space:]].*/\1/p')"
69 + loader="$(printf '%s\n' "$entry" | sed -nE 's/.*(\\EFI\\[^\\]+\\[^ \t]*\.efi).*/\1/p' | head -1)"
70 + uuid="$(printf '%s\n' "$entry" | sed -nE 's/.*HD\([0-9]+,GPT,([0-9a-fA-F-]+),.*/\1/p')"
71 + [ -n "$num" ] && [ -n "$loader" ] && [ -n "$uuid" ] || {
72 + echo "could not read an entry off: $entry" >&2
73 + continue
74 + }
75 +
76 + # The disk and partition the entry already names, so the replacement points
77 + # at the same place the firmware boots from rather than at a device this
78 + # script guessed. It also keeps it off the installer medium, which carries
79 + # an EFI System partition of its own and is still plugged in.
80 + dev="$(lsblk -rno PATH,PARTUUID 2>/dev/null | awk -v u="$uuid" 'tolower($2)==tolower(u){print $1}' | head -1)"
81 + [ -n "$dev" ] || { echo "no device carries PARTUUID $uuid; leaving Boot$num alone" >&2; continue; }
82 + disk="/dev/$(lsblk -no PKNAME "$dev" | head -1)"
83 + partn="$(cat "/sys/class/block/$(basename "$dev")/partition" 2>/dev/null || true)"
84 + [ -b "$disk" ] && [ -n "$partn" ] || { echo "could not place $dev; leaving Boot$num alone" >&2; continue; }
85 +
86 + before="$(efibootmgr | sed -nE 's/^Boot([0-9A-Fa-f]{4})\*?.*/\1/p' | sort -u)"
87 + efibootmgr -q -c -d "$disk" -p "$partn" -L "$NAME" -l "$loader"
88 + after="$(efibootmgr | sed -nE 's/^Boot([0-9A-Fa-f]{4})\*?.*/\1/p' | sort -u)"
89 + created="$(printf '%s\n' "$before" "$after" | sort | uniq -u | head -1)"
90 +
91 + # From here the machine has two entries pointing at shim and boots either
92 + # way, so every exit below is safe. Non-zero because NVRAM now holds
93 + # something this script put there and could not finish accounting for.
94 + [ -n "$created" ] || {
95 + echo "no new boot entry appeared; Boot$num is unchanged" >&2
96 + exit 1
97 + }
98 + efibootmgr | grep -q "^Boot${created}\*\?[[:space:]]*${NAME}\b" || {
99 + echo "Boot$created is not the $NAME entry expected; Boot$num is unchanged" >&2
100 + exit 1
101 + }
102 +
103 + efibootmgr -q -b "$num" -B
104 + # printf, not echo: the loader path is full of backslashes and `echo` in
105 + # dash interprets them, so `\EFI\fedora\shimx64.efi` logs as
106 + # `\EFIedora\shimx64.efi` and the line quietly misreports what was done.
107 + printf 'boot entry: Boot%s -> Boot%s "%s" (%s)\n' "$num" "$created" "$NAME" "$loader"
108 +
109 + new_order="$(printf '%s' "$new_order" | sed "s/\b$num\b/$created/I")"
110 + done < "$entries"
111 +
112 + if [ -n "$new_order" ] && [ "$new_order" != "$order" ]; then
113 + efibootmgr -q -o "$new_order"
114 + echo "boot order: $order -> $new_order"
115 + fi