-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathefibootmirrorsetup
executable file
·464 lines (434 loc) · 15.1 KB
/
efibootmirrorsetup
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
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
#!/bin/sh
# efibootmirrorsetup (part of ossobv/vcutil) // wdoekes/2020-2022
# // Public Domain
#
# Setup helper script to set EFI up on software-raid mirrored disks, and
# keep them in sync. If one disk fails or is removed, you will
# automatically boot from the other disk as if nothing happened.
#
# (The software raid (e.g. zfsonroot) generally handles the mirroring on
# the larger partitions, but not mirror the EFI partition.)
#
# Run this when:
# - you're using a software mirrored disk (with room reserved for EFI)
# - EFI has been set up on at least one of the disks
#
# Supply /dev/disk and /dev/otherdisk.
#
# It will do the following, if it hasn't been done yet:
# - ensure there is a FAT filesystem on the other drive's EFI partition
# - ask if it can update fstab for you (you'll get to see a diff)
# - mount the EFI partitions on /boot/efi and /boot/efi2
# - ask if it may add/update zz-grub-install-efibootmirrorsetup in the
# /etc/kernel/postinst.d/ update hooks
# - run that hook, inserting both partitions into the EFI boot order
#
# NOTE: If the partition IDs are equal on the different drives (maybe you
# cloned the drives), it will prompt you to manually fix that.
#
# The script is idempotent, so running it multiple times should be safe.
#
# When everything has run, you can look at the output of efibootmgr -v. It
# should contain:
#
# BootOrder: 0007,0006,... <-- your two partitions at the front
#
# And then lines, including two of these (the numbers will be different):
#
# Boot0007* <DISK_MODEL_NAME> \
# HD(<N>,GPT,<PARTUUID>,0x800,0x100000)/File(\EFI\UBUNTU\SHIMX64.EFI)
#
# The DISK_MODEL_NAME and PARTUUID speak for themselves (see blkid). The
# N is the Nth partition on the drive (2 for sda2). The File() path may
# be different, for starters depending on whether secure boot is
# enabled or not.
#
# This script has been tested on Ubuntu bionic and focal (March-Oct 2020).
set -eu
export LC_ALL=C
check_args() {
if test $# -ne 2; then
echo "Usage: efibootmirrorsetup /dev/DISK /dev/OTHER_DISK" >&2
exit 1
fi
}
check_two_disk_args() {
# Example pre-condition:
# $disk0 = $1 = "/dev/nvme0n1"
# $disk1 = $2 = "/dev/sdb"
# Example post-condition:
# # (sorted by $diskbyid0 < $diskbyid1)
# $disk0 = "sdb"
# $disk0p = "sdb"
# $diskbyid0 = "/dev/disk/by-id/ata-INTEL_SSDSC2KB019T8_PHYF83"
# $disk1 = "nvme0n1"
# $disk1p = "nvme0n1p"
# $diskbyid1 = "/dev/disk/by-id/nvme-INTEL_SSDSC2KB019T8_PHYF84"
# $partitions = "1 524288 <LF> 2 1874848071" # num size <LF> num size
local partitions0
local partitions1
disk0="${1:-}"
disk1="${2:-}"
if ! test -b "$disk0" || ! test -b "$disk1"; then
ls -l "$disk0" "$disk1" || true >&2
echo "Expected two block devices: /dev/DISK /dev/OTHER_DISK" >&2
exit 1
fi
disk0=$(basename "$disk0")
diskbyid0=$(disk_by_id $disk0)
disk1=$(basename "$disk1")
diskbyid1=$(disk_by_id $disk1)
if test -z "$diskbyid0" || test "$diskbyid0" = "$diskbyid1"; then
echo "Disk by id is empty or equal?" >&2
exit 1
elif test "$(basename $diskbyid0)" '>' "$(basename $diskbyid1)"; then
# swap disk order
disk_=$disk0; disk0=$disk1; disk1=$disk_
diskbyid_=$diskbyid0; diskbyid0=$diskbyid1; diskbyid1=$diskbyid_
fi
test "${disk0%[0-9]}" = "${disk0}" && disk0p=$disk0 || disk0p=${disk0}p
test "${disk1%[0-9]}" = "${disk1}" && disk1p=$disk1 || disk1p=${disk1}p
partitions0=$(sed -e '
/ '"$disk0p"'[0-9]\+$/!d
s/.*[[:blank:]]\([0-9]\+\)[[:blank:]]\+'"$disk0p"'\([0-9]\+\)$/\2 \1/' \
/proc/partitions | sort -n)
partitions1=$(sed -e '
/ '"$disk1p"'[0-9]\+$/!d
s/.*[[:blank:]]\([0-9]\+\)[[:blank:]]\+'"$disk1p"'\([0-9]\+\)$/\2 \1/' \
/proc/partitions | sort -n)
if test -z "$partitions0" || test "$partitions0" != "$partitions1"; then
echo "Partition mismatch, expected equality:" >&2
printf " /dev/$disk0p%s %s\\n" $partitions0 >&2
echo "versus:" >&2
printf " /dev/$disk1p%s %s\\n" $partitions1 >&2
echo >&2
echo -n "Is this the right drive? Proceed anyway [y/N]? "
read yn
if test "$yn" != y; then
echo "Aborted" >&2
exit 1
fi
fi
partitions="$partitions0"
}
lines() {
if test -z "$1"; then
echo 0
else
echo "$1" | wc -l
fi
}
check_efi_partition() {
local action=$1
local disk=$2
local partnum=$3
local diskp
local dev
local vals
local has_vfat
local has_efidos
local has_efigpt
test "${disk%[0-9]}" = "${disk}" && diskp=$disk || diskp=${disk}p
dev=/dev/$diskp$partnum
vals=$(blkid -o export "$dev") || return
has_vfat=$(echo "$vals" | grep '^TYPE=vfat$' | wc -l)
has_efidos=$(fdisk -lo device,type "/dev/$disk" 2>/dev/null |
grep "^$dev[[:blank:]]\+EFI System" | wc -l)
has_efigpt=$(lsblk -no PARTTYPE "$dev" 2>/dev/null |
grep c12a7328-f81f-11d2-ba4b-00a0c93ec93b | wc -l)
case $action in
scan)
if test $((has_vfat + has_efidos + has_efigpt)) -ne 0; then
echo "HINT: $dev is possibly an EFI partition" >&2
test $has_vfat -ne 0 && echo " - it has TYPE=vfat" >&2
test $has_efidos -ne 0 &&
echo " - it has the EFI dos partition type (fdisk t)" >&2
test $has_efigpt -ne 0 &&
echo " - it has the EFI partition GUID" >&2
echo "\
If this _is_ an EFI partition, please use fatlabel(1) and label it:
fatlabel '$dev' 'EFI'
" >&2
fi
;;
verify)
if test $((has_vfat + has_efidos + has_efigpt)) -ne 3; then
echo "WARNING: $dev lacks some EFI characteristics" >&2
test $has_vfat -eq 0 && echo "- it lacks TYPE=vfat" >&2
test $has_efidos -eq 0 &&
echo "- it lacks the EFI dos partition type (fdisk t)" >&2
test $has_efigpt -eq 0 &&
echo "- it lacks the EFI partition GUID \
(sgdisk --typecode=$partnum:ef00 /dev/$disk)" >&2
echo >&2
fi
;;
*)
echo "Programming error: $action" >&2
exit 1
;;
esac
}
locate_efi_partitions() {
# Example pre-condition:
# $disk0p = "sdb"
# $disk1p = "nvme0n1p"
# $partitions = "1 524288 <LF> 2 1874848071" # num size <LF> num size
# Example stdout:
# sda 1 <uuid>
# sdb 1 <uuid>
local found=0
local disk
local diskp
local partnum
local dev
local vals
for disk in $disk0 $disk1; do
for partnum in $(echo "$partitions" | sed -e 's/ .*//'); do
test "${disk%[0-9]}" = "${disk}" && diskp=$disk || diskp=${disk}p
dev=/dev/$diskp$partnum
vals=$(
blkid -t LABEL=EFI -o export "$dev" ||
blkid -t PARTLABEL='EFI System Partition' -o export "$dev") ||
continue
if echo "$vals" | grep -qE \
'^LABEL=EFI$|^PARTLABEL=EFI\\ System\\ Partition$' &&
echo "$vals" | grep -q '^TYPE=vfat$'; then
echo "$diskp $partnum $(
echo "$vals" | sed -ne 's/^PARTUUID=//p')"
found=$((found+1))
fi
done
done
if test $found -eq 0; then
for disk in $disk0 $disk1; do
for partnum in $(echo "$partitions" | sed -e 's/ .*//'); do
check_efi_partition scan "$disk" "$partnum"
done
done
fi
}
disk_by_id() {
# Example pre-condition:
# $1 = "sdb"
# Example stdout:
# /dev/disk/by-id/ata-INTEL_BLA
local disk=$1
local tmp=
local x
for x in /dev/disk/by-id/*; do
if test "$(readlink "$x")" = "../../$disk"; then
tmp=$x
break
fi
done
test -z "$tmp" && echo "No disk-by-id for $disk" >&2 && exit 1
echo "$tmp"
}
check_two_efi_partitions() {
# Example pre-condition:
# <globals for locate_efi_partitions>
# $disk0 = "sdb"
# $disk1 = "nvme0n1"
# Example post-condition:
# $part0 = 1
# $part1 = 1
# $uuid0 = <some-uuid>
# $uuid1 = <some-uuid>
local located="$(locate_efi_partitions)"
local located_n=$(lines "$located")
echo "Found $located_n EFI partition(s):"
echo "$located" | sed -e 's#\([^ ]*\) \([^ ]*\) #- /dev/\1\2 PARTUUID=#'
echo
if test -z "$located" || test $located_n = 0; then
echo "No EFI partitions found. Aborting" >&2
exit 1
elif test $located_n -gt 2; then
echo "Did not expect more than 2 VFAT partitions. Abort" >&2
exit 1
elif test $located_n -gt 1 &&
test $(echo "$located"|cut -d' ' -f1|sort -u| wc -l) -eq 1; then
echo "Did not expect more VFAT partitions on same drive. Abort" >&2
exit 1
fi
local located0
local located1
local founddisk
local foundpart
local otherdisk
local destpart
if test $located_n -eq 1; then
founddisk=$(echo "$located" | cut -d' ' -f1)
foundpart=$(echo "$located" | cut -d' ' -f2)
if test $founddisk = $disk0p; then
otherdisk=$disk1p
elif test $founddisk = $disk1p; then
otherdisk=$disk0p
else
echo "Unexpected founddisk $founddisk" >&2
exit 1
fi
destpart=/dev/$otherdisk$foundpart
echo -n "Shall I format the EFI partition for you on $destpart [y/n]? "
read yn
if test "$yn" != y; then
echo "Aborted" >&2
exit 1
fi
# Until this commit, the sectors/cluster default for FAT32 is wrong:
# https://github.com/dosfstools/dosfstools/pull/153/commits/
# 404ead8adb76fcb3a3521c0c3f13ef39898c0818
# For a 510MiB filesystem, we'd get 130300 clusters for 512 byte
# sectors (4K clusters), but only 16312 clusters for 4096 byte
# sectors (32K clusters(!)). Microsoft and UEFI systems will detect
# FAT16 if there are fewer than 65,525 clusters.
# Two options, use FAT16, or manually reduce the number of sectors
# per cluster to 1 (using -s 1), but that is only valid if the
# sectors are large enough (4096).
mkfs.fat -F 16 -n EFI $destpart
located="$(locate_efi_partitions)"
located_n=$(lines "$located")
test "$located_n" -eq 2
fi
located0=$(echo "$located" | grep "^$disk0" )
located1=$(echo "$located" | grep "^$disk1" )
part0=$(echo "$located0" | cut -d' ' -f2)
part1=$(echo "$located1" | cut -d' ' -f2)
uuid0=$(echo "$located0" | cut -d' ' -f3)
uuid1=$(echo "$located1" | cut -d' ' -f3)
test -n "$part0" && test -n "$part1"
if test "$part0" != "$part1"; then
# NOTE: This may very well work, but we'll want to test this
# before allowing it.
echo "Did not expect EFI on different partition numbers \
($disk0p$part0 vs. $disk1p$part1)" >&2
exit 1
fi
if test -z "$uuid0" || test "$uuid0" = "$uuid1"; then
echo "Empty or duplicate PARTUUID=" >&2
echo "Please fix with fdisk: p x i/u r w" >&2
echo "See blkid(1). Use uuidgen(1) to get fresh UUIDs" >&2
exit 1
fi
check_efi_partition verify "$disk0" "$part0"
check_efi_partition verify "$disk1" "$part1"
}
check_and_fix_fstab() {
# Example pre-condition:
# $disk0 = "sdb"
# $diskbyid0 = "/dev/disk/by-id/ata-INTEL_SSDSC2KB019T8_PHYF83"
# $part0 = 1
# $uuid0 = <uuid>
# $disk1 = "nvme0n1"
# $diskbyid1 = "/dev/disk/by-id/nvme-INTEL_SSDSC2KB019T8_PHYF84"
# $part1 = 1
# $uuid1 = <uuid>
local temp=$(mktemp)
mkdir -p /boot/efi /boot/efi2
cat /etc/fstab | sed -e '
/^# EFI:/d
/ \/boot\/efi[0-9]* /d
' >>"$temp"
cat >>"$temp" <<EOF
# EFI: $diskbyid0-part$part0 ($disk0p$part0)
PARTUUID=$uuid0 /boot/efi vfat nofail,x-systemd.device-timeout=15 0 2
# EFI: $diskbyid1-part$part1 ($disk1p$part1)
PARTUUID=$uuid1 /boot/efi2 vfat nofail,x-systemd.device-timeout=15 0 2
EOF
if ! cmp -s /etc/fstab "$temp"; then
diff -u /etc/fstab "$temp" || true
echo
echo -n "Shall I update fstab for you [y/n]? "
read yn
if test "$yn" != y; then
echo "Aborted" >&2
rm "$temp"
exit 1
fi
cat "$temp" >/etc/fstab
rm "$temp"
fi
umount /boot/efi || true
umount /boot/efi2 || true
mount /boot/efi
mount /boot/efi2
}
make_grub_postinstall() {
# Example pre-condition:
# $diskbyid0 = "/dev/disk/by-id/ata-INTEL_SSDSC2KB019T8_PHYF83"
# $part0 = 1
# $diskbyid1 = "/dev/disk/by-id/ata-INTEL_SSDSC2KB019T8_PHYF84"
# $part1 = 1
local temp=$(mktemp)
local dst=/etc/kernel/postinst.d/zz-grub-install-efibootmirrorsetup
cat >"$temp" <<EOF2
#!/bin/sh
# THIS FILE IS GENERATED BY efibootmirrorsetup (ossobv/vcutil)
disk0='$diskbyid0'
disk0name="\${disk0##*/}" # ${diskbyid0##*/}
disk0part=$part0
disk1='$diskbyid1'
disk1name="\${disk1##*/}" # ${diskbyid1##*/}
disk1part=$part1
# Do not use grub-install --bootloader-id because it breaks the hardcoded path
# in grub.efi to EFI\\\\ubuntu\\\\grub.cfg
err=
grub-install --efi-directory /boot/efi \$disk0 || err=1
grub-install --efi-directory /boot/efi2 \$disk1 || err=1
if test -n "\$err"; then
# Observed "grub-install: error: unknown filesystem." in the wild;
# was fixed by a reboot.
cat >&2 <<EOF
ERROR: issue with grub-install (unknown filesystem?)
You may need to reboot first.. but FIRST manually check:
efibootmgr -v # look for File()
find /boot/efi # see if the file is here
find /boot/efi2 # .. and here
They should have the appropriate *.efi files.
EOF
fi
binpath=\$(efibootmgr -v | sed -e '/ ubuntu[[:blank:]]/!d;s/.*File(//;s/)\$//')
if test -z "\$binpath"; then
binpath='\\EFI\\ubuntu\\shimx64.efi'
printf '\\nWARNING: did not find ubuntu file path in EFI\\n\\n\
Using: %s\\n' \\
"\$binpath" >&2
fi
# Remove labels that we'll create. Remove the just-created 'ubuntu'.
for label in ubuntu \$(basename \$disk0) \$(basename \$disk1); do
for num in \$(efibootmgr |
sed -e '/ '\$label'\$/!d;s/^Boot\\(....\\).*/\\1/'); do
efibootmgr -q -B -b \$num
done
done
efibootmgr -q -c -d \$disk0 -p \$disk0part -w -L "\$disk0name" -l "\$binpath"
efibootmgr -q -c -d \$disk1 -p \$disk1part -w -L "\$disk1name" -l "\$binpath"
EOF2
if ! cmp -s "$dst" "$temp"; then
diff --text -Nu "$dst" "$temp" || true
echo
echo -n "Shall I update $dst for you? [y/n] "
read yn
if test "$yn" != y; then
echo "Aborted" >&2
rm "$temp"
exit 1
fi
cat "$temp" >"$dst"
chmod 755 "$dst"
rm "$temp"
fi
}
run_grub_postinstall() {
echo "Running: /etc/kernel/postinst.d/zz-grub-install-efibootmirrorsetup"
echo
/etc/kernel/postinst.d/zz-grub-install-efibootmirrorsetup
echo
echo OK
}
check_args "$@"
check_two_disk_args "$@"
check_two_efi_partitions
check_and_fix_fstab
make_grub_postinstall
run_grub_postinstall