about summary refs log tree commit diff
path: root/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
diff options
context:
space:
mode:
authorMatthias Treydte <mt@waldheinz.de>2022-05-13 12:09:25 +0200
committerMatthias Treydte <mt@waldheinz.de>2022-05-28 13:18:21 +0200
commita30de3b849bb29b4d2206e1a652707fba8ea18a4 (patch)
tree2e143a126d38615a8925e3839f0d2b2299293a1e /nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
parent2e01fc66bc57790490ed261c9e4f71c64cf91e58 (diff)
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 <mweinelt@users.noreply.github.com>
Diffstat (limited to 'nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py')
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py46
1 files changed, 31 insertions, 15 deletions
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")