Skip to main content

max / alloy

7.1 KB · 156 lines History Blame Raw
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 # --report EXISTS SO THE WRITE CAN BE LOOKED AT FIRST. Reading NVRAM needs no
36 # privilege and writing it needs root, so without a read-only mode the only way
37 # to find out what this would do was to let it do it. Everything up to the first
38 # efibootmgr write runs identically in both modes, so a report is the plan and
39 # not a second implementation of it.
40
41 set -eu
42
43 NAME="Alloy"
44 DRY=0
45
46 usage() {
47 sed -n '2,39p' "$0"
48 echo
49 echo "Usage: alloy-boot-entry [--report]"
50 echo " --report, -n say what would change and write nothing. Needs no root."
51 }
52
53 while [ $# -gt 0 ]; do
54 case "$1" in
55 --report|--dry-run|-n) DRY=1; shift ;;
56 -h|--help) usage; exit 0 ;;
57 *) printf 'error: unknown argument %s\n' "$1" >&2; exit 2 ;;
58 esac
59 done
60
61 [ -d /sys/firmware/efi ] || { echo "not an EFI boot; no entry to name"; exit 0; }
62 command -v efibootmgr >/dev/null 2>&1 || { echo "no efibootmgr; leaving the boot entry alone"; exit 0; }
63
64 dump="$(efibootmgr -v)" || { echo "efibootmgr could not read NVRAM; leaving it alone"; exit 0; }
65
66 # Entries pointing at a shim on an ESP whose description is not already ours.
67 stale="$(printf '%s\n' "$dump" \
68 | grep -E '^Boot[0-9A-Fa-f]{4}\*?[[:space:]]' \
69 | grep -iE '\\EFI\\[^\\]+\\shim' \
70 | grep -viE "[[:space:]]${NAME}[[:space:]]" || true)"
71
72 if [ -z "$stale" ]; then
73 echo "the firmware boot entry already says $NAME"
74 exit 0
75 fi
76
77 order="$(printf '%s\n' "$dump" | sed -nE 's/^BootOrder: (.*)$/\1/p')"
78 new_order="$order"
79
80 # Looped from a file rather than from a pipe. A `while read` on the right of a
81 # pipe runs in a subshell, where the order this builds would be discarded at the
82 # end of the loop and an `exit 1` would end the subshell rather than the script.
83 # Both halves of this loop's job are exactly those two things.
84 entries="$(mktemp)"
85 trap 'rm -f "$entries"' EXIT
86 printf '%s\n' "$stale" > "$entries"
87
88 while read -r entry; do
89 [ -n "$entry" ] || continue
90 num="$(printf '%s\n' "$entry" | sed -nE 's/^Boot([0-9A-Fa-f]{4})\*?[[:space:]].*/\1/p')"
91 loader="$(printf '%s\n' "$entry" | sed -nE 's/.*(\\EFI\\[^\\]+\\[^ \t]*\.efi).*/\1/p' | head -1)"
92 uuid="$(printf '%s\n' "$entry" | sed -nE 's/.*HD\([0-9]+,GPT,([0-9a-fA-F-]+),.*/\1/p')"
93 [ -n "$num" ] && [ -n "$loader" ] && [ -n "$uuid" ] || {
94 echo "could not read an entry off: $entry" >&2
95 continue
96 }
97
98 # The disk and partition the entry already names, so the replacement points
99 # at the same place the firmware boots from rather than at a device this
100 # script guessed. It also keeps it off the installer medium, which carries
101 # an EFI System partition of its own and is still plugged in.
102 dev="$(lsblk -rno PATH,PARTUUID 2>/dev/null | awk -v u="$uuid" 'tolower($2)==tolower(u){print $1}' | head -1)"
103 [ -n "$dev" ] || { echo "no device carries PARTUUID $uuid; leaving Boot$num alone" >&2; continue; }
104 disk="/dev/$(lsblk -no PKNAME "$dev" | head -1)"
105 partn="$(cat "/sys/class/block/$(basename "$dev")/partition" 2>/dev/null || true)"
106 [ -b "$disk" ] && [ -n "$partn" ] || { echo "could not place $dev; leaving Boot$num alone" >&2; continue; }
107
108 # The last point both modes share. Everything above is a read; everything
109 # below writes NVRAM, so the report stops exactly here and the plan it
110 # prints is the one the write would follow.
111 if [ "$DRY" -eq 1 ]; then
112 printf 'would rename: Boot%s -> a new entry "%s" (%s on %s partition %s), then delete Boot%s\n' \
113 "$num" "$NAME" "$loader" "$disk" "$partn" "$num"
114 continue
115 fi
116
117 before="$(efibootmgr | sed -nE 's/^Boot([0-9A-Fa-f]{4})\*?.*/\1/p' | sort -u)"
118 efibootmgr -q -c -d "$disk" -p "$partn" -L "$NAME" -l "$loader"
119 after="$(efibootmgr | sed -nE 's/^Boot([0-9A-Fa-f]{4})\*?.*/\1/p' | sort -u)"
120 created="$(printf '%s\n' "$before" "$after" | sort | uniq -u | head -1)"
121
122 # From here the machine has two entries pointing at shim and boots either
123 # way, so every exit below is safe. Non-zero because NVRAM now holds
124 # something this script put there and could not finish accounting for.
125 [ -n "$created" ] || {
126 echo "no new boot entry appeared; Boot$num is unchanged" >&2
127 exit 1
128 }
129 efibootmgr | grep -q "^Boot${created}\*\?[[:space:]]*${NAME}\b" || {
130 echo "Boot$created is not the $NAME entry expected; Boot$num is unchanged" >&2
131 exit 1
132 }
133
134 efibootmgr -q -b "$num" -B
135 # printf, not echo: the loader path is full of backslashes and `echo` in
136 # dash interprets them, so `\EFI\fedora\shimx64.efi` logs as
137 # `\EFIedora\shimx64.efi` and the line quietly misreports what was done.
138 printf 'boot entry: Boot%s -> Boot%s "%s" (%s)\n' "$num" "$created" "$NAME" "$loader"
139
140 new_order="$(printf '%s' "$new_order" | sed "s/\b$num\b/$created/I")"
141 done < "$entries"
142
143 if [ "$DRY" -eq 1 ]; then
144 # The new numbers are assigned by the firmware at creation, so a report
145 # cannot name the order it would write. It can say the slots are kept,
146 # which is the property worth checking.
147 echo "boot order: $order, each renamed entry taking the slot of the one it replaces"
148 echo "nothing was written"
149 exit 0
150 fi
151
152 if [ -n "$new_order" ] && [ "$new_order" != "$order" ]; then
153 efibootmgr -q -o "$new_order"
154 echo "boot order: $order -> $new_order"
155 fi
156