Skip to content

Commit c3ab47b

Browse files
committed
initrd/bin/kexec-select-boot.sh: add boot menu compat markers, display-only ISO-param stripping, dedup and TPM2 guard
New functions: - boot_marker(): reads /tmp/kexec_initrd_compat.txt and returns [OK]/[!]/ blank per-initrd marker (ANSI-colored in CLI mode) - fmt_boot_target(): formats kernel/initrd as "[path | path]", collapsing NixOS store paths to basename when > 35 chars - fmt_display_params(): strips ISO-finding parameters (iso-scan/filename, findiso, fromiso, img_dev, img_loop, iso, live-media) from params for menu display only. Full params preserved in entry passed to kexec-boot.sh and in saved defaults. parse_option() rewrite: - Extract kernel, initrd, and params from pipe-delimited entries - Log parsed values for debugging Menu display (get_menu_option): - Show NOTE legend for [OK]/[!]/blank markers before menu - Display "[OK] name (params) [kernel | initrd]" in whiptail and CLI - Add "b" option: "Select different ISO" (exit code 2) - Log each menu line to debug.log for remote troubleshooting - Sort entries alphabetically by name; dedup with awk -F'|' '!seen[$1]++' (replaces sort -t\| -k1 -u which BusyBox evaluates by full line, not key) - Restore original entry order for non-unique (non-ISO) paths using cp - Replace expr with $((...)) Confirmation dialog (confirm_menu_option): - Show full kernel, initrd, params, Board+/Board- add/remove, USB+ params, and final combined cmdline - Add "Back to menu" (b) option — Esc/Cancel returns to menu TPM2 primary handle guard: - Skip TPM2 primhdl hash check when valid_rollback is already "y" (ISO/force boot modes), avoiding spurious warning when paramsdir is tmpfs Misc: - Remove CHANGED_FILES whiptail display before DIE - Soft-check confirm_menu_option return in whiptail - Add DEBUG log for hash mismatch Signed-off-by: Thierry Laurion <insurgo@riseup.net>
1 parent 6d89c9c commit c3ab47b

1 file changed

Lines changed: 185 additions & 36 deletions

File tree

initrd/bin/kexec-select-boot.sh

Lines changed: 185 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ paramsdev="${paramsdev%%/}"
5959
paramsdir="${paramsdir%%/}"
6060

6161
PRIMHASH_FILE="$paramsdir/kexec_primhdl_hash.txt"
62-
if [ "$CONFIG_TPM2_TOOLS" = "y" ]; then
62+
if [ "$CONFIG_TPM2_TOOLS" = "y" ] && [ "$valid_rollback" != "y" ]; then
6363
if [ -s "$PRIMHASH_FILE" ]; then
6464
#PRIMHASH_FILE (normally /boot/kexec_primhdl_hash.txt) exists and is not empty
6565
sha256sum -c "$PRIMHASH_FILE" >/dev/null 2>&1 ||
@@ -91,6 +91,7 @@ verify_global_hashes() {
9191
whiptail_error --title 'ERROR: Boot Hash Mismatch' \
9292
--msgbox "The following files failed the verification process:\n${CHANGED_FILES}\nExiting to a recovery shell" 0 80
9393
fi
94+
DEBUG "kexec-select-boot: hash mismatch in $TMP_HASH_FILE"
9495
DIE "$TMP_HASH_FILE: boot hash mismatch"
9596
fi
9697
# If user enables it, check root hashes before boot as well
@@ -139,82 +140,230 @@ get_menu_option() {
139140
if [ $num_options -eq 1 -a $first_menu = "y" ]; then
140141
option_index=1
141142
elif [ "$gui_menu" = "y" ]; then
143+
if [ ! -f /tmp/kexec_compat_shown ] && [ -f /tmp/kexec_initrd_compat.txt ]; then
144+
NOTE "$(printf '\033[0;32m[OK]\033[0m=compatible \033[1;33m[!]\033[0m=may fail after kexec (blank)=cannot verify')"
145+
touch /tmp/kexec_compat_shown
146+
fi
142147
MENU_OPTIONS=()
143148
n=0
149+
# Show kernel/initrd in menu as "[OK] name (params) [kernel | initrd]"
150+
# Log to debug.log so remote troubleshooting can see exact menu format.
151+
# Long store paths (NixOS) collapse to basename; short paths keep directory context
144152
while read option; do
145153
parse_option
146154
n=$(expr $n + 1)
147-
MENU_OPTIONS+=("$n" "$name")
155+
local marker target display_params optline
156+
marker=$(boot_marker)
157+
target=$(fmt_boot_target)
158+
display_params=$(fmt_display_params "$params")
159+
if [ -n "$display_params" ]; then
160+
optline="$name ($display_params) $target"
161+
else
162+
optline="$name $target"
163+
fi
164+
if [ -n "$marker" ]; then
165+
MENU_OPTIONS+=("$n" "$marker $optline")
166+
else
167+
MENU_OPTIONS+=("$n" "$optline")
168+
fi
169+
DEBUG "whiptail menu: [$n] $marker $optline"
148170
done <$TMP_MENU_FILE
171+
if [ -n "$add" ]; then
172+
MENU_OPTIONS+=("b" "Select different ISO")
173+
fi
149174

175+
if [ -n "$add" ]; then
176+
local menu_prompt="Choose the boot option [1-$n, a to abort, b to select different ISO]:"
177+
else
178+
local menu_prompt="Choose the boot option [1-$n, a to abort]:"
179+
fi
150180
whiptail_type $BG_COLOR_MAIN_MENU --title "Select your boot option" \
151-
--menu "Choose the boot option [1-$n, a to abort]:" 0 80 8 \
181+
--menu "$menu_prompt" 0 80 8 \
152182
-- "${MENU_OPTIONS[@]}" \
153-
2>/tmp/whiptail || DIE "Aborting boot attempt"
183+
2>/tmp/whiptail || option_index="a"
154184

155185
option_index=$(cat /tmp/whiptail)
156186
else
187+
if [ ! -f /tmp/kexec_compat_shown ] && [ -f /tmp/kexec_initrd_compat.txt ]; then
188+
NOTE "$(printf '\033[0;32m[OK]\033[0m=compatible \033[1;33m[!]\033[0m=may fail after kexec (blank)=cannot verify')"
189+
touch /tmp/kexec_compat_shown
190+
fi
157191
STATUS "Select your boot option:"
158192
n=0
159193
while read option; do
160194
parse_option
161-
n=$(expr $n + 1)
162-
# Use the same device routing as INPUT so option lines and the
163-
# prompt share the same unbuffered fd (HEADS_TTY when in gui-init
164-
# context, stderr otherwise). Writing to stdout is wrong here
165-
# because DO_WITH_DEBUG pipes stdout through tee for debug logging,
166-
# making it fully buffered — the last option would appear after the
167-
# INPUT prompt.
168-
printf '%d. %s [%s]\n' "$n" "$name" "$kernel" >"${HEADS_TTY:-/dev/stderr}"
195+
n=$((n + 1))
196+
local marker target display_params optline
197+
marker=$(boot_marker)
198+
target=$(fmt_boot_target)
199+
display_params=$(fmt_display_params "$params")
200+
if [ -n "$marker" ]; then
201+
optline="$n. $marker $name ${display_params:+($display_params)} $target"
202+
else
203+
optline="$n. $name ${display_params:+($display_params)} $target"
204+
fi
205+
printf '%s\n' "$optline" >"${HEADS_TTY:-/dev/stderr}"
206+
DEBUG "CLI menu: $optline"
169207
done <$TMP_MENU_FILE
170208

171-
INPUT "Choose the boot option [1-$n, a to abort]:" -r option_index
172-
173-
if [ "$option_index" = "a" ]; then
174-
DIE "Aborting boot attempt"
209+
if [ -n "$add" ]; then
210+
INPUT "Choose the boot option [1-$n, a to abort, b for different ISO]:" -r option_index
211+
else
212+
INPUT "Choose the boot option [1-$n, a to abort]:" -r option_index
175213
fi
176214
fi
215+
216+
if [ "$option_index" = "a" ]; then
217+
STATUS "Boot aborted by user"
218+
exit 1
219+
fi
220+
if [ "$option_index" = "b" ] && [ -n "$add" ]; then
221+
STATUS "Returning to ISO selection"
222+
exit 2
223+
fi
177224
first_menu="n"
178225

179226
option=$(head -n $option_index $TMP_MENU_FILE | tail -1)
180227
parse_option
181228
}
182229

183230
confirm_menu_option() {
184-
if [ "$gui_menu" = "y" ]; then
185-
default_text="Make default"
186-
[[ "$CONFIG_TPM_NO_LUKS_DISK_UNLOCK" = "y" ]] && default_text="${default_text} and boot"
187-
whiptail_warning --title "Confirm boot details" \
188-
--menu "Confirm the boot details for $name:\n\n$(echo $kernel | fold -s -w 80) \n\n" 0 80 8 \
189-
-- 'd' "${default_text}" 'y' "Boot one time" \
190-
2>/tmp/whiptail || DIE "Aborting boot attempt"
191-
192-
option_confirm=$(cat /tmp/whiptail)
231+
# Show full kernel/initrd/params in the confirmation dialog.
232+
# Cancel/Esc returns to the menu (option_confirm="b") instead of aborting,
233+
# so users can change their selection without restarting the boot flow.
234+
# The full cmdline combines the entry's parsed params with the global ADD
235+
# params (injected by kexec-iso-init.sh for ISO boot).
236+
if [ "$gui_menu" = "y" ]; then
237+
default_text="Make default"
238+
[[ "$CONFIG_TPM_NO_LUKS_DISK_UNLOCK" = "y" ]] && default_text="${default_text} and boot"
239+
whiptail_warning --title "Confirm boot details" \
240+
--menu "$name\n\nKernel: $kernel\nInitramfs: ${initrd:--}\nOptions: ${params:--}\n${CONFIG_BOOT_KERNEL_ADD:+Board adds: $CONFIG_BOOT_KERNEL_ADD\n}${CONFIG_BOOT_KERNEL_REMOVE:+Board removes: $CONFIG_BOOT_KERNEL_REMOVE\n}${add:+ISO params: $add\n}Kernel cmdline: $(echo "$params $CONFIG_BOOT_KERNEL_ADD $add" | xargs)\n" 0 80 8 \
241+
-- 'y' "Boot" 'd' "${default_text}" 'b' "Back to menu" \
242+
2>/tmp/whiptail && option_confirm=$(cat /tmp/whiptail) || option_confirm="b"
193243
else
194-
STATUS "Confirm boot details for $name:"
195-
INFO "$option"
196-
197-
INPUT "Confirm selection by pressing 'y', make default with 'd':" -n 1 option_confirm
244+
STATUS " Confirm boot details for $name:"
245+
STATUS " Kernel: $kernel"
246+
STATUS " Initramfs: ${initrd:--}"
247+
STATUS " Options: ${params:--}"
248+
[ -n "$CONFIG_BOOT_KERNEL_ADD" ] && STATUS " Board adds: $CONFIG_BOOT_KERNEL_ADD"
249+
[ -n "$CONFIG_BOOT_KERNEL_REMOVE" ] && STATUS " Board removes: $CONFIG_BOOT_KERNEL_REMOVE"
250+
[ -n "$add" ] && STATUS " ISO params: $add"
251+
local final="$params"
252+
for rem in $CONFIG_BOOT_KERNEL_REMOVE; do
253+
final=" $final "
254+
final="${final// $rem / }"
255+
final="${final# }"
256+
final="${final% }"
257+
done
258+
final="$final $CONFIG_BOOT_KERNEL_ADD $add"
259+
STATUS " Kernel cmdline: $(echo "$final" | xargs)"
260+
INPUT "Boot (Y), make default (d), back to menu (b) [Y/d/b]:" -n 1 option_confirm
261+
[ -z "$option_confirm" ] && option_confirm="y"
262+
return 0
198263
fi
199264
}
200265

201266
parse_option() {
267+
# Parse pipe-delimited boot entry: name|kexectype|kernel /path|initrd /path|append params
268+
# Field 4 can be either "initrd /path" or "append ..." when no initrd is present.
202269
name=$(echo $option | cut -d\| -f1)
203-
kernel=$(echo $option | cut -d\| -f3)
270+
kernel=$(echo $option | cut -d\| -f3 | sed 's/^kernel //')
271+
initrd=""; params=""
272+
f4=$(echo $option | cut -d\| -f4)
273+
case "$f4" in
274+
initrd*) initrd="${f4#initrd }"; params=$(echo $option | cut -d\| -f5 | sed 's/append //' | xargs) ;;
275+
append*) params=$(echo "$f4" | sed 's/^append //' | xargs) ;;
276+
*) ;;
277+
esac
278+
LOG "parse_option: name='$name' kernel='$kernel' initrd='$initrd' params='${params:0:80}...'"
279+
}
280+
281+
# Return the initrd compat marker for the current entry's initrd.
282+
# Three possible states:
283+
# [OK] — initrd has the USB fs module as .ko or in modules.builtin
284+
# [!] — initrd has loadable modules but none for the USB fs type
285+
# "" — initrd has zero .ko files (can't verify — assume OK)
286+
#
287+
# The $initrd global is set by parse_option() for each menu entry before
288+
# this function is called, so each entry independently looks up its own
289+
# initrd in the compat file.
290+
#
291+
# Compat file format (written by kexec-iso-init.sh Layer 1):
292+
# initrd/relative/path [OK]
293+
# other/initrd/path [!]
294+
# (absent entries = zero modules, can't verify)
295+
#
296+
# Each line maps one initrd to its USB-filesystem compat status.
297+
# boot_marker() greps for the current entry's initrd path and returns
298+
# that initrd's marker — entries using different initrds show different
299+
# markers even on the same ISO.
300+
# In CLI mode adds ANSI colors: green [OK], yellow [!].
301+
boot_marker() {
302+
local m="" grn="" ylw="" rst=""
303+
[ "$gui_menu" != "y" ] && { grn=$'\033[0;32m'; ylw=$'\033[1;33m'; rst=$'\033[0m'; }
304+
if [ -n "$initrd" ] && [ -r "/tmp/kexec_initrd_compat.txt" ]; then
305+
local ip=$(echo "$initrd" | sed 's|^/*||')
306+
m=$(grep "^$ip " /tmp/kexec_initrd_compat.txt 2>/dev/null | head -1 | cut -d' ' -f2)
307+
[ -n "$m" ] && LOG "boot_marker: initrd=$ip marker=$m" || LOG "boot_marker: initrd=$ip no compat entry"
308+
[ "$m" = "[OK]" ] && m="${grn}[OK]${rst}"
309+
[ "$m" = "[!]" ] && m="${ylw}[!]${rst}"
310+
fi
311+
echo "$m"
312+
}
313+
314+
# Strip ISO-finding boot parameters for display only.
315+
# The full params remain in the entry passed to kexec-boot.sh.
316+
# These params are injected by kexec-iso-init.sh via -a and would
317+
# clutter the menu if shown redundantly. Only affects menu display.
318+
fmt_display_params() {
319+
local dp="$1"
320+
[ -z "$dp" ] && echo "" && return
321+
echo "$dp" | sed \
322+
-e 's|iso-scan/filename=[^ ]*| |g' \
323+
-e 's|findiso=[^ ]*| |g' \
324+
-e 's|fromiso=[^ ]*| |g' \
325+
-e 's|img_dev=[^ ]*| |g' \
326+
-e 's|img_loop=[^ ]*| |g' \
327+
-e 's|iso=[^ ]*| |g' \
328+
-e 's|live-media=[^ ]*| |g' \
329+
-e 's| *| |g' \
330+
-e 's|^ ||' \
331+
-e 's| $||' | xargs
332+
}
333+
334+
# Format kernel/initrd for menu display: "[path | path]"
335+
# Keeps directory context for short paths (live/vmlinuz) but falls back to
336+
# basename for unreasonably long store paths (NixOS /nix/store/.../bzImage).
337+
# 35-char threshold: typical paths like "boot/x86_64/loader/linux" fit;
338+
# NixOS store paths with hashes exceed it.
339+
fmt_boot_target() {
340+
local k i
341+
k=$(echo "$kernel" | sed 's|^/*||')
342+
[ -z "$k" ] && k="$kernel"
343+
[ "${#k}" -gt 35 ] && k=$(basename "$k")
344+
i=$(echo "$initrd" | sed 's|^/*||')
345+
[ "${#i}" -gt 35 ] && i=$(basename "$i")
346+
if [ -n "$i" ]; then echo "[$k | $i]"; else echo "[$k]"; fi
204347
}
205348

206349
scan_options() {
207-
STATUS "Scanning for boot options"
350+
STATUS "Scanning for unsigned boot options"
208351
option_file="/tmp/kexec_options.txt"
209352
scan_boot_options "$bootdir" "$config" "$option_file"
210353
if [ ! -s $option_file ]; then
211354
DIE "Failed to parse any boot options"
212355
fi
213-
if [ "$unique" = 'y' ]; then
214-
sort -r $option_file | uniq >$TMP_MENU_FILE
215-
else
216-
cp $option_file $TMP_MENU_FILE
217-
fi
356+
# Sort entries by name so users can scan the menu alphabetically.
357+
# When -u (unique) is set, strip --- markers from append params first
358+
# so entries differing only by GRUB's bootloader separator get deduped.
359+
if [ "$unique" = 'y' ]; then
360+
sed 's/|append \([^|]*\)---[^|]*/|append \1/g' "$option_file" | awk -F'|' '!seen[$1]++' >"$TMP_MENU_FILE"
361+
else
362+
cp "$option_file" "$TMP_MENU_FILE"
363+
fi
364+
DEBUG "kexec-select-boot: parsed boot options for user selection"
365+
# Option entries are already logged as echo_entry by kexec-parse-boot.sh;
366+
# no need to dump them again here.
218367
}
219368

220369
save_default_option() {

0 commit comments

Comments
 (0)