{ runTest, runTestOn, lib, ... }: let testScriptPreamble = # python '' def check_current_system(system_path): machine.succeed(f'test $(readlink -f /run/current-system) = "{system_path}"') def check_generation(generation: int, tries_left=0, tries_failed=0, specialisation=None) -> list[str]: if specialisation: title = f"NixOS ({specialisation})" else: title = "NixOS" conf_files = machine.succeed( f"grep --files-with-matches 'version Generation {generation} NixOS' /boot/loader/entries/nixos-*.conf | xargs grep --line-regexp --fixed-strings --files-with-matches 'title {title}'" ).split("\n") suffix = "" if tries_left: suffix += f"+{tries_left}" if tries_failed: suffix += f"-{tries_failed}" assert conf_files[0].endswith(f"{suffix}.conf"), f"Expected {conf_files[0]} to end with {suffix}.conf" return conf_files ''; common = { pkgs, ... }: { virtualisation.useBootLoader = true; virtualisation.useEFIBoot = true; boot.loader.systemd-boot.enable = true; boot.loader.efi.canTouchEfiVariables = true; environment.systemPackages = [ pkgs.efibootmgr ]; system.switch.enable = true; # Needed for machine-id to be persisted between reboots. # Must be a valid (non-zero) ID, otherwise sd_id128_get_machine() # returns -ENOMEDIUM and dbus-broker refuses to start. environment.etc."machine-id".text = "1234567890abcdef1234567890abcdef\n"; }; commonXbootldr = { config, lib, pkgs, ... }: let diskImage = import ../lib/make-disk-image.nix { inherit config lib pkgs; label = "nixos"; format = "qcow2"; partitionTableType = "efixbootldr"; touchEFIVars = true; installBootLoader = true; # Don't copy the channel to avoid rebuilding this image, and all tests # that use it, every time that nixpkgs changes copyChannel = false; }; in { imports = [ common ]; virtualisation.useBootLoader = lib.mkForce false; # Only way to tell qemu-vm not to create the default system image virtualisation.directBoot.enable = false; # But don't direct boot either because we're testing systemd-boot system.build.diskImage = diskImage; # Use custom disk image with an XBOOTLDR partition virtualisation.efi.variables = "${diskImage}/efi-vars.fd"; virtualisation.useDefaultFilesystems = false; # Needs custom setup for `diskImage` virtualisation.bootPartition = null; virtualisation.fileSystems = { "/" = { device = "/dev/vda3"; fsType = "ext4"; }; "/boot" = { device = "/dev/vda2"; fsType = "vfat"; noCheck = true; }; "/efi" = { device = "/dev/vda1"; fsType = "vfat"; noCheck = true; }; }; boot.loader.systemd-boot.enable = true; boot.loader.efi.efiSysMountPoint = "/efi"; boot.loader.systemd-boot.xbootldrMountPoint = "/boot"; }; customDiskImage = nodes: # python '' import os import subprocess import tempfile tmp_disk_image = tempfile.NamedTemporaryFile() subprocess.run([ "${nodes.machine.virtualisation.qemu.package}/bin/qemu-img", "create", "-f", "qcow2", "-b", "${nodes.machine.system.build.diskImage}/nixos.qcow2", "-F", "qcow2", tmp_disk_image.name, ]) # Set NIX_DISK_IMAGE so that the qemu script finds the right disk image. os.environ['NIX_DISK_IMAGE'] = tmp_disk_image.name ''; # Check that we are booting the default entry and not the generation with largest version number defaultEntry = { withBootCounting ? false, ... }: runTest { name = "systemd-boot-default-entry" + lib.optionalString withBootCounting "-with-boot-counting"; meta.maintainers = with lib.maintainers; [ julienmalka ]; nodes = { machine = { nodes, ... }: { imports = [ common ]; system.extraDependencies = [ nodes.other_machine.system.build.toplevel ]; boot.loader.systemd-boot.bootCounting.enable = withBootCounting; }; other_machine = { pkgs, ... }: { imports = [ common ]; boot.loader.systemd-boot.bootCounting.enable = withBootCounting; environment.systemPackages = [ pkgs.hello ]; }; }; testScript = { nodes, ... }: let orig = nodes.machine.system.build.toplevel; other = nodes.other_machine.system.build.toplevel; in # python '' ${testScriptPreamble} orig = "${orig}" other = "${other}" check_current_system(orig) # Switch to other configuration machine.succeed("nix-env -p /nix/var/nix/profiles/system --set ${other}") machine.succeed(f"{other}/bin/switch-to-configuration boot") # Rollback, default entry is now generation 1 machine.succeed("nix-env -p /nix/var/nix/profiles/system --rollback") machine.succeed(f"{orig}/bin/switch-to-configuration boot") machine.shutdown() machine.start() machine.wait_for_unit("multi-user.target") # Check that we booted generation 1 (default) # even though generation 2 comes first in alphabetical order check_current_system(orig) ''; }; garbage-collect-entry = { withBootCounting ? false, ... }: runTest ( { lib, ... }: { name = "systemd-boot-garbage-collect-entry" + lib.optionalString withBootCounting "-with-boot-counting"; meta.maintainers = with lib.maintainers; [ julienmalka ]; nodes = { inherit common; machine = { nodes, ... }: { imports = [ common ]; boot.loader.systemd-boot.bootCounting.enable = withBootCounting; boot.loader.systemd-boot.memtest86.enable = true; # These are configs for different nodes, but we'll use them here in `machine` system.extraDependencies = [ nodes.common.system.build.toplevel ]; }; }; testScript = { nodes, ... }: let baseSystem = nodes.common.system.build.toplevel; in # python '' ${testScriptPreamble} machine.succeed("nix-env -p /nix/var/nix/profiles/system --set ${baseSystem}") machine.succeed("nix-env -p /nix/var/nix/profiles/system --delete-generations 1") conf_file = check_generation(1)[0] new_conf_file = conf_file.replace(".conf", "+1-3.conf") # At this point generation 1 has already been marked as good so we reintroduce counters artificially ${lib.optionalString withBootCounting '' machine.succeed(f"mv {conf_file} {new_conf_file}") ''} machine.succeed("${baseSystem}/bin/switch-to-configuration boot") machine.fail( "grep --files-with-matches 'version Generation 1 NixOS' /boot/loader/entries/nixos-*.conf" ) check_generation(2) ''; } ); in { defaultEntry = defaultEntry { }; garbage-collect-entry = garbage-collect-entry { }; basic = runTest ( { lib, ... }: { name = "systemd-boot"; meta.maintainers = with lib.maintainers; [ danielfullmer julienmalka ]; nodes.machine = common; testScript = # python '' ${testScriptPreamble} machine.start() machine.wait_for_unit("multi-user.target") conf_file = check_generation(1)[0] machine.succeed(f"grep 'sort-key nixos' {conf_file}") # Ensure we actually booted using systemd-boot # Magic number is the vendor UUID used by systemd-boot. machine.succeed( "test -e /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f" ) # "bootctl install" should have created an EFI entry machine.succeed('efibootmgr | grep "Linux Boot Manager"') ''; } ); # Test that systemd-boot works with secure boot secureBoot = runTest ( { pkgs, lib, ... }: { name = "systemd-boot-secure-boot"; nodes.machine = { pkgs, ... }: { imports = [ common ]; environment.systemPackages = [ pkgs.sbctl ]; }; testScript = { nodes, ... }: let efiArch = pkgs.stdenv.hostPlatform.efiArch; in #python '' machine.start(allow_reboot=True) machine.wait_for_unit("multi-user.target") machine.succeed("sbctl create-keys") machine.succeed("sbctl enroll-keys --yes-this-might-brick-my-machine") machine.succeed('sbctl sign /boot/EFI/systemd/systemd-boot${efiArch}.efi') machine.succeed('sbctl sign /boot/EFI/BOOT/BOOT${lib.toUpper efiArch}.EFI') machine.succeed('sbctl sign /boot/EFI/nixos/*${nodes.machine.system.boot.loader.kernelFile}.efi') machine.reboot() assert "Secure Boot: enabled (user)" in machine.succeed("bootctl status") ''; } ); # TODO: Fix on aarch64-linux basicXbootldr = runTestOn [ "x86_64-linux" ] ( { lib, ... }: { name = "systemd-boot-xbootldr"; meta.maintainers = with lib.maintainers; [ sdht0 ]; nodes.machine = commonXbootldr; testScript = { nodes, ... }: #python '' ${testScriptPreamble} ${customDiskImage nodes} machine.start() machine.wait_for_unit("multi-user.target") machine.succeed("test -e /efi/EFI/systemd/systemd-bootx64.efi") check_generation(1) # Ensure we actually booted using systemd-boot # Magic number is the vendor UUID used by systemd-boot. machine.succeed( "test -e /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f" ) # "bootctl install" should have created an EFI entry machine.succeed('efibootmgr | grep "Linux Boot Manager"') ''; } ); # Check that specialisations create corresponding boot entries. specialisation = runTest ( { pkgs, lib, ... }: { name = "systemd-boot-specialisation"; meta.maintainers = with lib.maintainers; [ lukegb julienmalka ]; nodes.machine = { pkgs, lib, ... }: { imports = [ common ]; specialisation.something.configuration = { boot.loader.systemd-boot.sortKey = "something"; # Since qemu will dynamically create a devicetree blob when starting # up, it is not straight forward to create an export of that devicetree # blob without knowing before-hand all the flags we would pass to qemu # (we would then be able to use `dumpdtb`). Thus, the following config # will not boot, but it does allow us to assert that the boot entry has # the correct contents. boot.loader.systemd-boot.installDeviceTree = pkgs.stdenv.hostPlatform.isAarch64; hardware.deviceTree.name = "dummy.dtb"; hardware.deviceTree.package = lib.mkForce ( pkgs.runCommand "dummy-devicetree-package" { } '' mkdir -p $out cp ${pkgs.emptyFile} $out/dummy.dtb '' ); }; }; testScript = # python '' ${testScriptPreamble} machine.start() machine.wait_for_unit("multi-user.target") conf_files = check_generation(1, specialisation="something") machine.succeed( f"grep --fixed-strings --line-regexp 'sort-key something' {" ".join(conf_files)}" ) ${lib.optionalString pkgs.stdenv.hostPlatform.isAarch64 #python '' machine.succeed( fr"grep 'devicetree /EFI/nixos/[a-z0-9]\{32\}.*dummy' {" ".join(conf_files)}" ) '' } ''; } ); # Boot without having created an EFI entry--instead using default "/EFI/BOOT/BOOTX64.EFI" fallback = runTest ( { lib, ... }: { name = "systemd-boot-fallback"; meta.maintainers = with lib.maintainers; [ danielfullmer julienmalka ]; nodes.machine = { lib, ... }: { imports = [ common ]; boot.loader.efi.canTouchEfiVariables = lib.mkForce false; }; testScript = # python '' ${testScriptPreamble} machine.start() machine.wait_for_unit("multi-user.target") check_generation(1) # Ensure we actually booted using systemd-boot # Magic number is the vendor UUID used by systemd-boot. machine.succeed( "test -e /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f" ) # "bootctl install" should _not_ have created an EFI entry machine.fail('efibootmgr | grep "Linux Boot Manager"') ''; } ); # TODO: Fix on aarch64-linux update = runTestOn [ "x86_64-linux" ] ( { lib, ... }: { name = "systemd-boot-update"; meta.maintainers = with lib.maintainers; [ danielfullmer julienmalka ]; nodes.machine = common; testScript = # python '' machine.succeed("mount -o remount,rw /boot") def switch(): # Replace version inside sd-boot with something older. See SD_MAGIC in systemd src/boot/boot.c # Note: the sed replacement has to be length-preserving, because section length matters. machine.succeed( r""" find /boot -iname '*boot*.efi' -print0 | \ xargs -0 -I '{}' sed -i 's/#### LoaderInfo: systemd-boot [0-9]\(.*\) ####/#### LoaderInfo: systemd-boot 0\1 ####/' '{}' """ ) return machine.succeed("/run/current-system/bin/switch-to-configuration boot 2>&1") output = switch() assert 'to "/boot/EFI/systemd/systemd-bootx64.efi"' in output, "systemd-boot not copied to to /boot/EFI/systemd/systemd-bootx64.efi" assert 'to "/boot/EFI/BOOT/BOOTX64.EFI"' in output, "systemd-boot not copied to to /boot/EFI/BOOT/BOOTX64.EFI" with subtest("Test that updating works with lowercase bootx64.efi"): machine.succeed( # Move to tmp file name first, otherwise mv complains the new location is the same "mv /boot/EFI/BOOT/BOOTX64.EFI /boot/EFI/BOOT/bootx64.efi.new", "mv /boot/EFI/BOOT/bootx64.efi.new /boot/EFI/BOOT/bootx64.efi", ) output = switch() assert 'to "/boot/EFI/systemd/systemd-bootx64.efi"' in output, "systemd-boot not copied to to /boot/EFI/systemd/systemd-bootx64.efi" assert 'to "/boot/EFI/BOOT/BOOTX64.EFI"' in output, "systemd-boot not copied to to /boot/EFI/BOOT/BOOTX64.EFI" with subtest("Test that switching with an up-to-date bootloader is a no-op"): output = machine.succeed("/run/current-system/bin/switch-to-configuration boot 2>&1") assert "same boot loader version in place already" in output, "Expected bootctl to skip already-current binary" ''; } ); memtest86 = runTestOn [ "x86_64-linux" ] ( { lib, ... }: { name = "systemd-boot-memtest86"; meta.maintainers = with lib.maintainers; [ julienmalka ]; nodes.machine = { imports = [ common ]; boot.loader.systemd-boot.memtest86.enable = true; }; testScript = # python '' machine.succeed("test -e /boot/loader/entries/memtest86.conf") machine.succeed("test -e /boot/efi/memtest86/memtest.efi") ''; } ); netbootxyz = runTest ( { lib, ... }: { name = "systemd-boot-netbootxyz"; meta.maintainers = with lib.maintainers; [ julienmalka ]; nodes.machine = { imports = [ common ]; boot.loader.systemd-boot.netbootxyz.enable = true; }; testScript = # python '' machine.succeed("test -e /boot/loader/entries/netbootxyz.conf") machine.succeed("test -e /boot/efi/netbootxyz/netboot.xyz.efi") ''; } ); edk2-uefi-shell = runTest ( { lib, ... }: { name = "systemd-boot-edk2-uefi-shell"; meta.maintainers = with lib.maintainers; [ iFreilicht ]; nodes.machine = { ... }: { imports = [ common ]; boot.loader.systemd-boot.edk2-uefi-shell.enable = true; }; testScript = # python '' machine.succeed("test -e /boot/loader/entries/edk2-uefi-shell.conf") machine.succeed("test -e /boot/efi/edk2-uefi-shell/shell.efi") ''; } ); windows = runTest ( { lib, ... }: { name = "systemd-boot-windows"; meta.maintainers = with lib.maintainers; [ iFreilicht ]; nodes.machine = { ... }: { imports = [ common ]; boot.loader.systemd-boot.windows = { "7" = { efiDeviceHandle = "HD0c1"; sortKey = "before_all_others"; }; "Ten".efiDeviceHandle = "FS0"; "11" = { title = "Title with-_-punctuation ...?!"; efiDeviceHandle = "HD0d4"; sortKey = "zzz"; }; }; }; testScript = # python '' machine.succeed("test -e /boot/efi/edk2-uefi-shell/shell.efi") machine.succeed("test -e /boot/loader/entries/windows_7.conf") machine.succeed("test -e /boot/loader/entries/windows_Ten.conf") machine.succeed("test -e /boot/loader/entries/windows_11.conf") machine.succeed("grep 'efi /efi/edk2-uefi-shell/shell.efi' /boot/loader/entries/windows_7.conf") machine.succeed("grep 'efi /efi/edk2-uefi-shell/shell.efi' /boot/loader/entries/windows_Ten.conf") machine.succeed("grep 'efi /efi/edk2-uefi-shell/shell.efi' /boot/loader/entries/windows_11.conf") machine.succeed("grep 'HD0c1:EFI\\\\Microsoft\\\\Boot\\\\Bootmgfw.efi' /boot/loader/entries/windows_7.conf") machine.succeed("grep 'FS0:EFI\\\\Microsoft\\\\Boot\\\\Bootmgfw.efi' /boot/loader/entries/windows_Ten.conf") machine.succeed("grep 'HD0d4:EFI\\\\Microsoft\\\\Boot\\\\Bootmgfw.efi' /boot/loader/entries/windows_11.conf") machine.succeed("grep 'sort-key before_all_others' /boot/loader/entries/windows_7.conf") machine.succeed("grep 'sort-key o_windows_Ten' /boot/loader/entries/windows_Ten.conf") machine.succeed("grep 'sort-key zzz' /boot/loader/entries/windows_11.conf") machine.succeed("grep 'title Windows 7' /boot/loader/entries/windows_7.conf") machine.succeed("grep 'title Windows Ten' /boot/loader/entries/windows_Ten.conf") machine.succeed('grep "title Title with-_-punctuation ...?!" /boot/loader/entries/windows_11.conf') ''; } ); memtestSortKey = runTest ( { lib, ... }: { name = "systemd-boot-memtest-sortkey"; meta.maintainers = with lib.maintainers; [ julienmalka ]; nodes.machine = { imports = [ common ]; boot.loader.systemd-boot.memtest86.enable = true; boot.loader.systemd-boot.memtest86.sortKey = "apple"; }; testScript = # python '' machine.succeed("test -e /boot/loader/entries/memtest86.conf") machine.succeed("test -e /boot/efi/memtest86/memtest.efi") machine.succeed("grep 'sort-key apple' /boot/loader/entries/memtest86.conf") ''; } ); # TODO: Fix on aarch64-linux entryFilenameXbootldr = runTestOn [ "x86_64-linux" ] ( { lib, ... }: { name = "systemd-boot-entry-filename-xbootldr"; meta.maintainers = with lib.maintainers; [ sdht0 ]; nodes.machine = { imports = [ commonXbootldr ]; boot.loader.systemd-boot.memtest86.enable = true; }; testScript = { nodes, ... }: # python '' ${customDiskImage nodes} machine.start() machine.wait_for_unit("multi-user.target") machine.succeed("test -e /efi/EFI/systemd/systemd-bootx64.efi") machine.succeed("test -e /boot/loader/entries/memtest86.conf") machine.succeed("test -e /boot/EFI/memtest86/memtest.efi") ''; } ); extraEntries = runTest ( { lib, ... }: { name = "systemd-boot-extra-entries"; meta.maintainers = with lib.maintainers; [ julienmalka ]; nodes.machine = { imports = [ common ]; boot.loader.systemd-boot.extraEntries = { "banana.conf" = '' title banana ''; }; }; testScript = # python '' machine.succeed("test -e /boot/loader/entries/banana.conf") machine.succeed("test -e /boot/efi/nixos/.extra-files/loader/entries/banana.conf") ''; } ); extraFiles = runTest ( { lib, ... }: { name = "systemd-boot-extra-files"; meta.maintainers = with lib.maintainers; [ julienmalka ]; nodes.machine = { pkgs, ... }: { imports = [ common ]; boot.loader.systemd-boot.extraFiles = { "efi/fruits/tomato.efi" = pkgs.netbootxyz-efi; }; }; testScript = # python '' machine.succeed("test -e /boot/efi/fruits/tomato.efi") machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") ''; } ); switch-test = runTest ( { lib, ... }: { name = "systemd-boot-switch-test"; meta.maintainers = with lib.maintainers; [ julienmalka ]; nodes = { inherit common; machine = { pkgs, nodes, ... }: { imports = [ common ]; boot.loader.systemd-boot.extraFiles = { "efi/fruits/tomato.efi" = pkgs.netbootxyz-efi; }; # These are configs for different nodes, but we'll use them here in `machine` system.extraDependencies = [ nodes.common.system.build.toplevel nodes.with_netbootxyz.system.build.toplevel ]; }; with_netbootxyz = { imports = [ common ]; boot.loader.systemd-boot.netbootxyz.enable = true; }; }; testScript = { nodes, ... }: let originalSystem = nodes.machine.system.build.toplevel; baseSystem = nodes.common.system.build.toplevel; finalSystem = nodes.with_netbootxyz.system.build.toplevel; in # python '' machine.succeed("test -e /boot/efi/fruits/tomato.efi") machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") with subtest("remove files when no longer needed"): machine.succeed("${baseSystem}/bin/switch-to-configuration boot") machine.fail("test -e /boot/efi/fruits/tomato.efi") machine.fail("test -d /boot/efi/fruits") machine.succeed("test -d /boot/efi/nixos/.extra-files") machine.fail("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") machine.fail("test -d /boot/efi/nixos/.extra-files/efi/fruits") with subtest("files are added back when needed again"): machine.succeed("${originalSystem}/bin/switch-to-configuration boot") machine.succeed("test -e /boot/efi/fruits/tomato.efi") machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") with subtest("simultaneously removing and adding files works"): machine.succeed("${finalSystem}/bin/switch-to-configuration boot") machine.fail("test -e /boot/efi/fruits/tomato.efi") machine.fail("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") 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/nixos/.extra-files/loader/entries/netbootxyz.conf") machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/netbootxyz/netboot.xyz.efi") ''; } ); bootCounting = let baseConfig = { imports = [ common ]; boot.loader.systemd-boot.bootCounting = { enable = true; tries = 2; }; }; in runTest { name = "systemd-boot-counting"; meta.maintainers = with lib.maintainers; [ julienmalka ]; nodes = { machine = { nodes, ... }: { imports = [ baseConfig ]; system.extraDependencies = [ nodes.bad_machine.system.build.toplevel nodes.unused_machine.system.build.toplevel ]; }; unused_machine = { pkgs, ... }: { imports = [ baseConfig ]; # Distinguish this closure from `machine` without pulling in extra deps. environment.systemPackages = [ pkgs.hello ]; }; bad_machine = { imports = [ baseConfig ]; systemd.services."failing" = { script = "exit 1"; requiredBy = [ "boot-complete.target" ]; before = [ "boot-complete.target" ]; serviceConfig.Type = "oneshot"; }; }; }; testScript = { nodes, ... }: let orig = nodes.machine.system.build.toplevel; bad = nodes.bad_machine.system.build.toplevel; unused = nodes.unused_machine.system.build.toplevel; in # python '' ${testScriptPreamble} orig = "${orig}" bad = "${bad}" unused = "${unused}" machine.start(allow_reboot=True) # Ensure we booted using an entry with counters enabled machine.succeed( "test -e /sys/firmware/efi/efivars/LoaderBootCountPath-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f" ) # systemd-bless-boot should have already removed the "+2" suffix from the boot entry machine.wait_for_unit("systemd-bless-boot.service") conf_file = check_generation(1) check_current_system(orig) print(machine.succeed("cat /boot/loader/entries/*.conf")) # Register the bad configuration as generation 2 and another good # configuration as generation 3, then make generation 2 the default. # This verifies that `preferred` in loader.conf selects gen 2 even # though gen 3 sorts higher, and that once gen 2 is marked bad we # fall back to the newest non-bad entry (gen 3). machine.succeed("nix-env -p /nix/var/nix/profiles/system --set ${bad}") machine.succeed("nix-env -p /nix/var/nix/profiles/system --set ${unused}") machine.succeed(f"{bad}/bin/switch-to-configuration boot") # Ensure new bootloader entries have initialized counters check_generation(1) check_generation(2, 2) check_generation(3, 2) machine.reboot() machine.wait_for_unit("multi-user.target") check_current_system(bad) check_generation(1) check_generation(2, 1, 1) check_generation(3, 2) machine.reboot() machine.wait_for_unit("multi-user.target") check_current_system(bad) check_generation(1) check_generation(2, 0, 2) check_generation(3, 2) machine.reboot() machine.wait_for_unit("multi-user.target") # Gen 2 has exhausted its tries; `preferred` skips it and `default # nixos-*` resolves to the newest non-bad entry, which is gen 3. check_current_system(unused) machine.wait_for_unit("systemd-bless-boot.service") check_generation(2, 0, 2) check_generation(3) ''; }; bootCountingSpecialisation = let baseConfig = { imports = [ common ]; boot.loader.systemd-boot.bootCounting = { enable = true; tries = 2; }; }; specialisationName = "+something+-+that+-+breaks-parsing+-+"; in runTest { name = "systemd-boot-counting-specialisation"; meta.maintainers = with lib.maintainers; [ julienmalka ]; nodes = { machine = { nodes, lib, ... }: { imports = [ baseConfig ]; specialisation.${specialisationName}.configuration = { boot.loader.systemd-boot.sortKey = "something"; }; }; }; testScript = { nodes, ... }: let orig = nodes.machine.system.build.toplevel; in # python '' ${testScriptPreamble} orig = "${orig}" # Ensure we booted using an entry with counters enabled machine.succeed( "test -e /sys/firmware/efi/efivars/LoaderBootCountPath-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f" ) check_generation(1) check_current_system(orig) # Ensure the bootloader entry for the specialisation has initialized the boot counter check_generation(1, 2, specialisation="${specialisationName}") ''; }; defaultEntryWithBootCounting = defaultEntry { withBootCounting = true; }; garbageCollectEntryWithBootCounting = garbage-collect-entry { withBootCounting = true; }; }