/
940_grub2_rescue.sh
253 lines (224 loc) · 13.2 KB
/
940_grub2_rescue.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# 940_grub2_rescue.sh
# This file is part of Relax-and-Recover, licensed under the GNU General
# Public License. Refer to the included COPYING for full text of license.
# Add the rescue kernel and initrd to the local GRUB 2 bootloader.
# With EFI_STUB enabled there will be no Grub entry.
is_true "$EFI_STUB" && return 0
# Only do it when explicitly enabled:
is_true "$GRUB_RESCUE" || return 0
# Only run this script when GRUB 2 is there
# (grub-probe or grub2-probe only exist in GRUB 2)
# in particular do not run this script when GRUB Legacy is used
# (for GRUB Legacy output/default/940_grub_rescue.sh is run):
if [[ ! $( type -p grub-probe ) && ! $( type -p grub2-probe ) ]] ; then
LogPrint "Skipping GRUB_RESCUE setup for GRUB 2 (no GRUB 2 found)."
return
fi
# Now GRUB_RESCUE is explicitly wanted and this script is the right one to set it up.
local grub_rear_menu_entry_name="Relax-and-Recover"
LogPrint "Setting up GRUB_RESCUE: Adding $grub_rear_menu_entry_name rescue system to the local GRUB 2 configuration."
test "unrestricted" = "$GRUB_RESCUE_USER" && LogPrint "Anyone can boot that and replace the current system via 'rear recover'."
# Now error out whenever it cannot setup the GRUB_RESCUE functionality.
# We don't need to do grub(2)-probe all the time
# adding $grub_num to whatever grub binary should do the trick
# e.g. grub${grub_num}-mkimage
local grub_num=""
if [[ $( type -p grub2-probe ) ]]; then
grub_num="2"
fi
# Ensure that kernel and initrd are there:
test -r "$KERNEL_FILE" || Error "Cannot setup GRUB_RESCUE: Cannot read kernel file '$KERNEL_FILE'."
local initrd_file=$TMP_DIR/$REAR_INITRD_FILENAME
test -r $initrd_file || Error "Cannot setup GRUB_RESCUE: Cannot read initrd '$initrd_file'."
# Some commonly needed values:
local boot_dir="/boot"
local boot_kernel_name="rear-kernel"
local boot_initrd_name="rear-$REAR_INITRD_FILENAME"
local boot_kernel_file="$boot_dir/$boot_kernel_name"
local boot_initrd_file="$boot_dir/$boot_initrd_name"
local grub_config_dir="$boot_dir/grub${grub_num}"
# Esure there is sufficient disk space available in /boot for the local Relax-and-Recover rescue system:
function total_filesize {
stat --format '%s' $@ 2>/dev/null | awk 'BEGIN { t=0 } { t+=$1 } END { print t }'
}
# Free space in /boot:
local free_space=$( df -Pkl $boot_dir | awk 'END { print $4 * 1024 }' )
# Used space by an already existing Relax-and-Recover rescue system in /boot:
local already_used_space=$( total_filesize $boot_kernel_file $boot_initrd_file )
# Available space is the free space plus what an already existing Relax-and-Recover rescue system uses
# because an already existing Relax-and-Recover rescue system would be overwritten:
local available_space=$(( free_space + already_used_space ))
# Required space for the new Relax-and-Recover rescue system:
local required_space=$( total_filesize $KERNEL_FILE $initrd_file )
if (( available_space < required_space )) ; then
required_MiB=$(( required_space / 1024 / 1024 ))
available_MiB=$(( available_space / 1024 / 1024 ))
Error "Cannot setup GRUB_RESCUE: Not enough disk space in $boot_dir for $grub_rear_menu_entry_name rescue system. Required: $required_MiB MiB. Available: $available_MiB MiB."
fi
# TODO: @gozora this is quite long comment, maybe it could me moved to documentation?
#
# UEFI "Relax-and-Recover" boot entry motivation:
#
# If UEFI boot is in use, we will not modify grub.cfg, but setup "Relax-and-Recover" entry in UEFI boot menu instead.
# This looks to be simplest and safest approach since finding out what mechanisms were used to boot OS in UEFI mode,
# looks to be near to impossible.
# One could argue that efibootmgr/efivars can tell you, however this entry is not mandatory and OS could be booted
# using default values or startup.nsh.
# Once UEFI loads Grub2 hell breaks loose, as Grub2 can load whatever arbitrary configuration file anywhere on the system
# or configuration file can be even embedded in bootx64.efi (and friends) as file or memdisk.
# Unfortunately there seems to be no reliable way how to track this back.
#
# Once "Relax-and-Recover" entry in UEFI boot menu is created, user can choose to boot it from OS at next boot:
# # efibootmgr
#
# BootCurrent: 0001
# BootOrder: 0000,0001,0002,0003,0004
# Boot0000* EFI DVD/CDROM
# Boot0001* EFI Hard Drive
# Boot0002* EFI Hard Drive 1
# Boot0003* EFI Internal Shell
# Boot0004* Relax-and-Recover
#
# # /usr/sbin/efibootmgr --bootnext 4
# At next boot "Relax-and-Recover" rescue image from /boot will be loaded.
#
# To remove "Relax-and-Recover" UEFI boot entry:
#
# # efibootmgr -B -b 4
#
# "Relax-and-Recover" entry can be of course selected during POST boot as well.
if ! is_true $USING_UEFI_BOOTLOADER ; then
# Ensure a GRUB 2 configuration file is found:
local grub_conf=$( readlink -f $grub_config_dir/grub.cfg )
test -w "$grub_conf" || Error "Cannot setup GRUB_RESCUE: GRUB 2 configuration '$grub_conf' cannot be modified."
# Report no longer supported GRUB 2 superuser setup if GRUB_SUPERUSER is non-empty
# (be prepared for 'set -u' by specifying an empty fallback value if GRUB_SUPERUSER is not set):
test ${GRUB_SUPERUSER:-} && LogPrint "Skipping GRUB 2 superuser setup: GRUB_SUPERUSER is no longer supported (see default.conf)."
# Report no longer supported GRUB 2 password setup if GRUB_RESCUE_PASSWORD is non-empty
# (be prepared for 'set -u' by specifying an empty fallback value if GRUB_RESCUE_PASSWORD is not set):
test ${GRUB_RESCUE_PASSWORD:-} && LogPrint "Skipping GRUB 2 password setup: GRUB_RESCUE_PASSWORD is no longer supported (see default.conf)."
# It is no error when GRUB_SUPERUSER and/or GRUB_RESCUE_PASSWORD are non-empty
# because it should work reasonably backward compatible, see default.conf
# A simple check if a GRUB_RESCUE_USER exists in the usual GRUB 2 users file /etc/grub.d/01_users
# but this check is unreliable because the GRUB 2 users filename could be anything else
# so that it only notifies without interpretation and does not error out if the check fails.
# On the other hand when /etc/grub.d/01_users exists then we might even assume that
# this one is the only GRUB 2 users file and error out if GRUB_RESCUE_USER is not therein?
local supposed_grub_users_file="/etc/grub.d/01_users"
if test -r $supposed_grub_users_file -a "$GRUB_RESCUE_USER" -a "unrestricted" != "$GRUB_RESCUE_USER" ; then
grep -q "$GRUB_RESCUE_USER" $supposed_grub_users_file || LogPrint "GRUB_RESCUE_USER '$GRUB_RESCUE_USER' not found in $supposed_grub_users_file - is that okay?"
fi
fi
# Finding UUID of filesystem containing boot_dir (i.e. /boot)
grub_boot_uuid=$( df $boot_dir | awk 'END {print $1}' | xargs blkid -s UUID -o value )
# Stop if grub_boot_uuid is not a valid UUID
blkid -U $grub_boot_uuid > /dev/null 2>&1 || Error "$grub_boot_uuid is not a valid UUID"
# Creating Relax-and-Recover GRUB 2 menu entry:
local grub_rear_menu_entry_file="/etc/grub.d/45_rear"
local grub_boot_dir=$boot_dir
if mountpoint -q $boot_dir ; then
# When /boot is a mountpoint
# (i.e. a filesystem on a partition /dev/sdaN is mounted at /boot)
# then GRUB uses the filesystem on /dev/sdaN directly
# and in that filesystem there is no such thing as /boot
# so that for GRUB the files are in the root of that filesystem:
grub_boot_dir=""
fi
# Refer to: UEFI "Relax-and-Recover" boot entry motivation, near to beginning of this file ...
if is_true $USING_UEFI_BOOTLOADER ; then
# SLES12 SP1 throw kernel panic if root= variable was not set
# probably a bug, as I was able to boot with value set to root=anything
root_uuid=$(get_root_disk_UUID)
# Create configuration file for "Relax-and-Recover" UEFI boot entry.
# This file will not interact with existing Grub2 configuration in any way.
( echo "menuentry '$grub_rear_menu_entry_name' --class os {"
echo " search --no-floppy --fs-uuid --set=root $grub_boot_uuid"
echo " echo 'Loading kernel $boot_kernel_file ...'"
echo " linux $grub_boot_dir/$boot_kernel_name root=UUID=$root_uuid $KERNEL_CMDLINE"
echo " echo 'Loading initrd $boot_initrd_file (may take a while) ...'"
echo " initrd $grub_boot_dir/$boot_initrd_name"
echo "}"
) > $grub_config_dir/rear.cfg
# Tell rear.efi which configuration file to load
( echo "search --no-floppy --fs-uuid --set=root $grub_boot_uuid"
echo ""
echo "set btrfs_relative_path=y"
echo "set prefix=(\$root)${grub_boot_dir}/grub${grub_num}"
echo ""
echo "configfile (\$root)${grub_boot_dir}/grub${grub_num}/rear.cfg"
) > $grub_config_dir/rear_embed.cfg
# Create rear.efi at UEFI default boot directory location.
build_bootx86_efi $boot_dir/efi/EFI/BOOT/rear.efi $grub_config_dir/rear_embed.cfg
# If UEFI boot entry for "Relax-and-Recover" does not exist, create it.
# This will also add "Relax-and-Recover" to boot order because if UEFI entry is not listed in BootOrder,
# it is not visible in UEFI boot menu.
if [[ $(efibootmgr | grep -cw $grub_rear_menu_entry_name) -eq 0 ]]; then
# This part might not go that well with drivers like HPEs cciss...
# However UEFI booting is present since Gen8 (AFAIK), and cciss drivers were replaced by hpsa long time ago,
# so it looks like impossible configuration, lets wait ...
efi_disk_part=$(grep -w /boot/efi /proc/mounts | awk '{print $1}')
efi_disk=$(echo $efi_disk_part | sed -e 's/[0-9]//g')
efi_part=$(echo $efi_disk_part | sed -e 's/[^0-9]//g')
# Save current BootOrder, as during `efibootmgr -c ...' phase (creating of "Relax-and-Recover" UEFI boot entry),
# newly created entry will be set as primary, which is not something we don't really want
efi_boot_order=$(efibootmgr | grep "BootOrder" | cut -d ":" -f2)
efibootmgr -c -d $efi_disk -p $efi_part -L "$grub_rear_menu_entry_name" -l "\EFI\BOOT\rear.efi" > /dev/null 2>&1
rear_boot_id=$(efibootmgr | grep -w $grub_rear_menu_entry_name | cut -d " " -f1 | sed -e 's/[^0-9]//g')
# Set "Relax-and-Recover" as last entry in UEFI boot menu.
efibootmgr -o ${efi_boot_order},${rear_boot_id} > /dev/null 2>&1
fi
else
# Create a GRUB 2 menu config file:
( echo "#!/bin/bash"
echo "cat << EOF"
) > $grub_rear_menu_entry_file
if test "$GRUB_RESCUE_USER" ; then
if test "unrestricted" = "$GRUB_RESCUE_USER" ; then
echo "menuentry '$grub_rear_menu_entry_name' --class os --unrestricted {" >> $grub_rear_menu_entry_file
else
echo "menuentry '$grub_rear_menu_entry_name' --class os --users $GRUB_RESCUE_USER {" >> $grub_rear_menu_entry_file
fi
else
echo "menuentry '$grub_rear_menu_entry_name' --class os {" >> $grub_rear_menu_entry_file
fi
( echo " search --no-floppy --fs-uuid --set=root $grub_boot_uuid"
echo " echo 'Loading kernel $boot_kernel_file ...'"
echo " linux $grub_boot_dir/$boot_kernel_name $KERNEL_CMDLINE"
echo " echo 'Loading initrd $boot_initrd_file (may take a while) ...'"
echo " initrd $grub_boot_dir/$boot_initrd_name"
echo "}"
echo "EOF"
) >> $grub_rear_menu_entry_file
chmod 755 $grub_rear_menu_entry_file
# Generate a GRUB 2 configuration file:
local generated_grub_conf="$TMP_DIR/grub.cfg"
if [[ $( type -f grub2-mkconfig ) ]] ; then
grub2-mkconfig -o $generated_grub_conf || Error "Failed to generate GRUB 2 configuration file (using grub2-mkconfig)."
else
grub-mkconfig -o $generated_grub_conf || Error "Failed to generate GRUB 2 configuration file (using grub-mkconfig)."
fi
test -s $generated_grub_conf || BugError "Generated empty GRUB 2 configuration file '$generated_grub_conf'."
# Modifying local GRUB 2 configuration if it was actually changed:
if ! diff -u $grub_conf $generated_grub_conf >&2 ; then
LogPrint "Modifying local GRUB 2 configuration."
cp -af $v $grub_conf $grub_conf.old >&2
cat $generated_grub_conf >$grub_conf
fi
fi
# Provide the kernel as boot_kernel_file (i.e. /boot/rear-kernel):
if [[ $( stat -L -c '%d' $KERNEL_FILE ) == $( stat -L -c '%d' $boot_dir/ ) ]] ; then
# Hardlink file, if possible:
cp -pLlf $v $KERNEL_FILE $boot_kernel_file || BugError "Failed to hardlink '$KERNEL_FILE' to '$boot_kernel_file'."
elif [[ $( stat -L -c '%s %Y' $KERNEL_FILE ) == $( stat -L -c '%s %Y' $boot_kernel_file ) ]] ; then
# If an already existing boot_kernel_file has exact same size and modification time
# as the current KERNEL_FILE, assume both are the same and do nothing:
:
else
# In all other cases, replace boot_kernel_file with the current KERNEL_FILE:
cp -pLf $v $KERNEL_FILE $boot_kernel_file || BugError "Failed to copy '$KERNEL_FILE' to '$boot_kernel_file'."
fi
# Provide the rear recovery system in initrd_file (i.e. TMP_DIR/initrd.cgz or TMP_DIR/initrd.xz)
# as boot_initrd_file (i.e. /boot/rear-initrd.cgz or /boot/rear-initrd.xz)
# (regarding '.cgz' versus '.xz' see https://github.com/rear/rear/issues/1142)
cp -af $v $initrd_file $boot_initrd_file || BugError "Failed to copy '$initrd_file' to '$boot_initrd_file'."
LogPrint "Finished GRUB_RESCUE setup: Added '$grub_rear_menu_entry_name' GRUB 2 menu entry."