Merge pull request #283547 from r-vdp/boot_sort_key

This commit is contained in:
Julien Malka 2024-03-02 22:17:52 +01:00 committed by GitHub
commit 4ee410d8f8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 88 additions and 24 deletions

View file

@ -43,6 +43,7 @@ class BootSpec:
system: str system: str
toplevel: str toplevel: str
specialisations: Dict[str, "BootSpec"] specialisations: Dict[str, "BootSpec"]
sortKey: str
initrdSecrets: str | None = None initrdSecrets: str | None = None
@ -73,6 +74,7 @@ def system_dir(profile: str | None, generation: int, specialisation: str | None)
return d return d
BOOT_ENTRY = """title {title} BOOT_ENTRY = """title {title}
sort-key {sort_key}
version Generation {generation} {description} version Generation {generation} {description}
linux {kernel} linux {kernel}
initrd {initrd} initrd {initrd}
@ -123,7 +125,13 @@ def get_bootspec(profile: str | None, generation: int) -> BootSpec:
def bootspec_from_json(bootspec_json: Dict) -> BootSpec: def bootspec_from_json(bootspec_json: Dict) -> BootSpec:
specialisations = bootspec_json['org.nixos.specialisation.v1'] specialisations = bootspec_json['org.nixos.specialisation.v1']
specialisations = {k: bootspec_from_json(v) for k, v in specialisations.items()} 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: 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: with open(tmp_path, 'w') as f:
f.write(BOOT_ENTRY.format(title=title, f.write(BOOT_ENTRY.format(title=title,
sort_key=bootspec.sortKey,
generation=generation, generation=generation,
kernel=kernel, kernel=kernel,
initrd=initrd, initrd=initrd,

View file

@ -87,6 +87,16 @@ in {
imports = imports =
[ (mkRenamedOptionModule [ "boot" "loader" "gummiboot" "enable" ] [ "boot" "loader" "systemd-boot" "enable" ]) [ (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 = { 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 { editor = mkOption {
default = true; default = true;
@ -184,13 +223,15 @@ in {
''; '';
}; };
entryFilename = mkOption { sortKey = mkOption {
default = "memtest86.conf"; default = "o_memtest86";
type = types.str; type = types.str;
description = lib.mdDoc '' 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, so if you want something to appear after all the NixOS entries,
it should start with {file}`o` or onwards. it should start with {file}`o` or onwards.
See also {option}`boot.loader.systemd-boot.sortKey`.
''; '';
}; };
}; };
@ -207,13 +248,15 @@ in {
''; '';
}; };
entryFilename = mkOption { sortKey = mkOption {
default = "o_netbootxyz.conf"; default = "o_netbootxyz";
type = types.str; type = types.str;
description = lib.mdDoc '' 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, so if you want something to appear after all the NixOS entries,
it should start with {file}`o` or onwards. it should start with {file}`o` or onwards.
See also {option}`boot.loader.systemd-boot.sortKey`.
''; '';
}; };
}; };
@ -225,6 +268,7 @@ in {
{ "memtest86.conf" = ''' { "memtest86.conf" = '''
title Memtest86+ title Memtest86+
efi /efi/memtest86/memtest.efi efi /efi/memtest86/memtest.efi
sort-key z_memtest
'''; } '''; }
''; '';
description = lib.mdDoc '' description = lib.mdDoc ''
@ -233,9 +277,10 @@ in {
Each attribute name denotes the destination file name, Each attribute name denotes the destination file name,
and the corresponding attribute value is the contents of the entry. and the corresponding attribute value is the contents of the entry.
`systemd-boot` orders the menu entries by the config file names, To control the ordering of the entry in the boot menu, use the sort-key
so if you want something to appear after all the NixOS entries, field, see
it should start with {file}`o` or onwards. 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 [ boot.loader.systemd-boot.extraEntries = mkMerge [
(mkIf cfg.memtest86.enable { (mkIf cfg.memtest86.enable {
"${cfg.memtest86.entryFilename}" = '' "memtest86.conf" = ''
title Memtest86+ title Memtest86+
efi /efi/memtest86/memtest.efi efi /efi/memtest86/memtest.efi
sort-key ${cfg.memtest86.sortKey}
''; '';
}) })
(mkIf cfg.netbootxyz.enable { (mkIf cfg.netbootxyz.enable {
"${cfg.netbootxyz.entryFilename}" = '' "netbootxyz.conf" = ''
title netboot.xyz title netboot.xyz
efi /efi/netbootxyz/netboot.xyz.efi 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 = { system = {
build.installBootLoader = finalSystemdBootBuilder; build.installBootLoader = finalSystemdBootBuilder;

View file

@ -93,6 +93,7 @@ in
machine.wait_for_unit("multi-user.target") machine.wait_for_unit("multi-user.target")
machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf") machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf")
machine.succeed("grep 'sort-key nixos' /boot/loader/entries/nixos-generation-1.conf")
# Ensure we actually booted using systemd-boot # Ensure we actually booted using systemd-boot
# Magic number is the vendor UUID used by systemd-boot. # Magic number is the vendor UUID used by systemd-boot.
@ -166,7 +167,9 @@ in
nodes.machine = { pkgs, lib, ... }: { nodes.machine = { pkgs, lib, ... }: {
imports = [ common ]; imports = [ common ];
specialisation.something.configuration = {}; specialisation.something.configuration = {
boot.loader.systemd-boot.sortKey = "something";
};
}; };
testScript = '' testScript = ''
@ -179,6 +182,9 @@ in
machine.succeed( machine.succeed(
"grep -q 'title NixOS (something)' /boot/loader/entries/nixos-generation-1-specialisation-something.conf" "grep -q 'title NixOS (something)' /boot/loader/entries/nixos-generation-1-specialisation-something.conf"
) )
machine.succeed(
"grep 'sort-key something' /boot/loader/entries/nixos-generation-1-specialisation-something.conf"
)
''; '';
}; };
@ -256,25 +262,25 @@ in
}; };
testScript = '' testScript = ''
machine.succeed("test -e /boot/loader/entries/o_netbootxyz.conf") machine.succeed("test -e /boot/loader/entries/netbootxyz.conf")
machine.succeed("test -e /boot/efi/netbootxyz/netboot.xyz.efi") machine.succeed("test -e /boot/efi/netbootxyz/netboot.xyz.efi")
''; '';
}; };
entryFilename = makeTest { memtestSortKey = makeTest {
name = "systemd-boot-entry-filename"; name = "systemd-boot-memtest-sortkey";
meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ]; meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
nodes.machine = { pkgs, lib, ... }: { nodes.machine = { pkgs, lib, ... }: {
imports = [ common ]; imports = [ common ];
boot.loader.systemd-boot.memtest86.enable = true; boot.loader.systemd-boot.memtest86.enable = true;
boot.loader.systemd-boot.memtest86.entryFilename = "apple.conf"; boot.loader.systemd-boot.memtest86.sortKey = "apple";
}; };
testScript = '' testScript = ''
machine.fail("test -e /boot/loader/entries/memtest86.conf") machine.succeed("test -e /boot/loader/entries/memtest86.conf")
machine.succeed("test -e /boot/loader/entries/apple.conf")
machine.succeed("test -e /boot/efi/memtest86/memtest.efi") machine.succeed("test -e /boot/efi/memtest86/memtest.efi")
machine.succeed("grep 'sort-key apple' /boot/loader/entries/memtest86.conf")
''; '';
}; };
@ -285,7 +291,6 @@ in
nodes.machine = { pkgs, lib, ... }: { nodes.machine = { pkgs, lib, ... }: {
imports = [ commonXbootldr ]; imports = [ commonXbootldr ];
boot.loader.systemd-boot.memtest86.enable = true; boot.loader.systemd-boot.memtest86.enable = true;
boot.loader.systemd-boot.memtest86.entryFilename = "apple.conf";
}; };
testScript = { nodes, ... }: '' testScript = { nodes, ... }: ''
@ -295,8 +300,7 @@ in
machine.wait_for_unit("multi-user.target") machine.wait_for_unit("multi-user.target")
machine.succeed("test -e /efi/EFI/systemd/systemd-bootx64.efi") machine.succeed("test -e /efi/EFI/systemd/systemd-bootx64.efi")
machine.fail("test -e /boot/loader/entries/memtest86.conf") machine.succeed("test -e /boot/loader/entries/memtest86.conf")
machine.succeed("test -e /boot/loader/entries/apple.conf")
machine.succeed("test -e /boot/EFI/memtest86/memtest.efi") machine.succeed("test -e /boot/EFI/memtest86/memtest.efi")
''; '';
}; };
@ -388,9 +392,9 @@ in
machine.succeed("${finalSystem}/bin/switch-to-configuration boot") machine.succeed("${finalSystem}/bin/switch-to-configuration boot")
machine.fail("test -e /boot/efi/fruits/tomato.efi") machine.fail("test -e /boot/efi/fruits/tomato.efi")
machine.fail("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") machine.fail("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi")
machine.succeed("test -e /boot/loader/entries/o_netbootxyz.conf") machine.succeed("test -e /boot/loader/entries/netbootxyz.conf")
machine.succeed("test -e /boot/efi/netbootxyz/netboot.xyz.efi") machine.succeed("test -e /boot/efi/netbootxyz/netboot.xyz.efi")
machine.succeed("test -e /boot/efi/nixos/.extra-files/loader/entries/o_netbootxyz.conf") machine.succeed("test -e /boot/efi/nixos/.extra-files/loader/entries/netbootxyz.conf")
machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/netbootxyz/netboot.xyz.efi") machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/netbootxyz/netboot.xyz.efi")
''; '';
}; };