BCF-6655: Default GRUB boot entry is changed after BMR#86
BCF-6655: Default GRUB boot entry is changed after BMR#86Andrus Suvalau (svlv) wants to merge 1 commit intodevelopfrom
Conversation
Jira-Ref: BCF-6655: Default GRUB boot entry is changed after BMR
There was a problem hiding this comment.
Pull request overview
This PR aims to preserve the GRUB2 environment block (grubenv) across bare-metal recovery (BMR) so that the default GRUB boot entry doesn’t change after recovery.
Changes:
- Add helper functions to detect
grub-editenv/grub2-editenvand to detect whether GRUB2 was used. - Save
grubenvvalues duringlayout/saveinto$VAR_DIR/recovery/grubenv. - Restore those values during
finalizeby re-creating/updating the target system’s GRUB environment block.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 8 comments.
| File | Description |
|---|---|
usr/share/rear/lib/bootloader-functions.sh |
Adds get_grub_editenv() and is_grub2_used() helpers used by save/restore scripts. |
usr/share/rear/layout/save/default/700_save_grubenv.sh |
New save-stage script intended to persist grubenv into recovery metadata. |
usr/share/rear/finalize/Linux-i386/665_set_grubenv.sh |
New finalize-stage script intended to restore saved grubenv into the recovered system. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| grub_editenv=$(chroot "$TARGET_FS_ROOT" /bin/bash -c "$cmd") | ||
| else | ||
| grub_editenv=$($cmd) |
There was a problem hiding this comment.
The chrooted type -P ... call relies on PATH inside the target system, but no PATH is set here. Elsewhere in the codebase chrooted type -P calls prepend PATH=/sbin:/usr/sbin:/usr/bin:/bin to avoid missing binaries. Consider doing the same here (or use run_in_chroot and set PATH in the command) so get_grub_editenv works reliably during recovery.
| grub_editenv=$(chroot "$TARGET_FS_ROOT" /bin/bash -c "$cmd") | |
| else | |
| grub_editenv=$($cmd) | |
| grub_editenv=$(chroot "$TARGET_FS_ROOT" /bin/bash -c "PATH=/sbin:/usr/sbin:/usr/bin:/bin:\$PATH; $cmd") | |
| else | |
| grub_editenv=$(PATH=/sbin:/usr/sbin:/usr/bin:/bin:$PATH $cmd) |
| used_bootloader="$( cat "$VAR_DIR/recovery/bootloader" )" | ||
|
|
||
| case $used_bootloader in |
There was a problem hiding this comment.
is_grub2_used compares the whole contents of $VAR_DIR/recovery/bootloader against GRUB2|GRUB2-EFI. Other scripts read the first non-comment token (e.g., via an array or grep -v), which is more robust if the file contains trailing whitespace/newlines or comments. Consider normalizing to the first word / first non-comment line before the case match.
| used_bootloader="$( cat "$VAR_DIR/recovery/bootloader" )" | |
| case $used_bootloader in | |
| if [[ -r "$VAR_DIR/recovery/bootloader" ]]; then | |
| # Read the first non-comment, non-empty line and take its first token | |
| used_bootloader=$( | |
| grep -v '^[[:space:]]*#' "$VAR_DIR/recovery/bootloader" | \ | |
| awk 'NF { print $1; exit }' | |
| ) | |
| fi | |
| if [[ -z "$used_bootloader" ]]; then | |
| return 1 | |
| fi | |
| case "$used_bootloader" in |
| "$grub_editenv" - list > "$grubenv_file" | ||
| exit_code=$? | ||
|
|
||
| if exit_code -ne 0; then |
There was a problem hiding this comment.
The grub-editenv invocation looks incorrect: "$grub_editenv" - list passes - as a positional argument. grub-editenv expects list as the command (and optionally a FILE); - will be treated as a filename and can fail or read/write the wrong place. Call "$grub_editenv" list (or pass the explicit grubenv file path you intend to read).
| "$grub_editenv" - list > "$grubenv_file" | |
| exit_code=$? | |
| if exit_code -ne 0; then | |
| "$grub_editenv" list > "$grubenv_file" | |
| exit_code=$? | |
| if [ "$exit_code" -ne 0 ]; then |
| "$grub_editenv" - list > "$grubenv_file" | ||
| exit_code=$? | ||
|
|
||
| if exit_code -ne 0; then |
There was a problem hiding this comment.
This condition is a syntax error (if exit_code -ne 0; then). It should reference the variable and use a test expression (e.g., [ $exit_code -ne 0 ] or (( exit_code != 0 ))). As written, the script will fail to parse when sourced.
| if exit_code -ne 0; then | |
| if [ "$exit_code" -ne 0 ]; then |
|
|
||
| local grubenv_file="$VAR_DIR/recovery/grubenv" | ||
| if [ ! -f "$grubenv_file" ]; then | ||
| LogPrintError "Failed to set grubenv: '$VAR_DIR/recovery/grubenv' does not exist." |
There was a problem hiding this comment.
If $grubenv_file is missing you only log an error but continue; the subsequent input redirection done < "$grubenv_file" will fail and exit_code will remain 0, so the script can report success. Return non-zero (or set exit_code=1 and return) immediately when the file does not exist.
| LogPrintError "Failed to set grubenv: '$VAR_DIR/recovery/grubenv' does not exist." | |
| LogPrintError "Failed to set grubenv: '$VAR_DIR/recovery/grubenv' does not exist." | |
| return 1 |
| for restored_grubenv in /boot/grub2/grubenv /boot/grub/grubenv; do | ||
| if [ -f "$restored_grubenv" ]; then | ||
| rm "$restored_grubenv" | ||
| Log "'$restored_grubenv' was removed" | ||
| fi | ||
| done |
There was a problem hiding this comment.
These paths (/boot/grub2/grubenv and /boot/grub/grubenv) are checked/removed in the recovery environment, not in the restored target system. To remove grubenv from the restored filesystem, prefix with $TARGET_FS_ROOT (or run the removal via run_in_chroot). As written, it may delete the wrong file and leave the target grubenv untouched.
| # It is essential to set up the environment block in the reserved btrfs sector | ||
| # See https://en.opensuse.org/GRUB#GRUB2_on_btrfs_/boot for more details | ||
| chroot "$TARGET_FS_ROOT" /bin/bash -c "\"$grub_editenv\" - unset dummy" | ||
|
|
||
| local exit_code=0 | ||
| local var_value | ||
| while IFS= read -r var_value; do | ||
| local var="${var_value%=*}" | ||
| # env_block is read only | ||
| if [ "$var" = "env_block" ] ; then | ||
| continue | ||
| fi | ||
| if ! chroot "$TARGET_FS_ROOT" /bin/bash -c "\"$grub_editenv\" - set \"$var_value\""; then | ||
| LogPrintError "Failed to set '$var_value' to grubenv" |
There was a problem hiding this comment.
The chroot ... bash -c "\"$grub_editenv\" - unset dummy" / ... - set ... commands have fragile quoting and again pass - as an argument. Using run_in_chroot with a straightforward command string (and calling grub-editenv with the correct syntax and explicit grubenv file if needed) will be more reliable and easier to maintain.
| if test "$RECOVERY_MODE"; then | ||
| grub_editenv=$(chroot "$TARGET_FS_ROOT" /bin/bash -c "$cmd") | ||
| else | ||
| grub_editenv=$($cmd) |
There was a problem hiding this comment.
In non-recovery mode this runs grub_editenv=$($cmd) where cmd contains shell operators (||). Bash won’t re-parse || from an expanded variable, so this ends up calling type with literal arguments like || type -P ..., which can yield incorrect output. Use bash -c "$cmd" (or eval for this fixed string) consistently in both branches.
| grub_editenv=$($cmd) | |
| grub_editenv=$(bash -c "$cmd") |
Save and recover grubenv
BCF-6655: Default GRUB boot entry is changed after BMR