Skip to content

Commit 3d1ede6

Browse files
committed
kexec-iso-init: improve hybrid ISO detection and boot param handling
- Add check_hybrid_iso() using MBR signature at offset 510 (0x55AA) - Add detect_iso_boot_method() to extract boot params from initrd via strings - Add inspect_iso_boot_config() to extract boot params from GRUB configs - Simplify header to document Dracut vs Anaconda boot methods - Use DEBUG level for NOTE/WARN/STATUS spam per logging.md - Change terminal prompts to [Y,d] style with Enter defaulting to yes - Remove Anaconda blocking - let user attempt boot (Qubes R4.3 works) - Keep combined boot params approach (let ISO initrd pick what it needs) Tested with Qubes R4.3 on Q35 QEMU (works). Ref: #2083, #2008
1 parent 8f78967 commit 3d1ede6

2 files changed

Lines changed: 183 additions & 13 deletions

File tree

initrd/bin/kexec-iso-init.sh

Lines changed: 177 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
#!/bin/bash
2-
# Boot from signed ISO
2+
# Boot ISO file from USB media (ext4/fat/exfat USB stick)
3+
#
4+
# References:
5+
# - https://wiki.archlinux.org/title/ISO_Spring_(%27Loop%27_device)
6+
# - https://a1ive.github.io/grub2_loopback.html
7+
#
8+
# Boot Methods (detected via initrd strings analysis):
9+
# - Dracut-based: iso-scan/filename=, findiso=, live-media=, boot=casper
10+
# Works: Ubuntu, Debian Live, Tails, NixOS, Fedora Workstation Live, PureOS
11+
# - Anaconda-based: inst.stage2=hd:LABEL=, inst.repo=hd:LABEL=
12+
# Requires block device (CD-ROM or dd'd USB) - CANNOT boot from ISO file
13+
# Examples: Fedora Silverblue, Fedora Server, Qubes OS, Kicksecure
14+
#
15+
# Anaconda ISOs require: dd if=image.iso of=/dev/sdX or distribution media tool.
16+
# See: https://github.com/linuxboot/heads/issues/2008
317
set -e -o pipefail
418
. /etc/functions.sh
519
. /etc/gui_functions.sh
@@ -22,8 +36,8 @@ ISO_PATH="${ISO_PATH##/}"
2236

2337
if [ -r "$ISOSIG" ]; then
2438
# Signature found, verify it
25-
gpgv.sh --homedir=/etc/distro/ "$ISOSIG" "$MOUNTED_ISO_PATH" \
26-
|| DIE 'ISO signature failed'
39+
gpgv.sh --homedir=/etc/distro/ "$ISOSIG" "$MOUNTED_ISO_PATH" ||
40+
DIE 'ISO signature failed'
2741
STATUS_OK "ISO signature verified"
2842
else
2943
# No signature found, prompt user with warning
@@ -46,11 +60,166 @@ else
4660
NOTE "Proceeding with unsigned ISO boot"
4761
fi
4862

63+
check_hybrid_iso() {
64+
local iso_path="$1"
65+
local mbr_sig=$(dd if="$iso_path" bs=2 skip=255 count=2 2>/dev/null | xxd -p)
66+
if [ "$mbr_sig" = "55aa" ]; then
67+
local efi_magic=$(dd if="$iso_path" bs=1 skip=135 count=8 2>/dev/null | xxd -p)
68+
if [ -n "$efi_magic" ]; then
69+
echo "hybrid"
70+
else
71+
echo "cdrom"
72+
fi
73+
else
74+
echo "cdrom"
75+
fi
76+
}
77+
78+
STATUS "Checking ISO boot capability..."
79+
ISO_BOOT_TYPE=$(check_hybrid_iso "$MOUNTED_ISO_PATH")
80+
DEBUG "ISO boot type: $ISO_BOOT_TYPE"
81+
82+
if [ "$ISO_BOOT_TYPE" != "hybrid" ]; then
83+
DEBUG "Non-hybrid ISO detected (CD-ROM only)"
84+
fi
85+
4986
STATUS "Mounting ISO and booting"
50-
mount -t iso9660 -o loop $MOUNTED_ISO_PATH /boot \
51-
|| DIE '$MOUNTED_ISO_PATH: Unable to mount /boot'
87+
mount -t iso9660 -o loop $MOUNTED_ISO_PATH /boot ||
88+
DIE '$MOUNTED_ISO_PATH: Unable to mount /boot'
89+
90+
detect_iso_boot_method() {
91+
local method=""
92+
local found=0
93+
94+
for path in $(find /boot -name 'initrd*' -type f 2>/dev/null | head -5); do
95+
[ -r "$path" ] || continue
96+
tmpdir=$(mktemp -d)
97+
/bin/bash /bin/unpack_initramfs.sh "$path" "$tmpdir" 2>/dev/null
98+
99+
if find "$tmpdir" -type f 2>/dev/null | xargs strings 2>/dev/null | grep -qE "iso.scan|findiso"; then
100+
method="${method}iso-scan/findiso "
101+
found=1
102+
fi
103+
if find "$tmpdir" -type f 2>/dev/null | xargs strings 2>/dev/null | grep -qE "live.media|live-media"; then
104+
method="${method}live-media= "
105+
found=1
106+
fi
107+
if find "$tmpdir" -type f 2>/dev/null | xargs strings 2>/dev/null | grep -qE "inst.stage2|inst\.stage2"; then
108+
method="${method}inst.stage2= "
109+
found=1
110+
fi
111+
if find "$tmpdir" -type f 2>/dev/null | xargs strings 2>/dev/null | grep -qE "inst.repo"; then
112+
method="${method}inst.repo= "
113+
found=1
114+
fi
115+
if find "$tmpdir" -type f 2>/dev/null | xargs strings 2>/dev/null | grep -qE "boot.casper|live-boot|casper"; then
116+
method="${method}boot=casper "
117+
found=1
118+
fi
119+
if find "$tmpdir" -type f 2>/dev/null | xargs strings 2>/dev/null | grep -qE "nixos"; then
120+
method="${method}nixos "
121+
found=1
122+
fi
123+
rm -rf "$tmpdir"
124+
done
125+
126+
if [ $found -eq 0 ]; then
127+
return 1
128+
fi
129+
echo "$method"
130+
return 0
131+
}
132+
133+
inspect_iso_boot_config() {
134+
local grub_cfg="$1"
135+
local boot_params=""
136+
137+
[ -f "$grub_cfg" ] || return 1
138+
139+
while IFS= read -r line; do
140+
case "$line" in
141+
*inst.stage2=*)
142+
params="${line##*inst.stage2=}"
143+
params="${params%% *}"
144+
[ -n "$params" ] && boot_params="${boot_params} inst.stage2=${params}"
145+
;;
146+
*inst.repo=*)
147+
params="${line##*inst.repo=}"
148+
params="${params%% *}"
149+
[ -n "$params" ] && boot_params="${boot_params} inst.repo=${params}"
150+
;;
151+
*live-media=*)
152+
params="${line##*live-media=}"
153+
params="${params%% *}"
154+
[ -n "$params" ] && boot_params="${boot_params} live-media=${params}"
155+
;;
156+
*iso-scan/filename=* | *findiso=*)
157+
params="${line##*iso-scan/filename=}"
158+
[ "$params" = "$line" ] && params="${line##*findiso=}"
159+
params="${params%% *}"
160+
[ -n "$params" ] && boot_params="${boot_params} iso-scan/filename=${params}"
161+
;;
162+
*boot=casper*)
163+
boot_params="${boot_params} boot=casper"
164+
;;
165+
esac
166+
done <"$grub_cfg"
167+
168+
echo "$boot_params"
169+
return 0
170+
}
171+
172+
STATUS "Detecting ISO boot method..."
173+
BOOT_METHODS=$(detect_iso_boot_method) || BOOT_METHODS=""
174+
EXTRACTED_PARAMS=""
175+
176+
if [ -n "$BOOT_METHODS" ]; then
177+
DEBUG "Detected boot methods: $BOOT_METHODS"
178+
else
179+
DEBUG "No built-in ISO boot support in initrd; checking GRUB config..."
180+
181+
for cfg in $(find /boot -name '*.cfg' -type f 2>/dev/null); do
182+
[ -r "$cfg" ] || continue
183+
if grep -qE "iso.scan|findiso|live.media=|boot=casper" "$cfg" 2>/dev/null; then
184+
BOOT_METHODS="${BOOT_METHODS}grub "
185+
break
186+
fi
187+
if grep -qE "inst.repo=|inst.stage2=" "$cfg" 2>/dev/null; then
188+
BOOT_METHODS="${BOOT_METHODS}anaconda "
189+
fi
190+
done
191+
192+
if [ -n "$BOOT_METHODS" ]; then
193+
DEBUG "Found boot support: $BOOT_METHODS"
194+
else
195+
WARN "ISO may not boot from USB file: no boot support in initrd"
196+
if [ -x /bin/whiptail ]; then
197+
if ! whiptail_warning --title 'ISO BOOT COMPATIBILITY WARNING' --yesno \
198+
"ISO boot from USB file may not work.\n\nThis ISO does not have iso-scan/findiso/live-media in its initrd - it was designed for CD/DVD or dd-to-USB.\n\nKernel parameters passed externally may not be sufficient.\n\nTry:\n- Use distribution-specific ISO (e.g., Debian hd-media)\n- Write ISO directly to USB with dd\n- Use a live USB image\n\nDo you want to proceed anyway?" \
199+
0 80; then
200+
DIE "ISO boot cancelled - unsupported ISO on USB file"
201+
fi
202+
else
203+
INPUT "Proceed with boot anyway? [y/N]:" -n 1 response
204+
[ "$response" != "y" ] && [ "$response" != "Y" ] && DIE "ISO boot cancelled - unsupported ISO on USB file"
205+
fi
206+
fi
207+
fi
208+
209+
if echo "$BOOT_METHODS" | grep -qE "anaconda|inst.repo|inst.stage2"; then
210+
DEBUG "Anaconda-based ISO detected (inst.stage2=)"
211+
fi
212+
213+
if [ -z "$BOOT_METHODS" ]; then
214+
for cfg in $(find /boot -name '*.cfg' -type f 2>/dev/null); do
215+
EXTRACTED_PARAMS=$(inspect_iso_boot_config "$cfg")
216+
[ -n "$EXTRACTED_PARAMS" ] && break
217+
done
218+
DEBUG "Extracted boot params from GRUB: $EXTRACTED_PARAMS"
219+
fi
52220

53-
DEV_UUID=`blkid $DEV | tail -1 | tr " " "\n" | grep UUID | cut -d\" -f2`
221+
# Detect USB stick filesystem and validate initrd supports it
222+
DEV_UUID=$(blkid $DEV | tail -1 | tr " " "\n" | grep UUID | cut -d\" -f2)
54223
ADD="fromiso=/dev/disk/by-uuid/$DEV_UUID/$ISO_PATH img_dev=/dev/disk/by-uuid/$DEV_UUID iso-scan/filename=/${ISO_PATH} img_loop=$ISO_PATH iso=$DEV_UUID/$ISO_PATH"
55224
REMOVE=""
56225

@@ -59,14 +228,14 @@ check_config $paramsdir
59228

60229
ADD_FILE=/tmp/kexec/kexec_iso_add.txt
61230
if [ -r $ADD_FILE ]; then
62-
NEW_ADD=`cat $ADD_FILE`
231+
NEW_ADD=$(cat $ADD_FILE)
63232
ADD=$(eval "echo \"$NEW_ADD\"")
64233
fi
65234
DEBUG "Overriding ISO kernel arguments with additions: $ADD"
66235

67236
REMOVE_FILE=/tmp/kexec/kexec_iso_remove.txt
68237
if [ -r $REMOVE_FILE ]; then
69-
NEW_REMOVE=`cat $REMOVE_FILE`
238+
NEW_REMOVE=$(cat $REMOVE_FILE)
70239
REMOVE=$(eval "echo \"$NEW_REMOVE\"")
71240
fi
72241
DEBUG "Overriding ISO kernel arguments with suppressions: $REMOVE"

initrd/bin/kexec-select-boot.sh

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,8 @@ confirm_menu_option() {
194194
STATUS "Confirm boot details for $name:"
195195
INFO "$option"
196196

197-
INPUT "Confirm selection by pressing 'y', make default with 'd':" -n 1 option_confirm
197+
INPUT "Confirm selection by pressing 'Y' or 'd' to make default [Y,d]:" -n 1 option_confirm
198+
[ -z "$option_confirm" ] && option_confirm="y"
198199
fi
199200
}
200201

@@ -219,11 +220,11 @@ scan_options() {
219220

220221
save_default_option() {
221222
if [ "$gui_menu" != "y" ]; then
222-
INPUT "Saving a default will modify the disk. Proceed? (Y/n):" -n 1 default_confirm
223+
INPUT "Saving a default will modify the disk. Proceed? [Y/n]:" -n 1 default_confirm
224+
[ -z "$default_confirm" ] && default_confirm="y"
223225
fi
224226

225-
[ "$default_confirm" = "" ] && default_confirm="y"
226-
if [[ "$default_confirm" = "y" || "$default_confirm" = "Y" ]]; then
227+
if [[ "$default_confirm" = [yY] ]]; then
227228
if kexec-save-default.sh \
228229
-b "$bootdir" \
229230
-d "$paramsdev" \
@@ -287,7 +288,7 @@ user_select() {
287288
# No default expected boot parameters, ask user
288289

289290
option_confirm=""
290-
while [ "$option_confirm" != "y" -a "$option_confirm" != "d" ]; do
291+
while [[ "$option_confirm" != [yY] && "$option_confirm" != "d" ]]; do
291292
get_menu_option
292293
# In force boot mode, no need offer the option to set a default, just boot
293294
if [[ "$force_boot" = "y" || "$skip_confirm" = "y" ]]; then

0 commit comments

Comments
 (0)