From a30de3b849bb29b4d2206e1a652707fba8ea18a4 Mon Sep 17 00:00:00 2001 From: Matthias Treydte Date: Fri, 13 May 2022 12:09:25 +0200 Subject: nixos/systemd-boot: fix systemd-boot-builder dowgrade to fail Since, 4ddc78818e4d69605f49f5f8faa22fb4601b0a0c systemd-boot-builder is broken in two ways: * if no systemd-boot is currently installed *and* the NIXOS_INSTALL_BOOTLOADER env variable is not set, it will try to run "bootctl update", which will fail * if the currently installed systemd-boot version is newer than the version we're about to install, it will also try to run "bootctl update", which will fail This patch changes the behaviour, * for the first case to still fail, but not even bother to try running "bootctl update" and instead erroring out with an exception * for the second case to leave the newer version in place, restoring the pre - 4ddc78818e4d69605f49f5f8faa22fb4601b0a0c behaviour To do the proper version check a new "should_update" helper function was introduced, mimicing the compare_product C function from bootctl. If the following systemd issue gets resolved, we would have a nice way to get rid of this function: > https://github.com/systemd/systemd/issues/23450 This change allows to again switch to a different NixOS configuration which contains an older systemd-boot. Co-authored-by: Martin Weinelt --- .../loader/systemd-boot/systemd-boot-builder.py | 46 +++++++++++++++------- 1 file changed, 31 insertions(+), 15 deletions(-) (limited to 'nixos/modules/system') diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py index fa879437fd810..4bb39811434c3 100644 --- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py +++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py @@ -204,6 +204,20 @@ def get_profiles() -> List[str]: else: return [] +def should_update(v_from: str, v_to: str) -> bool: + # see https://github.com/systemd/systemd/blob/main/src/boot/bootctl.c compare_product function + + len_from = len(v_from) + len_to = len(v_to) + + if len_from < len_to: + return False + + if len_from > len_to: + return True + + return v_from < v_to + def main() -> None: parser = argparse.ArgumentParser(description='Update NixOS-related systemd-boot files') @@ -244,27 +258,29 @@ def main() -> None: subprocess.check_call(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@"] + flags + ["install"]) else: # Update bootloader to latest if needed - systemd_version = subprocess.check_output(["@systemd@/bin/bootctl", "--version"], universal_newlines=True).split()[2] - sdboot_status = subprocess.check_output(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@", "status"], universal_newlines=True) + available_out = subprocess.check_output(["@systemd@/bin/bootctl", "--version"], universal_newlines=True).split()[2] + installed_out = subprocess.check_output(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@", "status"], universal_newlines=True) # See status_binaries() in systemd bootctl.c for code which generates this - m = re.search("^\W+File:.*/EFI/(BOOT|systemd)/.*\.efi \(systemd-boot ([\d.]+[^)]*)\)$", - sdboot_status, re.IGNORECASE | re.MULTILINE) + installed_match = re.search(r"^\W+File:.*/EFI/(?:BOOT|systemd)/.*\.efi \(systemd-boot ([\d.]+[^)]*)\)$", + installed_out, re.IGNORECASE | re.MULTILINE) - needs_install = False + available_match = re.search(r"^\((.*)\)$", available_out) - if m is None: - print("could not find any previously installed systemd-boot, installing.") - # Let systemd-boot attempt an installation if a previous one wasn't found - needs_install = True - else: - sdboot_version = f'({m.group(2)})' - if systemd_version != sdboot_version: - print("updating systemd-boot from %s to %s" % (sdboot_version, systemd_version)) - needs_install = True + if installed_match is None: + raise Exception("could not find any previously installed systemd-boot") + + if available_match is None: + raise Exception("could not determine systemd-boot version") - if needs_install: + installed_version = installed_match.group(1) + available_version = available_match.group(1) + + if should_update(installed_version, available_version): + print("updating systemd-boot from %s to %s" % (installed_version, available_version)) subprocess.check_call(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@", "update"]) + else: + print("leaving systemd-boot %s in place (%s is not newer)" % (installed_version, available_version)) mkdir_p("@efiSysMountPoint@/efi/nixos") mkdir_p("@efiSysMountPoint@/loader/entries") -- cgit 1.4.1