ENH make efi update script waaaaaay more robust and general
This commit is contained in:
parent
ef8967ebb6
commit
f031d70b11
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue