ENH make efi update script waaaaaay more robust and general

This commit is contained in:
Nathan Dwarshuis 2022-07-23 19:22:57 -04:00
parent ef8967ebb6
commit f031d70b11
1 changed files with 126 additions and 23 deletions

View File

@ -1,31 +1,134 @@
#! /bin/bash #!/bin/env python3
create_entry() { """Update EFI boot entries for Linux kernels configured with EFISTUB.
local -n params=$1
efibootmgr -q -b $bootNumber -B
efibootmgr -q -b $bootNumber -d "${params[device]}" -p "${params[part]}" -c -L "${params[label]}" -l "${params[loader]}" -u "${params[args]}"
if [ $bootNumber -ne 0 ]; then bootOrder+=",$bootNumber"; else bootOrder+="0"; fi
((bootNumber+=1))
}
# must disable KMS for nvidia else X does not start :/ Requires root access.
root="root=/dev/mapper/vg1-root rw rootfstype=btrfs rootflags=noatime,compress-force,ssd,space_cache,subvolid=5,subvol=/" USAGE: efi-update PATH
opts="resume=/dev/nvme0n1p2 systemd.unified_cgroup_hierarchy=1 libahci.ignore_sss=1 nmi_watchdog=0 vt.global_cursor_default=0 acpi_osi=\"Windows 2019\" quiet loglevel=3 rd.systemd.show_status=0 rd.udev.log-priority=3 initrd=/intel-ucode.img"
declare -A arch_lts=( ["device"]="/dev/nvme0n1" ["part"]=1 ["label"]="Arch Linux (LTS)" ["loader"]="/vmlinuz-linux-lts" ["args"]="$root $opts elevator=deadline initrd=/initramfs-linux-lts.img") PATH points to a JSON file which holds the boot entries to insert. This file is
declare -A arch_lts_native=( ["device"]="/dev/nvme0n1" ["part"]=1 ["label"]="Arch Linux (LTS-native)" ["loader"]="/vmlinuz-linux-lts-native" ["args"]="$root $opts pcie_aspm=force elevator=deadline initrd=/initramfs-linux-lts-native.img") an array of objects where each object is one boot entry and the order of each
#~ declare -A win10=( ["device"]="/dev/nvme0n1" ["part"]=2 ["label"]="Windows 10" ["loader"]="\EFI\MICROSOFT\BOOT\BOOTMGFW.EFI" ["args"]="") entry determines its order in the boot sequence.
declare -a entries Each entry follows this schema:
- label: the name of this entry (as it should appear in the boot menu)
- device: the path to the device holding the kernel to boot
- partition: the partition on the device (above) holding the kernel
- kernel: the name of the kernel to boot (something like "linux-lts")
- root: and object like:
- path: the device path of the root partition
- fstype: the filesytem type of the root partition
- mountopts: an array of mount options for the root file system
- params: an array of kernel parameters to pass (in addition to those for root)
bootOrder="" ASSUMPTIONS:
bootNumber=0 - linux EFISTUB kernels only
- no secondary bootloader (eg grub)
- root filesystem mounts as rw
- initramfs exists (eg no monolithic kernels)
- initramfs and loader paths are named according to the kernel
# order of these commands determines boot order """
create_entry arch_lts_native
create_entry arch_lts
efibootmgr -q -D import sys
efibootmgr -q -O import json
efibootmgr -o $bootOrder import re
import subprocess as sp
from shutil import which
from itertools import chain
EFIEXE = "efibootmgr"
def call_efibootmgr(args):
"""Call efibootmgr (quietly) with ARGS"""
return sp.run([EFIEXE, "-q", *args], check=True)
def read_entries():
"""Read boot entries from JSON file"""
if len(sys.argv) != 2:
print("must supply entry JSON as single argument")
sys.exit(1)
with open(sys.argv[1], encoding="utf-8") as file:
# hack to remove comments from (nonstandard) json
out = re.sub("//.*?\n", "", file.read())
return json.loads(re.sub("/\\*.*?\\*/", "", out))
def delete_entry(bootnum):
"""Remove the entry associated with BOOTNUM"""
call_efibootmgr(["-b", str(bootnum), "-B"])
def insert_entry(bootnum, entry):
"""Insert ENTRY at position BOOTNUM"""
def fmt_keyval(key, val):
return f"{key}={val}"
def fmt_flag_(flag, arg, quote=False):
return [f"-{flag}", f"'{arg}'" if quote else f"{arg}"]
def fmt_flag(flag, key, quote=False):
return fmt_flag_(flag, entry[key], quote)
kernel = entry["kernel"]
root = entry["root"]
unicode_args = " ".join(
[
fmt_keyval("root", root["path"]),
"rw",
fmt_keyval("rootfstype", root["fstype"]),
fmt_keyval("rootflags", ",".join(root["mountopts"])),
*entry["params"],
fmt_keyval("initrd", f"/initramfs-{kernel}.img"),
]
)
args = [
fmt_flag_("b", bootnum),
["-c"],
fmt_flag("L", "label"),
fmt_flag("d", "device"),
fmt_flag("p", "partition"),
fmt_flag_("l", f"/vmlinuz-{kernel}"),
fmt_flag_("u", unicode_args),
]
call_efibootmgr([*chain(*args)])
def update_entry(bootnum, entry):
"""Update (delete and insert) ENTRY at position BOOTNUM"""
print(f"Updating entry '{entry['label']}'")
try:
delete_entry(bootnum)
except sp.CalledProcessError:
pass
insert_entry(bootnum, entry)
def remove_dups():
"""Remove duplicate boot entries if they exist"""
call_efibootmgr("-D")
def set_order(entries):
"""Set the boot order according to the order of ENTRIES"""
call_efibootmgr("-O")
order = ",".join([str(i) for i in range(0, len(entries))])
call_efibootmgr(order)
def main():
"""Update all EFI entries"""
if not which(EFIEXE):
print(f"{EFIEXE} not found")
sys.exit(1)
entries = read_entries()
for bootnum, entry in enumerate(entries):
update_entry(bootnum, entry)
remove_dups()
set_order(entries)
main()