-
Notifications
You must be signed in to change notification settings - Fork 179
[disk-imaging] Set bootorder with efibootmgr #4290
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,6 +31,7 @@ image_auth_token= | |
| distro_name= | ||
| distro_version= | ||
| distro_release= | ||
| installed_os_label= | ||
| serial_port= | ||
| serial_port_num= | ||
| log_output= | ||
|
|
@@ -164,6 +165,24 @@ function expand_root_fs() { | |
| # not handling lvm resize currently | ||
| } | ||
|
|
||
| function resolve_esp_partition() { | ||
| # Find the ESP by its GPT partition type code (EF00), reading the GPT | ||
| # directly via sgdisk rather than relying on blkid's label cache, which | ||
| # can be stale right after a raw qemu-img write onto a previously-used | ||
| # (reprovisioned) disk. | ||
| disk=$1 | ||
| part_num=$(sgdisk -p "$disk" 2>/dev/null | awk '{for (i=1;i<=NF;i++) if ($i=="EF00") {print $1; exit}}') | ||
| if [ -z "$part_num" ]; then | ||
| return 1 | ||
| fi | ||
| is_nvme=$(echo "$disk" | grep nvme) | ||
| if [ ! -z "$is_nvme" ]; then | ||
| echo "$disk"p"$part_num" | ||
| else | ||
| echo "$disk""$part_num" | ||
| fi | ||
| } | ||
|
|
||
| function get_root_dev() { | ||
| if [ ! -z "$rootfs_uuid" ]; then | ||
| root_dev=$(blkid -U $rootfs_uuid) | ||
|
|
@@ -176,6 +195,10 @@ function get_root_dev() { | |
| if [ ! -z "$efi_label" ]; then | ||
| efi_dev=$(blkid -L $efi_label) | ||
| fi | ||
| if [ -z "$efi_dev" ] && [ ! -z "$image_disk" ]; then | ||
| echo "EFI partition not found by label [$efi_label]; falling back to GPT partition type EF00 (EFI System Partition) on $image_disk" | tee $log_output | ||
| efi_dev=$(resolve_esp_partition "$image_disk") | ||
| fi | ||
| } | ||
|
|
||
| function is_port_in_list() { | ||
|
|
@@ -305,6 +328,8 @@ function modify_grub_cfg() { | |
| else | ||
| chroot /mnt /bin/sh -c update-grub 2>&1 | tee $log_output | ||
| fi | ||
| create_efi_boot_entry | ||
| set_boot_order | ||
| umount /mnt/boot/efi 2>&1 | tee $log_output | ||
| umount /mnt/sys | ||
| umount /mnt/proc | ||
|
|
@@ -314,11 +339,183 @@ function modify_grub_cfg() { | |
| fi | ||
| } | ||
|
|
||
| function get_part_num() { | ||
| dev=$1 | ||
| is_nvme=$(echo $dev | grep nvme) | ||
| if [ ! -z "$is_nvme" ]; then | ||
| echo $dev | sed -E 's/.*p([0-9]+)$/\1/' | ||
| else | ||
| echo $dev | sed -E 's/.*[^0-9]([0-9]+)$/\1/' | ||
| fi | ||
| } | ||
|
|
||
| function create_efi_boot_entry() { | ||
| if ! command -v efibootmgr >/dev/null 2>&1; then | ||
| echo "efibootmgr not available, skipping EFI boot entry creation" | tee $log_output | ||
| return 0 | ||
| fi | ||
| if [ -z "$efi_dev" ]; then | ||
| echo "EFI device not resolved, skipping EFI boot entry creation" | tee $log_output | ||
| return 0 | ||
| fi | ||
|
|
||
| shim_arch= | ||
| efi_arch=$(uname -m) | ||
| if [ "$efi_arch" == "x86_64" ]; then | ||
| shim_arch="x64" | ||
| elif [ "$efi_arch" == "aarch64" ]; then | ||
| shim_arch="aa64" | ||
| else | ||
| echo "Unsupported arch $efi_arch for EFI boot entry creation" | tee $log_output | ||
| return 0 | ||
| fi | ||
|
|
||
| # Discover the installed bootloader directly from the ESP rather than | ||
| # assuming a path derived from distro_name, which is not always set | ||
| # (e.g. when the image is provisioned via a raw image_url instead of | ||
| # image_distro_name/image_distro_version on the kernel cmdline). | ||
| shim_path=$(find /mnt/boot/efi/EFI -mindepth 2 -maxdepth 2 -iname "shim${shim_arch}.efi" -print -quit 2>/dev/null) | ||
| if [ -z "$shim_path" ]; then | ||
| echo "No shim${shim_arch}.efi found under /mnt/boot/efi/EFI/*, skipping EFI boot entry creation" | tee $log_output | ||
| return 0 | ||
| fi | ||
|
|
||
| efi_dir=$(dirname "$shim_path") | ||
| distro_dir=$(basename "$efi_dir") | ||
| loader_rel="/EFI/$distro_dir/$(basename "$shim_path")" | ||
| loader_path=$(echo "$loader_rel" | sed 's#/#\\#g') | ||
|
|
||
| # Prefer the label recorded in the shim's own .csv hint file (the | ||
| # authoritative source per the UEFI shim convention: <loader>,<label>, | ||
| # <optional args>,<description>, UTF-16 encoded), falling back to the | ||
| # ESP directory name. | ||
| label= | ||
| csv_file=$(find "$efi_dir" -maxdepth 1 -iname "*.csv" -print -quit 2>/dev/null) | ||
| if [ ! -z "$csv_file" ]; then | ||
| label=$(iconv -f UTF-16 -t UTF-8 "$csv_file" 2>/dev/null | tr -d '\r' | head -n1 | cut -d',' -f2) | ||
| fi | ||
| if [ -z "$label" ]; then | ||
| label="$distro_dir" | ||
| fi | ||
| # Shared with set_boot_order() so it can prioritize this entry even | ||
| # when distro_name wasn't provided on the kernel cmdline. | ||
| installed_os_label="$label" | ||
|
|
||
| esp_part_num=$(get_part_num "$efi_dev") | ||
| if [ -z "$esp_part_num" ]; then | ||
| echo "Could not determine ESP partition number for $efi_dev, skipping EFI boot entry creation" | tee $log_output | ||
| return 0 | ||
| fi | ||
|
|
||
| # The partition's GPT UUID lets us distinguish "an Ubuntu entry on THIS | ||
| # disk's ESP" from "an Ubuntu entry with the same relative loader path on | ||
| # some OTHER disk" -- e.g. a leftover boot entry from a previous OS | ||
| # install on a different NVMe device. The loader path alone | ||
| # ("\EFI\ubuntu\shimaa64.efi") is identical regardless of which physical | ||
| # disk/partition it lives on, since it's only meaningful relative to | ||
| # whichever ESP the entry's HD(...) device-path node actually points at. | ||
| esp_part_uuid=$(blkid -s PARTUUID -o value "$efi_dev" 2>/dev/null | tr '[:upper:]' '[:lower:]') | ||
|
|
||
| # Require the label, the loader path, AND (when known) the target ESP's | ||
| # partition UUID to all match an existing entry. Each check guards | ||
| # against a different false match: | ||
| # - Label-only, end-anchored (the original approach) breaks because | ||
| # efibootmgr's plain (non -v) output on some firmware/builds still | ||
| # appends the full UEFI device path after the label with no | ||
| # unambiguous delimiter, so anchoring the label at end-of-line never | ||
| # matches and a duplicate same-labeled entry gets created every run. | ||
| # - Loader-path-only breaks the opposite way: during a reinstall (e.g. | ||
| # replacing DGX OS with Ubuntu) a pre-existing, differently-labeled | ||
| # entry (like a firmware-native "DGX OS" entry) can reference the same | ||
| # shim file path, which would wrongly be treated as "Ubuntu already | ||
| # exists" and skip creating the real entry. | ||
| # - Label + loader path without the partition UUID breaks on multi-disk | ||
| # systems: a same-named "Ubuntu" entry left over on a different disk's | ||
| # ESP would falsely count as a duplicate of the one we're about to | ||
| # create on this disk. | ||
| # Anchoring the label at the *start* (right after the "Boot####[*] " | ||
| # prefix) instead of the end avoids the first problem -- trailing device | ||
| # path text is now expected, not fatal -- while still requiring an exact | ||
| # label match avoids the second. Labels are compared via a quoted bash | ||
| # pattern (not grep/regex) so metacharacters in the label can't be | ||
| # misinterpreted. | ||
| label_lower=$(printf '%s' "$label" | tr '[:upper:]' '[:lower:]') | ||
| loader_path_lower=$(printf '%s' "$loader_path" | tr '[:upper:]' '[:lower:]') | ||
| duplicate_found=0 | ||
| while IFS= read -r bootline; do | ||
| [ -z "$bootline" ] && continue | ||
| remainder=$(printf '%s' "$bootline" | sed -E 's/^Boot[0-9A-Fa-f]{4}\*?[[:space:]]*//') | ||
| remainder_lower=$(printf '%s' "$remainder" | tr '[:upper:]' '[:lower:]') | ||
| bootline_lower=$(printf '%s' "$bootline" | tr '[:upper:]' '[:lower:]') | ||
| if [[ "$remainder_lower" == "$label_lower"* ]] \ | ||
| && printf '%s' "$bootline_lower" | grep -qF "$loader_path_lower" \ | ||
| && { [ -z "$esp_part_uuid" ] || printf '%s' "$bootline_lower" | grep -qF "$esp_part_uuid"; }; then | ||
| duplicate_found=1 | ||
| break | ||
| fi | ||
| done < <(efibootmgr | grep -E "^Boot[0-9A-Fa-f]{4}") | ||
|
|
||
| if [ "$duplicate_found" -eq 1 ]; then | ||
| echo "EFI boot entry for $label already exists on this disk's ESP (loader $loader_path found), skipping creation" | tee $log_output | ||
| return 0 | ||
| fi | ||
|
|
||
| echo "Creating EFI boot entry for $label ($loader_path on $image_disk part $esp_part_num)" | tee $log_output | ||
| efibootmgr --create --disk "$image_disk" --part "$esp_part_num" --label "$label" --loader "$loader_path" 2>&1 | tee $log_output | ||
|
Comment on lines
+463
to
+464
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift Preserve failures from Without
🧰 Tools🪛 Shellcheck (0.11.0)[info] 415-415: Double quote to prevent globbing and word splitting. (SC2086) [info] 416-416: Double quote to prevent globbing and word splitting. (SC2086) 📍 Affects 1 file
🤖 Prompt for AI AgentsSource: Path instructions |
||
| } | ||
|
|
||
| function set_boot_order() { | ||
| if ! command -v efibootmgr >/dev/null 2>&1; then | ||
| return 0 | ||
| fi | ||
|
|
||
| efi_list=$(efibootmgr) | ||
| current_order_line=$(echo "$efi_list" | grep '^BootOrder:') | ||
| if [ -z "$current_order_line" ]; then | ||
| echo "Could not read current BootOrder, skipping reorder" | tee $log_output | ||
| return 0 | ||
| fi | ||
| current_order_csv=$(echo "$current_order_line" | sed -E 's/^BootOrder:[[:space:]]*//') | ||
| IFS=',' read -r -a current_order <<< "$current_order_csv" | ||
|
|
||
| # Prefer the label create_efi_boot_entry() actually discovered on the | ||
| # ESP; distro_name alone can be empty (e.g. raw image_url provisioning), | ||
| # which would otherwise match every entry as "distro". | ||
| match_name="${installed_os_label:-$distro_name}" | ||
|
|
||
| network=() | ||
| distro=() | ||
| rest=() | ||
| for bootnum in "${current_order[@]}"; do | ||
| entry_label=$(echo "$efi_list" | grep "^Boot$bootnum" | sed -E 's/^Boot[0-9A-Fa-f]{4}\*?[[:space:]]*//') | ||
|
stoo-davies marked this conversation as resolved.
|
||
| lower_label=$(echo "$entry_label" | tr '[:upper:]' '[:lower:]') | ||
| if [[ "$lower_label" == *pxe* || "$lower_label" == *http* ]]; then | ||
| network+=("$bootnum") | ||
| elif [ -n "$match_name" ] && printf '%s' "$lower_label" | grep -qF "$(printf '%s' "$match_name" | tr '[:upper:]' '[:lower:]')"; then | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is matching just based on the label, not the actual disk the image was written to. In the case where the script creates the boot entry, efibootmgr puts that at the beginning of the list, so the script finds the order is correct and doesn't change anything, that's OK. However, if the boot entry already existed, due to a re-install of the same qcow where the PARTUUID already exists, and there is another boot entry with the same label, then the boot order may not be changed to be correct. I'm re-installing the image which has the PARTUUID starting with b49f.... Maybe it would be better to just clear all the existing entries with our label?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see this is the same as the first coderabbit point above. |
||
| distro+=("$bootnum") | ||
| else | ||
| rest+=("$bootnum") | ||
| fi | ||
| done | ||
|
|
||
| new_order=("${network[@]}" "${distro[@]}" "${rest[@]}") | ||
| new_order_csv=$(IFS=,; echo "${new_order[*]}") | ||
|
|
||
| if [ "$new_order_csv" == "$current_order_csv" ]; then | ||
| echo "Boot order already network-first then $match_name, no change needed" | tee $log_output | ||
| return 0 | ||
| fi | ||
|
|
||
| echo "Setting boot order to: $new_order_csv" | tee $log_output | ||
| efibootmgr -o "$new_order_csv" 2>&1 | tee $log_output | ||
| } | ||
|
|
||
| function mount_efi() { | ||
| if [ ! -z "$efifs_uuid" ]; then | ||
| efi_dev=$(blkid -U $efifs_uuid) | ||
| fi | ||
| if [ ! -z "$efi_dev" ]; then | ||
| mkdir -p /mnt/boot/efi | ||
| mount $efi_dev /mnt/boot/efi 2>&1 | tee $log_output | ||
| else | ||
| chroot /mnt /bin/sh -c 'mount /boot/efi' 2>&1 | tee $log_output | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
Repository: NVIDIA/infra-controller
Length of output: 50382
🏁 Script executed:
Repository: NVIDIA/infra-controller
Length of output: 12205
🌐 Web query:
efibootmgr README -v verbose output Boot#### GPT PARTUUID file path BootOrder💡 Result:
efibootmgr is a command-line utility used to manage the UEFI Boot Manager configuration [1][2]. Below is a breakdown of the terms and output components you requested. Boot#### These are persistent UEFI variables stored in the system firmware, where #### represents a unique 4-digit hexadecimal identifier (e.g., Boot0000, Boot0001) [1]. Each Boot#### variable defines a specific boot option, such as a disk, network boot, or removable drive, and indicates whether that method is currently active (marked with an asterisk *) [1][2]. BootOrder BootOrder is a global UEFI variable containing a comma-delimited list of Boot#### identifiers [1][2]. It dictates the sequence in which the UEFI firmware attempts to boot the system [2][3]. The firmware will attempt to boot the first active entry in this list; if it fails, it proceeds to the next [2][3]. Verbose Output (-v) The --verbose or -v option provides additional details for each boot entry, which are otherwise hidden [1][4]. In verbose mode, efibootmgr displays the hardware device path (such as ACPI, PCI, and HD mappings) and the specific EFI file path of the boot loader (e.g., \EFI\fedora\grub.efi) [5][6][7]. GPT PARTUUID and File Path When using -v, entries on GPT-partitioned disks are often displayed with an HD component [5][8]. This component typically includes: - Partition number - Partition type (e.g., GPT) - The unique PARTUUID (or GUID) of that specific partition - Disk offsets - The file path to the EFI executable on that partition (e.g., /File(\EFI...\loader.efi)) [5][6][8]. This verbose information is essential for troubleshooting or verifying exactly which partition and loader file a particular boot entry is referencing [5][8].
Citations:
🏁 Script executed:
Repository: NVIDIA/infra-controller
Length of output: 3626
🌐 Web query:
efibootmgr --create -c BootOrder position new entry added first source documentation💡 Result:
When using the efibootmgr command to create a new boot entry, the default behavior is to automatically append the new entry to the first position (the start) of the BootOrder list [1][2][3]. There is no native flag or option within the efibootmgr command to specify a custom index or position (such as inserting the entry at the second or third spot) during the creation process [4]. If you need to place a new boot entry at a position other than the first, or if you need to precisely control the order after creation, you must perform the following steps: 1. Create the boot entry: Use efibootmgr -c (or --create) to generate the entry. Because this automatically adds the new number to the front of the BootOrder, you must then adjust the order to your preference [1][3]. 2. Identify the new BootNum: The output of the creation command or a subsequent run of efibootmgr will reveal the hexadecimal boot number (e.g., 0005) assigned to the new entry [5]. 3. Reorder the BootOrder: Use the -o (or --bootorder) flag followed by a comma-separated list of all desired boot numbers in your preferred sequence [6][1][5]. Example of reordering: If you want to set the boot order such that your new entry (e.g., 0005) is in the second position, you would run: efibootmgr -o 0001,0005,0002,0003 Because efibootmgr does not support partial reordering, the -o flag requires you to provide the complete list of boot numbers you wish to retain in the system's active boot sequence [5][7]. If you omit an existing boot number from the -o list, that entry will be removed from the active boot order [8].
Citations:
Identify the target
Boot####before changingBootOrder.efibootmgris called without-v, so the loader path and ESP PARTUUID are not reliably available for duplicate detection. Reprovisioning can create duplicate entries.set_boot_order()also groups every entry whose text matchesmatch_name, so a same-label entry on another ESP can remain ahead of the target. Parseefibootmgr -v, match the exact label, loader path, and ESP PARTUUID, pass the selectedBoot####toset_boot_order(), and move only that identifier after network entries. Replace the current"$label_lower"*prefix match with an exact label match.🤖 Prompt for AI Agents