at 25.11-pre 20 kB view raw
1{ 2 system ? builtins.currentSystem, 3 config ? { }, 4 pkgs ? import ../.. { inherit system config; }, 5}: 6 7with import ../lib/testing-python.nix { inherit system pkgs; }; 8with pkgs.lib; 9 10let 11 common = { 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; 18 }; 19 20 commonXbootldr = 21 { 22 config, 23 lib, 24 pkgs, 25 ... 26 }: 27 let 28 diskImage = import ../lib/make-disk-image.nix { 29 inherit config lib pkgs; 30 label = "nixos"; 31 format = "qcow2"; 32 partitionTableType = "efixbootldr"; 33 touchEFIVars = true; 34 installBootLoader = true; 35 }; 36 in 37 { 38 imports = [ common ]; 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 41 42 system.build.diskImage = diskImage; # Use custom disk image with an XBOOTLDR partition 43 virtualisation.efi.variables = "${diskImage}/efi-vars.fd"; 44 45 virtualisation.useDefaultFilesystems = false; # Needs custom setup for `diskImage` 46 virtualisation.bootPartition = null; 47 virtualisation.fileSystems = { 48 "/" = { 49 device = "/dev/vda3"; 50 fsType = "ext4"; 51 }; 52 "/boot" = { 53 device = "/dev/vda2"; 54 fsType = "vfat"; 55 noCheck = true; 56 }; 57 "/efi" = { 58 device = "/dev/vda1"; 59 fsType = "vfat"; 60 noCheck = true; 61 }; 62 }; 63 64 boot.loader.systemd-boot.enable = true; 65 boot.loader.efi.efiSysMountPoint = "/efi"; 66 boot.loader.systemd-boot.xbootldrMountPoint = "/boot"; 67 }; 68 69 customDiskImage = nodes: '' 70 import os 71 import subprocess 72 import tempfile 73 74 tmp_disk_image = tempfile.NamedTemporaryFile() 75 76 subprocess.run([ 77 "${nodes.machine.virtualisation.qemu.package}/bin/qemu-img", 78 "create", 79 "-f", 80 "qcow2", 81 "-b", 82 "${nodes.machine.system.build.diskImage}/nixos.qcow2", 83 "-F", 84 "qcow2", 85 tmp_disk_image.name, 86 ]) 87 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 90 ''; 91in 92{ 93 basic = makeTest { 94 name = "systemd-boot"; 95 meta.maintainers = with pkgs.lib.maintainers; [ 96 danielfullmer 97 julienmalka 98 ]; 99 100 nodes.machine = common; 101 102 testScript = '' 103 machine.start() 104 machine.wait_for_unit("multi-user.target") 105 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") 108 109 # Ensure we actually booted using systemd-boot 110 # Magic number is the vendor UUID used by systemd-boot. 111 machine.succeed( 112 "test -e /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f" 113 ) 114 115 # "bootctl install" should have created an EFI entry 116 machine.succeed('efibootmgr | grep "Linux Boot Manager"') 117 ''; 118 }; 119 120 # Test that systemd-boot works with secure boot 121 secureBoot = makeTest { 122 name = "systemd-boot-secure-boot"; 123 124 nodes.machine = { 125 imports = [ common ]; 126 environment.systemPackages = [ pkgs.sbctl ]; 127 virtualisation.useSecureBoot = true; 128 }; 129 130 testScript = 131 let 132 efiArch = pkgs.stdenv.hostPlatform.efiArch; 133 in 134 { nodes, ... }: 135 '' 136 machine.start(allow_reboot=True) 137 machine.wait_for_unit("multi-user.target") 138 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') 144 145 machine.reboot() 146 147 assert "Secure Boot: enabled (user)" in machine.succeed("bootctl status") 148 ''; 149 }; 150 151 basicXbootldr = makeTest { 152 name = "systemd-boot-xbootldr"; 153 meta.maintainers = with pkgs.lib.maintainers; [ sdht0 ]; 154 155 nodes.machine = commonXbootldr; 156 157 testScript = 158 { nodes, ... }: 159 '' 160 ${customDiskImage nodes} 161 162 machine.start() 163 machine.wait_for_unit("multi-user.target") 164 165 machine.succeed("test -e /efi/EFI/systemd/systemd-bootx64.efi") 166 machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf") 167 168 # Ensure we actually booted using systemd-boot 169 # Magic number is the vendor UUID used by systemd-boot. 170 machine.succeed( 171 "test -e /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f" 172 ) 173 174 # "bootctl install" should have created an EFI entry 175 machine.succeed('efibootmgr | grep "Linux Boot Manager"') 176 ''; 177 }; 178 179 # Check that specialisations create corresponding boot entries. 180 specialisation = makeTest { 181 name = "systemd-boot-specialisation"; 182 meta.maintainers = with pkgs.lib.maintainers; [ 183 lukegb 184 julienmalka 185 ]; 186 187 nodes.machine = 188 { pkgs, lib, ... }: 189 { 190 imports = [ common ]; 191 specialisation.something.configuration = { 192 boot.loader.systemd-boot.sortKey = "something"; 193 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" { } '' 204 mkdir -p $out 205 cp ${pkgs.emptyFile} $out/dummy.dtb 206 '' 207 ); 208 }; 209 }; 210 211 testScript = 212 { nodes, ... }: 213 '' 214 machine.start() 215 machine.wait_for_unit("multi-user.target") 216 217 machine.succeed( 218 "test -e /boot/loader/entries/nixos-generation-1-specialisation-something.conf" 219 ) 220 machine.succeed( 221 "grep -q 'title NixOS (something)' /boot/loader/entries/nixos-generation-1-specialisation-something.conf" 222 ) 223 machine.succeed( 224 "grep 'sort-key something' /boot/loader/entries/nixos-generation-1-specialisation-something.conf" 225 ) 226 '' 227 + pkgs.lib.optionalString pkgs.stdenv.hostPlatform.isAarch64 '' 228 machine.succeed( 229 r"grep 'devicetree /EFI/nixos/[a-z0-9]\{32\}.*dummy' /boot/loader/entries/nixos-generation-1-specialisation-something.conf" 230 ) 231 ''; 232 }; 233 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; [ 238 danielfullmer 239 julienmalka 240 ]; 241 242 nodes.machine = 243 { pkgs, lib, ... }: 244 { 245 imports = [ common ]; 246 boot.loader.efi.canTouchEfiVariables = mkForce false; 247 }; 248 249 testScript = '' 250 machine.start() 251 machine.wait_for_unit("multi-user.target") 252 253 machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf") 254 255 # Ensure we actually booted using systemd-boot 256 # Magic number is the vendor UUID used by systemd-boot. 257 machine.succeed( 258 "test -e /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f" 259 ) 260 261 # "bootctl install" should _not_ have created an EFI entry 262 machine.fail('efibootmgr | grep "Linux Boot Manager"') 263 ''; 264 }; 265 266 update = makeTest { 267 name = "systemd-boot-update"; 268 meta.maintainers = with pkgs.lib.maintainers; [ 269 danielfullmer 270 julienmalka 271 ]; 272 273 nodes.machine = common; 274 275 testScript = '' 276 machine.succeed("mount -o remount,rw /boot") 277 278 def switch(): 279 # Replace version inside sd-boot with something older. See magic[] string in systemd src/boot/efi/boot.c 280 machine.succeed( 281 """ 282 find /boot -iname '*boot*.efi' -print0 | \ 283 xargs -0 -I '{}' sed -i 's/#### LoaderInfo: systemd-boot .* ####/#### LoaderInfo: systemd-boot 000.0-1-notnixos ####/' '{}' 284 """ 285 ) 286 return machine.succeed("/run/current-system/bin/switch-to-configuration boot 2>&1") 287 288 output = switch() 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" 292 293 with subtest("Test that updating works with lowercase bootx64.efi"): 294 machine.succeed( 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", 298 ) 299 output = switch() 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" 303 ''; 304 }; 305 306 memtest86 = 307 with pkgs.lib; 308 optionalAttrs (meta.availableOn { inherit system; } pkgs.memtest86plus) (makeTest { 309 name = "systemd-boot-memtest86"; 310 meta.maintainers = with maintainers; [ julienmalka ]; 311 312 nodes.machine = 313 { pkgs, lib, ... }: 314 { 315 imports = [ common ]; 316 boot.loader.systemd-boot.memtest86.enable = true; 317 }; 318 319 testScript = '' 320 machine.succeed("test -e /boot/loader/entries/memtest86.conf") 321 machine.succeed("test -e /boot/efi/memtest86/memtest.efi") 322 ''; 323 }); 324 325 netbootxyz = makeTest { 326 name = "systemd-boot-netbootxyz"; 327 meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ]; 328 329 nodes.machine = 330 { pkgs, lib, ... }: 331 { 332 imports = [ common ]; 333 boot.loader.systemd-boot.netbootxyz.enable = true; 334 }; 335 336 testScript = '' 337 machine.succeed("test -e /boot/loader/entries/netbootxyz.conf") 338 machine.succeed("test -e /boot/efi/netbootxyz/netboot.xyz.efi") 339 ''; 340 }; 341 342 edk2-uefi-shell = makeTest { 343 name = "systemd-boot-edk2-uefi-shell"; 344 meta.maintainers = with pkgs.lib.maintainers; [ iFreilicht ]; 345 346 nodes.machine = 347 { ... }: 348 { 349 imports = [ common ]; 350 boot.loader.systemd-boot.edk2-uefi-shell.enable = true; 351 }; 352 353 testScript = '' 354 machine.succeed("test -e /boot/loader/entries/edk2-uefi-shell.conf") 355 machine.succeed("test -e /boot/efi/edk2-uefi-shell/shell.efi") 356 ''; 357 }; 358 359 windows = makeTest { 360 name = "systemd-boot-windows"; 361 meta.maintainers = with pkgs.lib.maintainers; [ iFreilicht ]; 362 363 nodes.machine = 364 { ... }: 365 { 366 imports = [ common ]; 367 boot.loader.systemd-boot.windows = { 368 "7" = { 369 efiDeviceHandle = "HD0c1"; 370 sortKey = "before_all_others"; 371 }; 372 "Ten".efiDeviceHandle = "FS0"; 373 "11" = { 374 title = "Title with-_-punctuation ...?!"; 375 efiDeviceHandle = "HD0d4"; 376 sortKey = "zzz"; 377 }; 378 }; 379 }; 380 381 testScript = '' 382 machine.succeed("test -e /boot/efi/edk2-uefi-shell/shell.efi") 383 384 machine.succeed("test -e /boot/loader/entries/windows_7.conf") 385 machine.succeed("test -e /boot/loader/entries/windows_Ten.conf") 386 machine.succeed("test -e /boot/loader/entries/windows_11.conf") 387 388 machine.succeed("grep 'efi /efi/edk2-uefi-shell/shell.efi' /boot/loader/entries/windows_7.conf") 389 machine.succeed("grep 'efi /efi/edk2-uefi-shell/shell.efi' /boot/loader/entries/windows_Ten.conf") 390 machine.succeed("grep 'efi /efi/edk2-uefi-shell/shell.efi' /boot/loader/entries/windows_11.conf") 391 392 machine.succeed("grep 'HD0c1:EFI\\\\Microsoft\\\\Boot\\\\Bootmgfw.efi' /boot/loader/entries/windows_7.conf") 393 machine.succeed("grep 'FS0:EFI\\\\Microsoft\\\\Boot\\\\Bootmgfw.efi' /boot/loader/entries/windows_Ten.conf") 394 machine.succeed("grep 'HD0d4:EFI\\\\Microsoft\\\\Boot\\\\Bootmgfw.efi' /boot/loader/entries/windows_11.conf") 395 396 machine.succeed("grep 'sort-key before_all_others' /boot/loader/entries/windows_7.conf") 397 machine.succeed("grep 'sort-key o_windows_Ten' /boot/loader/entries/windows_Ten.conf") 398 machine.succeed("grep 'sort-key zzz' /boot/loader/entries/windows_11.conf") 399 400 machine.succeed("grep 'title Windows 7' /boot/loader/entries/windows_7.conf") 401 machine.succeed("grep 'title Windows Ten' /boot/loader/entries/windows_Ten.conf") 402 machine.succeed('grep "title Title with-_-punctuation ...?!" /boot/loader/entries/windows_11.conf') 403 ''; 404 }; 405 406 memtestSortKey = makeTest { 407 name = "systemd-boot-memtest-sortkey"; 408 meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ]; 409 410 nodes.machine = 411 { pkgs, lib, ... }: 412 { 413 imports = [ common ]; 414 boot.loader.systemd-boot.memtest86.enable = true; 415 boot.loader.systemd-boot.memtest86.sortKey = "apple"; 416 }; 417 418 testScript = '' 419 machine.succeed("test -e /boot/loader/entries/memtest86.conf") 420 machine.succeed("test -e /boot/efi/memtest86/memtest.efi") 421 machine.succeed("grep 'sort-key apple' /boot/loader/entries/memtest86.conf") 422 ''; 423 }; 424 425 entryFilenameXbootldr = makeTest { 426 name = "systemd-boot-entry-filename-xbootldr"; 427 meta.maintainers = with pkgs.lib.maintainers; [ sdht0 ]; 428 429 nodes.machine = 430 { pkgs, lib, ... }: 431 { 432 imports = [ commonXbootldr ]; 433 boot.loader.systemd-boot.memtest86.enable = true; 434 }; 435 436 testScript = 437 { nodes, ... }: 438 '' 439 ${customDiskImage nodes} 440 441 machine.start() 442 machine.wait_for_unit("multi-user.target") 443 444 machine.succeed("test -e /efi/EFI/systemd/systemd-bootx64.efi") 445 machine.succeed("test -e /boot/loader/entries/memtest86.conf") 446 machine.succeed("test -e /boot/EFI/memtest86/memtest.efi") 447 ''; 448 }; 449 450 extraEntries = makeTest { 451 name = "systemd-boot-extra-entries"; 452 meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ]; 453 454 nodes.machine = 455 { pkgs, lib, ... }: 456 { 457 imports = [ common ]; 458 boot.loader.systemd-boot.extraEntries = { 459 "banana.conf" = '' 460 title banana 461 ''; 462 }; 463 }; 464 465 testScript = '' 466 machine.succeed("test -e /boot/loader/entries/banana.conf") 467 machine.succeed("test -e /boot/efi/nixos/.extra-files/loader/entries/banana.conf") 468 ''; 469 }; 470 471 extraFiles = makeTest { 472 name = "systemd-boot-extra-files"; 473 meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ]; 474 475 nodes.machine = 476 { pkgs, lib, ... }: 477 { 478 imports = [ common ]; 479 boot.loader.systemd-boot.extraFiles = { 480 "efi/fruits/tomato.efi" = pkgs.netbootxyz-efi; 481 }; 482 }; 483 484 testScript = '' 485 machine.succeed("test -e /boot/efi/fruits/tomato.efi") 486 machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") 487 ''; 488 }; 489 490 switch-test = makeTest { 491 name = "systemd-boot-switch-test"; 492 meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ]; 493 494 nodes = { 495 inherit common; 496 497 machine = 498 { pkgs, nodes, ... }: 499 { 500 imports = [ common ]; 501 boot.loader.systemd-boot.extraFiles = { 502 "efi/fruits/tomato.efi" = pkgs.netbootxyz-efi; 503 }; 504 505 # These are configs for different nodes, but we'll use them here in `machine` 506 system.extraDependencies = [ 507 nodes.common.system.build.toplevel 508 nodes.with_netbootxyz.system.build.toplevel 509 ]; 510 }; 511 512 with_netbootxyz = 513 { pkgs, ... }: 514 { 515 imports = [ common ]; 516 boot.loader.systemd-boot.netbootxyz.enable = true; 517 }; 518 }; 519 520 testScript = 521 { nodes, ... }: 522 let 523 originalSystem = nodes.machine.system.build.toplevel; 524 baseSystem = nodes.common.system.build.toplevel; 525 finalSystem = nodes.with_netbootxyz.system.build.toplevel; 526 in 527 '' 528 machine.succeed("test -e /boot/efi/fruits/tomato.efi") 529 machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") 530 531 with subtest("remove files when no longer needed"): 532 machine.succeed("${baseSystem}/bin/switch-to-configuration boot") 533 machine.fail("test -e /boot/efi/fruits/tomato.efi") 534 machine.fail("test -d /boot/efi/fruits") 535 machine.succeed("test -d /boot/efi/nixos/.extra-files") 536 machine.fail("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") 537 machine.fail("test -d /boot/efi/nixos/.extra-files/efi/fruits") 538 539 with subtest("files are added back when needed again"): 540 machine.succeed("${originalSystem}/bin/switch-to-configuration boot") 541 machine.succeed("test -e /boot/efi/fruits/tomato.efi") 542 machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") 543 544 with subtest("simultaneously removing and adding files works"): 545 machine.succeed("${finalSystem}/bin/switch-to-configuration boot") 546 machine.fail("test -e /boot/efi/fruits/tomato.efi") 547 machine.fail("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") 548 machine.succeed("test -e /boot/loader/entries/netbootxyz.conf") 549 machine.succeed("test -e /boot/efi/netbootxyz/netboot.xyz.efi") 550 machine.succeed("test -e /boot/efi/nixos/.extra-files/loader/entries/netbootxyz.conf") 551 machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/netbootxyz/netboot.xyz.efi") 552 ''; 553 }; 554 555 garbage-collect-entry = makeTest { 556 name = "systemd-boot-garbage-collect-entry"; 557 meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ]; 558 559 nodes = { 560 inherit common; 561 machine = 562 { pkgs, nodes, ... }: 563 { 564 imports = [ common ]; 565 566 # These are configs for different nodes, but we'll use them here in `machine` 567 system.extraDependencies = [ 568 nodes.common.system.build.toplevel 569 ]; 570 }; 571 }; 572 573 testScript = 574 { nodes, ... }: 575 let 576 baseSystem = nodes.common.system.build.toplevel; 577 in 578 '' 579 machine.succeed("nix-env -p /nix/var/nix/profiles/system --set ${baseSystem}") 580 machine.succeed("nix-env -p /nix/var/nix/profiles/system --delete-generations 1") 581 machine.succeed("${baseSystem}/bin/switch-to-configuration boot") 582 machine.fail("test -e /boot/loader/entries/nixos-generation-1.conf") 583 machine.succeed("test -e /boot/loader/entries/nixos-generation-2.conf") 584 ''; 585 }; 586 587 no-bootspec = makeTest { 588 name = "systemd-boot-no-bootspec"; 589 meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ]; 590 591 nodes.machine = { 592 imports = [ common ]; 593 boot.bootspec.enable = false; 594 }; 595 596 testScript = '' 597 machine.start() 598 machine.wait_for_unit("multi-user.target") 599 ''; 600 }; 601}