about summary refs log tree commit diff
path: root/nixos/modules/system
diff options
context:
space:
mode:
authorr-vdp <ramses@well-founded.dev>2024-01-24 18:15:28 +0100
committerr-vdp <ramses@well-founded.dev>2024-03-02 02:11:32 +0100
commita8ab8b59a75a4bf0675fb3785e76c54873044f9e (patch)
tree965e489b9c8c00ca3fbf74859bfe0abfd818a07c /nixos/modules/system
parent0267739e11e44bb791b747565758c6446b01eac7 (diff)
systemd-boot: introduce options to set a sort-key for systemd-boot entries
Without sort-keys specified on entries, the entries are sorted only by
file name (in decreasing order, so starting at the end of the alphabet!),
without taking any other fields into account (see
[the boot loader specification reference][1]).
Moreover, entries without a sort-key are always ordered after all
entries with a sort-key, so by not adding a sort-key to the NixOS ones,
we cannot add a sort-key to any other entry while keeping it below the
NixOS entries.

So currently we have options to set the file names for additional entries like
memtest and netbootxyz.
However, as mentioned above, the sorting by file name is not very intuitive and
actually sorts in the opposite order of what is currently mentioned in the option
descriptions.

With this commit, we set a configurable sort-key on all NixOS entries,
and add options for setting the sort-keys for the memtest and netbootxyz
entries.
The sorting by sort-key is more intuitive (it starts at the start of the
alphabet) and also takes into account the machine-id and version for entries
with identical sort-keys.

We use a bootspec extension to store the sort keys, which allows us to
redefine the sort key for individual specialisations without needing any
special casing.

[1]: https://uapi-group.org/specifications/specs/boot_loader_specification/#sorting
Diffstat (limited to 'nixos/modules/system')
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py11
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix73
2 files changed, 72 insertions, 12 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 258cf622a894a..03bff1dee5b9d 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
@@ -43,6 +43,7 @@ class BootSpec:
     system: str
     toplevel: str
     specialisations: Dict[str, "BootSpec"]
+    sortKey: str
     initrdSecrets: str | None = None
 
 
@@ -73,6 +74,7 @@ def system_dir(profile: str | None, generation: int, specialisation: str | None)
         return d
 
 BOOT_ENTRY = """title {title}
+sort-key {sort_key}
 version Generation {generation} {description}
 linux {kernel}
 initrd {initrd}
@@ -123,7 +125,13 @@ def get_bootspec(profile: str | None, generation: int) -> BootSpec:
 def bootspec_from_json(bootspec_json: Dict) -> BootSpec:
     specialisations = bootspec_json['org.nixos.specialisation.v1']
     specialisations = {k: bootspec_from_json(v) for k, v in specialisations.items()}
-    return BootSpec(**bootspec_json['org.nixos.bootspec.v1'], specialisations=specialisations)
+    systemdBootExtension = bootspec_json.get('org.nixos.systemd-boot', {})
+    sortKey = systemdBootExtension.get('sortKey', 'nixos')
+    return BootSpec(
+        **bootspec_json['org.nixos.bootspec.v1'],
+        specialisations=specialisations,
+        sortKey=sortKey
+    )
 
 
 def copy_from_file(file: str, dry_run: bool = False) -> str:
@@ -170,6 +178,7 @@ def write_entry(profile: str | None, generation: int, specialisation: str | None
 
     with open(tmp_path, 'w') as f:
         f.write(BOOT_ENTRY.format(title=title,
+                    sort_key=bootspec.sortKey,
                     generation=generation,
                     kernel=kernel,
                     initrd=initrd,
diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
index 645b764760dad..ba07506266e26 100644
--- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
@@ -87,6 +87,16 @@ in {
 
   imports =
     [ (mkRenamedOptionModule [ "boot" "loader" "gummiboot" "enable" ] [ "boot" "loader" "systemd-boot" "enable" ])
+      (lib.mkChangedOptionModule
+        [ "boot" "loader" "systemd-boot" "memtest86" "entryFilename" ]
+        [ "boot" "loader" "systemd-boot" "memtest86" "sortKey" ]
+        (config: lib.strings.removeSuffix ".conf" config.boot.loader.systemd-boot.memtest86.entryFilename)
+      )
+      (lib.mkChangedOptionModule
+        [ "boot" "loader" "systemd-boot" "netbootxyz" "entryFilename" ]
+        [ "boot" "loader" "systemd-boot" "netbootxyz" "sortKey" ]
+        (config: lib.strings.removeSuffix ".conf" config.boot.loader.systemd-boot.netbootxyz.entryFilename)
+      )
     ];
 
   options.boot.loader.systemd-boot = {
@@ -102,6 +112,35 @@ in {
       '';
     };
 
+    sortKey = mkOption {
+      default = "nixos";
+      type = lib.types.str;
+      description = ''
+        The sort key used for the NixOS bootloader entries.
+        This key determines sorting relative to non-NixOS entries.
+        See also https://uapi-group.org/specifications/specs/boot_loader_specification/#sorting
+
+        This option can also be used to control the sorting of NixOS specialisations.
+
+        By default, specialisations inherit the sort key of their parent generation
+        and will have the same value for both the sort-key and the version (i.e. the generation number),
+        systemd-boot will therefore sort them based on their file name, meaning that
+        in your boot menu you will have each main generation directly followed by
+        its specialisations sorted alphabetically by their names.
+
+        If you want a different ordering for a specialisation, you can override
+        its sort-key which will cause the specialisation to be uncoupled from its
+        parent generation. It will then be sorted by its new sort-key just like
+        any other boot entry.
+
+        The sort-key is stored in the generation's bootspec, which means that
+        generations keep their sort-keys even if the original definition of the
+        generation was removed from the NixOS configuration.
+        It also means that updating the sort-key will only affect new generations,
+        while old ones will keep the sort-key that they were originally built with.
+      '';
+    };
+
     editor = mkOption {
       default = true;
 
@@ -184,13 +223,15 @@ in {
         '';
       };
 
-      entryFilename = mkOption {
-        default = "memtest86.conf";
+      sortKey = mkOption {
+        default = "o_memtest86";
         type = types.str;
         description = lib.mdDoc ''
-          `systemd-boot` orders the menu entries by the config file names,
+          `systemd-boot` orders the menu entries by their sort keys,
           so if you want something to appear after all the NixOS entries,
           it should start with {file}`o` or onwards.
+
+          See also {option}`boot.loader.systemd-boot.sortKey`.
         '';
       };
     };
@@ -207,13 +248,15 @@ in {
         '';
       };
 
-      entryFilename = mkOption {
-        default = "o_netbootxyz.conf";
+      sortKey = mkOption {
+        default = "o_netbootxyz";
         type = types.str;
         description = lib.mdDoc ''
-          `systemd-boot` orders the menu entries by the config file names,
+          `systemd-boot` orders the menu entries by their sort keys,
           so if you want something to appear after all the NixOS entries,
           it should start with {file}`o` or onwards.
+
+          See also {option}`boot.loader.systemd-boot.sortKey`.
         '';
       };
     };
@@ -225,6 +268,7 @@ in {
         { "memtest86.conf" = '''
           title Memtest86+
           efi /efi/memtest86/memtest.efi
+          sort-key z_memtest
         '''; }
       '';
       description = lib.mdDoc ''
@@ -233,9 +277,10 @@ in {
         Each attribute name denotes the destination file name,
         and the corresponding attribute value is the contents of the entry.
 
-        `systemd-boot` orders the menu entries by the config file names,
-        so if you want something to appear after all the NixOS entries,
-        it should start with {file}`o` or onwards.
+        To control the ordering of the entry in the boot menu, use the sort-key
+        field, see
+        https://uapi-group.org/specifications/specs/boot_loader_specification/#sorting
+        and {option}`boot.loader.systemd-boot.sortKey`.
       '';
     };
 
@@ -328,19 +373,25 @@ in {
 
     boot.loader.systemd-boot.extraEntries = mkMerge [
       (mkIf cfg.memtest86.enable {
-        "${cfg.memtest86.entryFilename}" = ''
+        "memtest86.conf" = ''
           title  Memtest86+
           efi    /efi/memtest86/memtest.efi
+          sort-key ${cfg.memtest86.sortKey}
         '';
       })
       (mkIf cfg.netbootxyz.enable {
-        "${cfg.netbootxyz.entryFilename}" = ''
+        "netbootxyz.conf" = ''
           title  netboot.xyz
           efi    /efi/netbootxyz/netboot.xyz.efi
+          sort-key ${cfg.netbootxyz.sortKey}
         '';
       })
     ];
 
+    boot.bootspec.extensions."org.nixos.systemd-boot" = {
+      inherit (config.boot.loader.systemd-boot) sortKey;
+    };
+
     system = {
       build.installBootLoader = finalSystemdBootBuilder;