2 system ? builtins.currentSystem,
4 pkgs ? import ../.. { inherit system config; },
7 with import ../lib/testing-python.nix { inherit system pkgs; };
12 virtualisation.useBootLoader = true;
13 virtualisation.useEFIBoot = true;
14 boot.loader.systemd-boot.enable = true;
15 boot.loader.efi.canTouchEfiVariables = true;
16 environment.systemPackages = [ pkgs.efibootmgr ];
17 system.switch.enable = true;
28 diskImage = import ../lib/make-disk-image.nix {
29 inherit config lib pkgs;
32 partitionTableType = "efixbootldr";
34 installBootLoader = true;
39 virtualisation.useBootLoader = lib.mkForce false; # Only way to tell qemu-vm not to create the default system image
40 virtualisation.directBoot.enable = false; # But don't direct boot either because we're testing systemd-boot
42 system.build.diskImage = diskImage; # Use custom disk image with an XBOOTLDR partition
43 virtualisation.efi.variables = "${diskImage}/efi-vars.fd";
45 virtualisation.useDefaultFilesystems = false; # Needs custom setup for `diskImage`
46 virtualisation.bootPartition = null;
47 virtualisation.fileSystems = {
64 boot.loader.systemd-boot.enable = true;
65 boot.loader.efi.efiSysMountPoint = "/efi";
66 boot.loader.systemd-boot.xbootldrMountPoint = "/boot";
69 customDiskImage = nodes: ''
74 tmp_disk_image = tempfile.NamedTemporaryFile()
77 "${nodes.machine.virtualisation.qemu.package}/bin/qemu-img",
82 "${nodes.machine.system.build.diskImage}/nixos.qcow2",
88 # Set NIX_DISK_IMAGE so that the qemu script finds the right disk image.
89 os.environ['NIX_DISK_IMAGE'] = tmp_disk_image.name
94 name = "systemd-boot";
95 meta.maintainers = with pkgs.lib.maintainers; [
100 nodes.machine = common;
104 machine.wait_for_unit("multi-user.target")
106 machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf")
107 machine.succeed("grep 'sort-key nixos' /boot/loader/entries/nixos-generation-1.conf")
109 # Ensure we actually booted using systemd-boot
110 # Magic number is the vendor UUID used by systemd-boot.
112 "test -e /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"
115 # "bootctl install" should have created an EFI entry
116 machine.succeed('efibootmgr | grep "Linux Boot Manager"')
120 # Test that systemd-boot works with secure boot
121 secureBoot = makeTest {
122 name = "systemd-boot-secure-boot";
125 imports = [ common ];
126 environment.systemPackages = [ pkgs.sbctl ];
127 virtualisation.useSecureBoot = true;
132 efiArch = pkgs.stdenv.hostPlatform.efiArch;
136 machine.start(allow_reboot=True)
137 machine.wait_for_unit("multi-user.target")
139 machine.succeed("sbctl create-keys")
140 machine.succeed("sbctl enroll-keys --yes-this-might-brick-my-machine")
141 machine.succeed('sbctl sign /boot/EFI/systemd/systemd-boot${efiArch}.efi')
142 machine.succeed('sbctl sign /boot/EFI/BOOT/BOOT${toUpper efiArch}.EFI')
143 machine.succeed('sbctl sign /boot/EFI/nixos/*${nodes.machine.system.boot.loader.kernelFile}.efi')
147 assert "Secure Boot: enabled (user)" in machine.succeed("bootctl status")
151 basicXbootldr = makeTest {
152 name = "systemd-boot-xbootldr";
153 meta.maintainers = with pkgs.lib.maintainers; [ sdht0 ];
155 nodes.machine = commonXbootldr;
160 ${customDiskImage nodes}
163 machine.wait_for_unit("multi-user.target")
165 machine.succeed("test -e /efi/EFI/systemd/systemd-bootx64.efi")
166 machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf")
168 # Ensure we actually booted using systemd-boot
169 # Magic number is the vendor UUID used by systemd-boot.
171 "test -e /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"
174 # "bootctl install" should have created an EFI entry
175 machine.succeed('efibootmgr | grep "Linux Boot Manager"')
179 # Check that specialisations create corresponding boot entries.
180 specialisation = makeTest {
181 name = "systemd-boot-specialisation";
182 meta.maintainers = with pkgs.lib.maintainers; [
190 imports = [ common ];
191 specialisation.something.configuration = {
192 boot.loader.systemd-boot.sortKey = "something";
194 # Since qemu will dynamically create a devicetree blob when starting
195 # up, it is not straight forward to create an export of that devicetree
196 # blob without knowing before-hand all the flags we would pass to qemu
197 # (we would then be able to use `dumpdtb`). Thus, the following config
198 # will not boot, but it does allow us to assert that the boot entry has
199 # the correct contents.
200 boot.loader.systemd-boot.installDeviceTree = pkgs.stdenv.hostPlatform.isAarch64;
201 hardware.deviceTree.name = "dummy.dtb";
202 hardware.deviceTree.package = lib.mkForce (
203 pkgs.runCommand "dummy-devicetree-package" { } ''
205 cp ${pkgs.emptyFile} $out/dummy.dtb
215 machine.wait_for_unit("multi-user.target")
218 "test -e /boot/loader/entries/nixos-generation-1-specialisation-something.conf"
221 "grep -q 'title NixOS (something)' /boot/loader/entries/nixos-generation-1-specialisation-something.conf"
224 "grep 'sort-key something' /boot/loader/entries/nixos-generation-1-specialisation-something.conf"
227 + pkgs.lib.optionalString pkgs.stdenv.hostPlatform.isAarch64 ''
229 r"grep 'devicetree /EFI/nixos/[a-z0-9]\{32\}.*dummy' /boot/loader/entries/nixos-generation-1-specialisation-something.conf"
234 # Boot without having created an EFI entry--instead using default "/EFI/BOOT/BOOTX64.EFI"
235 fallback = makeTest {
236 name = "systemd-boot-fallback";
237 meta.maintainers = with pkgs.lib.maintainers; [
245 imports = [ common ];
246 boot.loader.efi.canTouchEfiVariables = mkForce false;
251 machine.wait_for_unit("multi-user.target")
253 machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf")
255 # Ensure we actually booted using systemd-boot
256 # Magic number is the vendor UUID used by systemd-boot.
258 "test -e /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"
261 # "bootctl install" should _not_ have created an EFI entry
262 machine.fail('efibootmgr | grep "Linux Boot Manager"')
267 name = "systemd-boot-update";
268 meta.maintainers = with pkgs.lib.maintainers; [
273 nodes.machine = common;
276 machine.succeed("mount -o remount,rw /boot")
279 # Replace version inside sd-boot with something older. See magic[] string in systemd src/boot/efi/boot.c
282 find /boot -iname '*boot*.efi' -print0 | \
283 xargs -0 -I '{}' sed -i 's/#### LoaderInfo: systemd-boot .* ####/#### LoaderInfo: systemd-boot 000.0-1-notnixos ####/' '{}'
286 return machine.succeed("/run/current-system/bin/switch-to-configuration boot 2>&1")
289 assert "updating systemd-boot from 000.0-1-notnixos to " in output, "Couldn't find systemd-boot update message"
290 assert 'to "/boot/EFI/systemd/systemd-bootx64.efi"' in output, "systemd-boot not copied to to /boot/EFI/systemd/systemd-bootx64.efi"
291 assert 'to "/boot/EFI/BOOT/BOOTX64.EFI"' in output, "systemd-boot not copied to to /boot/EFI/BOOT/BOOTX64.EFI"
293 with subtest("Test that updating works with lowercase bootx64.efi"):
295 # Move to tmp file name first, otherwise mv complains the new location is the same
296 "mv /boot/EFI/BOOT/BOOTX64.EFI /boot/EFI/BOOT/bootx64.efi.new",
297 "mv /boot/EFI/BOOT/bootx64.efi.new /boot/EFI/BOOT/bootx64.efi",
300 assert "updating systemd-boot from 000.0-1-notnixos to " in output, "Couldn't find systemd-boot update message"
301 assert 'to "/boot/EFI/systemd/systemd-bootx64.efi"' in output, "systemd-boot not copied to to /boot/EFI/systemd/systemd-bootx64.efi"
302 assert 'to "/boot/EFI/BOOT/BOOTX64.EFI"' in output, "systemd-boot not copied to to /boot/EFI/BOOT/BOOTX64.EFI"
308 optionalAttrs (meta.availableOn { inherit system; } pkgs.memtest86plus) (makeTest {
309 name = "systemd-boot-memtest86";
310 meta.maintainers = with maintainers; [ julienmalka ];
315 imports = [ common ];
316 boot.loader.systemd-boot.memtest86.enable = true;
320 machine.succeed("test -e /boot/loader/entries/memtest86.conf")
321 machine.succeed("test -e /boot/efi/memtest86/memtest.efi")
325 netbootxyz = makeTest {
326 name = "systemd-boot-netbootxyz";
327 meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
332 imports = [ common ];
333 boot.loader.systemd-boot.netbootxyz.enable = true;
337 machine.succeed("test -e /boot/loader/entries/netbootxyz.conf")
338 machine.succeed("test -e /boot/efi/netbootxyz/netboot.xyz.efi")
342 edk2-uefi-shell = makeTest {
343 name = "systemd-boot-edk2-uefi-shell";
344 meta.maintainers = with pkgs.lib.maintainers; [ iFreilicht ];
346 nodes.machine = { ... }: {
347 imports = [ common ];
348 boot.loader.systemd-boot.edk2-uefi-shell.enable = true;
352 machine.succeed("test -e /boot/loader/entries/edk2-uefi-shell.conf")
353 machine.succeed("test -e /boot/efi/edk2-uefi-shell/shell.efi")
358 name = "systemd-boot-windows";
359 meta.maintainers = with pkgs.lib.maintainers; [ iFreilicht ];
361 nodes.machine = { ... }: {
362 imports = [ common ];
363 boot.loader.systemd-boot.windows = {
365 efiDeviceHandle = "HD0c1";
366 sortKey = "before_all_others";
368 "Ten".efiDeviceHandle = "FS0";
370 title = "Title with-_-punctuation ...?!";
371 efiDeviceHandle = "HD0d4";
378 machine.succeed("test -e /boot/efi/edk2-uefi-shell/shell.efi")
380 machine.succeed("test -e /boot/loader/entries/windows_7.conf")
381 machine.succeed("test -e /boot/loader/entries/windows_Ten.conf")
382 machine.succeed("test -e /boot/loader/entries/windows_11.conf")
384 machine.succeed("grep 'efi /efi/edk2-uefi-shell/shell.efi' /boot/loader/entries/windows_7.conf")
385 machine.succeed("grep 'efi /efi/edk2-uefi-shell/shell.efi' /boot/loader/entries/windows_Ten.conf")
386 machine.succeed("grep 'efi /efi/edk2-uefi-shell/shell.efi' /boot/loader/entries/windows_11.conf")
388 machine.succeed("grep 'HD0c1:EFI\\\\Microsoft\\\\Boot\\\\Bootmgfw.efi' /boot/loader/entries/windows_7.conf")
389 machine.succeed("grep 'FS0:EFI\\\\Microsoft\\\\Boot\\\\Bootmgfw.efi' /boot/loader/entries/windows_Ten.conf")
390 machine.succeed("grep 'HD0d4:EFI\\\\Microsoft\\\\Boot\\\\Bootmgfw.efi' /boot/loader/entries/windows_11.conf")
392 machine.succeed("grep 'sort-key before_all_others' /boot/loader/entries/windows_7.conf")
393 machine.succeed("grep 'sort-key o_windows_Ten' /boot/loader/entries/windows_Ten.conf")
394 machine.succeed("grep 'sort-key zzz' /boot/loader/entries/windows_11.conf")
396 machine.succeed("grep 'title Windows 7' /boot/loader/entries/windows_7.conf")
397 machine.succeed("grep 'title Windows Ten' /boot/loader/entries/windows_Ten.conf")
398 machine.succeed('grep "title Title with-_-punctuation ...?!" /boot/loader/entries/windows_11.conf')
402 memtestSortKey = makeTest {
403 name = "systemd-boot-memtest-sortkey";
404 meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
409 imports = [ common ];
410 boot.loader.systemd-boot.memtest86.enable = true;
411 boot.loader.systemd-boot.memtest86.sortKey = "apple";
415 machine.succeed("test -e /boot/loader/entries/memtest86.conf")
416 machine.succeed("test -e /boot/efi/memtest86/memtest.efi")
417 machine.succeed("grep 'sort-key apple' /boot/loader/entries/memtest86.conf")
421 entryFilenameXbootldr = makeTest {
422 name = "systemd-boot-entry-filename-xbootldr";
423 meta.maintainers = with pkgs.lib.maintainers; [ sdht0 ];
428 imports = [ commonXbootldr ];
429 boot.loader.systemd-boot.memtest86.enable = true;
435 ${customDiskImage nodes}
438 machine.wait_for_unit("multi-user.target")
440 machine.succeed("test -e /efi/EFI/systemd/systemd-bootx64.efi")
441 machine.succeed("test -e /boot/loader/entries/memtest86.conf")
442 machine.succeed("test -e /boot/EFI/memtest86/memtest.efi")
446 extraEntries = makeTest {
447 name = "systemd-boot-extra-entries";
448 meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
453 imports = [ common ];
454 boot.loader.systemd-boot.extraEntries = {
462 machine.succeed("test -e /boot/loader/entries/banana.conf")
463 machine.succeed("test -e /boot/efi/nixos/.extra-files/loader/entries/banana.conf")
467 extraFiles = makeTest {
468 name = "systemd-boot-extra-files";
469 meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
474 imports = [ common ];
475 boot.loader.systemd-boot.extraFiles = {
476 "efi/fruits/tomato.efi" = pkgs.netbootxyz-efi;
481 machine.succeed("test -e /boot/efi/fruits/tomato.efi")
482 machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi")
486 switch-test = makeTest {
487 name = "systemd-boot-switch-test";
488 meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
494 { pkgs, nodes, ... }:
496 imports = [ common ];
497 boot.loader.systemd-boot.extraFiles = {
498 "efi/fruits/tomato.efi" = pkgs.netbootxyz-efi;
501 # These are configs for different nodes, but we'll use them here in `machine`
502 system.extraDependencies = [
503 nodes.common.system.build.toplevel
504 nodes.with_netbootxyz.system.build.toplevel
511 imports = [ common ];
512 boot.loader.systemd-boot.netbootxyz.enable = true;
519 originalSystem = nodes.machine.system.build.toplevel;
520 baseSystem = nodes.common.system.build.toplevel;
521 finalSystem = nodes.with_netbootxyz.system.build.toplevel;
524 machine.succeed("test -e /boot/efi/fruits/tomato.efi")
525 machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi")
527 with subtest("remove files when no longer needed"):
528 machine.succeed("${baseSystem}/bin/switch-to-configuration boot")
529 machine.fail("test -e /boot/efi/fruits/tomato.efi")
530 machine.fail("test -d /boot/efi/fruits")
531 machine.succeed("test -d /boot/efi/nixos/.extra-files")
532 machine.fail("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi")
533 machine.fail("test -d /boot/efi/nixos/.extra-files/efi/fruits")
535 with subtest("files are added back when needed again"):
536 machine.succeed("${originalSystem}/bin/switch-to-configuration boot")
537 machine.succeed("test -e /boot/efi/fruits/tomato.efi")
538 machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi")
540 with subtest("simultaneously removing and adding files works"):
541 machine.succeed("${finalSystem}/bin/switch-to-configuration boot")
542 machine.fail("test -e /boot/efi/fruits/tomato.efi")
543 machine.fail("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi")
544 machine.succeed("test -e /boot/loader/entries/netbootxyz.conf")
545 machine.succeed("test -e /boot/efi/netbootxyz/netboot.xyz.efi")
546 machine.succeed("test -e /boot/efi/nixos/.extra-files/loader/entries/netbootxyz.conf")
547 machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/netbootxyz/netboot.xyz.efi")
551 garbage-collect-entry = makeTest {
552 name = "systemd-boot-garbage-collect-entry";
553 meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
558 { pkgs, nodes, ... }:
560 imports = [ common ];
562 # These are configs for different nodes, but we'll use them here in `machine`
563 system.extraDependencies = [
564 nodes.common.system.build.toplevel
572 baseSystem = nodes.common.system.build.toplevel;
575 machine.succeed("nix-env -p /nix/var/nix/profiles/system --set ${baseSystem}")
576 machine.succeed("nix-env -p /nix/var/nix/profiles/system --delete-generations 1")
577 machine.succeed("${baseSystem}/bin/switch-to-configuration boot")
578 machine.fail("test -e /boot/loader/entries/nixos-generation-1.conf")
579 machine.succeed("test -e /boot/loader/entries/nixos-generation-2.conf")
583 no-bootspec = makeTest {
584 name = "systemd-boot-no-bootspec";
585 meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
588 imports = [ common ];
589 boot.bootspec.enable = false;
594 machine.wait_for_unit("multi-user.target")