nixos/systemd-boot: Simpler windows dual booting (#344327)

authored by

Atemu and committed by
GitHub
12ef18d2 8d4e0861

+592 -271
+2
nixos/doc/manual/release-notes/rl-2411.section.md
··· 631 The derivation now installs "impl" headers selectively instead of by a wildcard. 632 Use `imgui.src` if you just want to access the unpacked sources. 633 634 - Linux 4.19 has been removed because it will reach its end of life within the lifespan of 24.11 635 636 - Unprivileged access to the kernel syslog via `dmesg` is now restricted by default. Users wanting to keep an
··· 631 The derivation now installs "impl" headers selectively instead of by a wildcard. 632 Use `imgui.src` if you just want to access the unpacked sources. 633 634 + - The new `boot.loader.systemd-boot.windows` option makes setting up dual-booting with Windows on a different drive easier 635 + 636 - Linux 4.19 has been removed because it will reach its end of life within the lifespan of 24.11 637 638 - Unprivileged access to the kernel syslog via `dmesg` is now restricted by default. Users wanting to keep an
+292 -91
nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
··· 1 - { config, lib, pkgs, ... }: 2 3 with lib; 4 ··· 10 # We check the source code in a derivation that does not depend on the 11 # system configuration so that most users don't have to redo the check and require 12 # the necessary dependencies. 13 - checkedSource = pkgs.runCommand "systemd-boot" { 14 - preferLocalBuild = true; 15 - } '' 16 - install -m755 -D ${./systemd-boot-builder.py} $out 17 - ${lib.getExe pkgs.buildPackages.mypy} \ 18 - --no-implicit-optional \ 19 - --disallow-untyped-calls \ 20 - --disallow-untyped-defs \ 21 - $out 22 - ''; 23 24 systemdBootBuilder = pkgs.substituteAll rec { 25 name = "systemd-boot"; ··· 44 45 configurationLimit = if cfg.configurationLimit == null then 0 else cfg.configurationLimit; 46 47 - inherit (cfg) consoleMode graceful editor rebootForBitlocker; 48 49 inherit (efi) efiSysMountPoint canTouchEfiVariables; 50 51 - bootMountPoint = if cfg.xbootldrMountPoint != null 52 - then cfg.xbootldrMountPoint 53 - else efi.efiSysMountPoint; 54 55 nixosDir = "/EFI/nixos"; 56 ··· 60 61 netbootxyz = optionalString cfg.netbootxyz.enable pkgs.netbootxyz-efi; 62 63 checkMountpoints = pkgs.writeShellScript "check-mountpoints" '' 64 fail() { 65 echo "$1 = '$2' is not a mounted partition. Is the path configured correctly?" >&2 66 exit 1 67 } 68 ${pkgs.util-linuxMinimal}/bin/findmnt ${efiSysMountPoint} > /dev/null || fail efiSysMountPoint ${efiSysMountPoint} 69 - ${lib.optionalString 70 - (cfg.xbootldrMountPoint != null) 71 - "${pkgs.util-linuxMinimal}/bin/findmnt ${cfg.xbootldrMountPoint} > /dev/null || fail xbootldrMountPoint ${cfg.xbootldrMountPoint}"} 72 ''; 73 74 copyExtraFiles = pkgs.writeShellScript "copy-extra-files" '' 75 empty_file=$(${pkgs.coreutils}/bin/mktemp) 76 77 - ${concatStrings (mapAttrsToList (n: v: '' 78 - ${pkgs.coreutils}/bin/install -Dp "${v}" "${bootMountPoint}/"${escapeShellArg n} 79 - ${pkgs.coreutils}/bin/install -D $empty_file "${bootMountPoint}/${nixosDir}/.extra-files/"${escapeShellArg n} 80 - '') cfg.extraFiles)} 81 82 - ${concatStrings (mapAttrsToList (n: v: '' 83 - ${pkgs.coreutils}/bin/install -Dp "${pkgs.writeText n v}" "${bootMountPoint}/loader/entries/"${escapeShellArg n} 84 - ${pkgs.coreutils}/bin/install -D $empty_file "${bootMountPoint}/${nixosDir}/.extra-files/loader/entries/"${escapeShellArg n} 85 - '') cfg.extraEntries)} 86 ''; 87 }; 88 ··· 91 ${systemdBootBuilder}/bin/systemd-boot "$@" 92 ${cfg.extraInstallCommands} 93 ''; 94 - in { 95 96 meta.maintainers = with lib.maintainers; [ julienmalka ]; 97 98 - imports = 99 - [ (mkRenamedOptionModule [ "boot" "loader" "gummiboot" "enable" ] [ "boot" "loader" "systemd-boot" "enable" ]) 100 - (lib.mkChangedOptionModule 101 - [ "boot" "loader" "systemd-boot" "memtest86" "entryFilename" ] 102 - [ "boot" "loader" "systemd-boot" "memtest86" "sortKey" ] 103 - (config: lib.strings.removeSuffix ".conf" config.boot.loader.systemd-boot.memtest86.entryFilename) 104 - ) 105 - (lib.mkChangedOptionModule 106 - [ "boot" "loader" "systemd-boot" "netbootxyz" "entryFilename" ] 107 - [ "boot" "loader" "systemd-boot" "netbootxyz" "sortKey" ] 108 - (config: lib.strings.removeSuffix ".conf" config.boot.loader.systemd-boot.netbootxyz.entryFilename) 109 - ) 110 - ]; 111 112 options.boot.loader.systemd-boot = { 113 enable = mkOption { ··· 124 125 sortKey = mkOption { 126 default = "nixos"; 127 - type = lib.types.str; 128 description = '' 129 The sort key used for the NixOS bootloader entries. 130 This key determines sorting relative to non-NixOS entries. ··· 218 consoleMode = mkOption { 219 default = "keep"; 220 221 - type = types.enum [ "0" "1" "2" "5" "auto" "max" "keep" ]; 222 223 description = '' 224 The resolution of the console. The following values are valid: ··· 281 }; 282 }; 283 284 extraEntries = mkOption { 285 type = types.attrsOf types.lines; 286 - default = {}; 287 example = literalExpression '' 288 { "memtest86.conf" = ''' 289 title Memtest86+ ··· 306 307 extraFiles = mkOption { 308 type = types.attrsOf types.path; 309 - default = {}; 310 example = literalExpression '' 311 { "efi/memtest86/memtest.efi" = "''${pkgs.memtest86plus}/memtest.efi"; } 312 ''; ··· 349 Windows can unseal the encryption key. 350 ''; 351 }; 352 }; 353 354 config = mkIf cfg.enable { 355 - assertions = [ 356 - { 357 - assertion = (hasPrefix "/" efi.efiSysMountPoint); 358 - message = "The ESP mount point '${toString efi.efiSysMountPoint}' must be an absolute path"; 359 - } 360 - { 361 - assertion = cfg.xbootldrMountPoint == null || (hasPrefix "/" cfg.xbootldrMountPoint); 362 - message = "The XBOOTLDR mount point '${toString cfg.xbootldrMountPoint}' must be an absolute path"; 363 - } 364 - { 365 - assertion = cfg.xbootldrMountPoint != efi.efiSysMountPoint; 366 - message = "The XBOOTLDR mount point '${toString cfg.xbootldrMountPoint}' cannot be the same as the ESP mount point '${toString efi.efiSysMountPoint}'"; 367 - } 368 - { 369 - assertion = (config.boot.kernelPackages.kernel.features or { efiBootStub = true; }) ? efiBootStub; 370 - message = "This kernel does not support the EFI boot stub"; 371 - } 372 - { 373 - assertion = cfg.installDeviceTree -> config.hardware.deviceTree.enable -> config.hardware.deviceTree.name != null; 374 - message = "Cannot install devicetree without 'config.hardware.deviceTree.enable' enabled and 'config.hardware.deviceTree.name' set"; 375 - } 376 - ] ++ concatMap (filename: [ 377 - { 378 - assertion = !(hasInfix "/" filename); 379 - message = "boot.loader.systemd-boot.extraEntries.${lib.strings.escapeNixIdentifier filename} is invalid: entries within folders are not supported"; 380 - } 381 - { 382 - assertion = hasSuffix ".conf" filename; 383 - message = "boot.loader.systemd-boot.extraEntries.${lib.strings.escapeNixIdentifier filename} is invalid: entries must have a .conf file extension"; 384 - } 385 - ]) (builtins.attrNames cfg.extraEntries) 386 ++ concatMap (filename: [ 387 { 388 assertion = !(hasPrefix "/" filename); ··· 396 assertion = !(hasInfix "nixos/.extra-files" (toLower filename)); 397 message = "boot.loader.systemd-boot.extraFiles.${lib.strings.escapeNixIdentifier filename} is invalid: files cannot be placed in the nixos/.extra-files directory"; 398 } 399 - ]) (builtins.attrNames cfg.extraFiles); 400 401 boot.loader.grub.enable = mkDefault false; 402 ··· 408 }) 409 (mkIf cfg.netbootxyz.enable { 410 "efi/netbootxyz/netboot.xyz.efi" = "${pkgs.netbootxyz-efi}"; 411 }) 412 ]; 413 414 - boot.loader.systemd-boot.extraEntries = mkMerge [ 415 - (mkIf cfg.memtest86.enable { 416 - "memtest86.conf" = '' 417 - title Memtest86+ 418 - efi /efi/memtest86/memtest.efi 419 - sort-key ${cfg.memtest86.sortKey} 420 ''; 421 - }) 422 - (mkIf cfg.netbootxyz.enable { 423 - "netbootxyz.conf" = '' 424 - title netboot.xyz 425 - efi /efi/netbootxyz/netboot.xyz.efi 426 - sort-key ${cfg.netbootxyz.sortKey} 427 - ''; 428 - }) 429 - ]; 430 431 boot.bootspec.extensions."org.nixos.systemd-boot" = { 432 inherit (config.boot.loader.systemd-boot) sortKey;
··· 1 + { 2 + config, 3 + lib, 4 + pkgs, 5 + ... 6 + }: 7 8 with lib; 9 ··· 15 # We check the source code in a derivation that does not depend on the 16 # system configuration so that most users don't have to redo the check and require 17 # the necessary dependencies. 18 + checkedSource = 19 + pkgs.runCommand "systemd-boot" 20 + { 21 + preferLocalBuild = true; 22 + } 23 + '' 24 + install -m755 -D ${./systemd-boot-builder.py} $out 25 + ${lib.getExe pkgs.buildPackages.mypy} \ 26 + --no-implicit-optional \ 27 + --disallow-untyped-calls \ 28 + --disallow-untyped-defs \ 29 + $out 30 + ''; 31 + 32 + edk2ShellEspPath = "efi/edk2-uefi-shell/shell.efi"; 33 34 systemdBootBuilder = pkgs.substituteAll rec { 35 name = "systemd-boot"; ··· 54 55 configurationLimit = if cfg.configurationLimit == null then 0 else cfg.configurationLimit; 56 57 + inherit (cfg) 58 + consoleMode 59 + graceful 60 + editor 61 + rebootForBitlocker 62 + ; 63 64 inherit (efi) efiSysMountPoint canTouchEfiVariables; 65 66 + bootMountPoint = 67 + if cfg.xbootldrMountPoint != null then cfg.xbootldrMountPoint else efi.efiSysMountPoint; 68 69 nixosDir = "/EFI/nixos"; 70 ··· 74 75 netbootxyz = optionalString cfg.netbootxyz.enable pkgs.netbootxyz-efi; 76 77 + edk2-uefi-shell = optionalString cfg.edk2-uefi-shell.enable pkgs.edk2-uefi-shell; 78 + 79 checkMountpoints = pkgs.writeShellScript "check-mountpoints" '' 80 fail() { 81 echo "$1 = '$2' is not a mounted partition. Is the path configured correctly?" >&2 82 exit 1 83 } 84 ${pkgs.util-linuxMinimal}/bin/findmnt ${efiSysMountPoint} > /dev/null || fail efiSysMountPoint ${efiSysMountPoint} 85 + ${lib.optionalString (cfg.xbootldrMountPoint != null) 86 + "${pkgs.util-linuxMinimal}/bin/findmnt ${cfg.xbootldrMountPoint} > /dev/null || fail xbootldrMountPoint ${cfg.xbootldrMountPoint}" 87 + } 88 ''; 89 90 copyExtraFiles = pkgs.writeShellScript "copy-extra-files" '' 91 empty_file=$(${pkgs.coreutils}/bin/mktemp) 92 93 + ${concatStrings ( 94 + mapAttrsToList (n: v: '' 95 + ${pkgs.coreutils}/bin/install -Dp "${v}" "${bootMountPoint}/"${escapeShellArg n} 96 + ${pkgs.coreutils}/bin/install -D $empty_file "${bootMountPoint}/${nixosDir}/.extra-files/"${escapeShellArg n} 97 + '') cfg.extraFiles 98 + )} 99 100 + ${concatStrings ( 101 + mapAttrsToList (n: v: '' 102 + ${pkgs.coreutils}/bin/install -Dp "${pkgs.writeText n v}" "${bootMountPoint}/loader/entries/"${escapeShellArg n} 103 + ${pkgs.coreutils}/bin/install -D $empty_file "${bootMountPoint}/${nixosDir}/.extra-files/loader/entries/"${escapeShellArg n} 104 + '') cfg.extraEntries 105 + )} 106 ''; 107 }; 108 ··· 111 ${systemdBootBuilder}/bin/systemd-boot "$@" 112 ${cfg.extraInstallCommands} 113 ''; 114 + in 115 + { 116 117 meta.maintainers = with lib.maintainers; [ julienmalka ]; 118 119 + imports = [ 120 + (mkRenamedOptionModule 121 + [ 122 + "boot" 123 + "loader" 124 + "gummiboot" 125 + "enable" 126 + ] 127 + [ 128 + "boot" 129 + "loader" 130 + "systemd-boot" 131 + "enable" 132 + ] 133 + ) 134 + (lib.mkChangedOptionModule 135 + [ 136 + "boot" 137 + "loader" 138 + "systemd-boot" 139 + "memtest86" 140 + "entryFilename" 141 + ] 142 + [ 143 + "boot" 144 + "loader" 145 + "systemd-boot" 146 + "memtest86" 147 + "sortKey" 148 + ] 149 + (config: lib.strings.removeSuffix ".conf" config.boot.loader.systemd-boot.memtest86.entryFilename) 150 + ) 151 + (lib.mkChangedOptionModule 152 + [ 153 + "boot" 154 + "loader" 155 + "systemd-boot" 156 + "netbootxyz" 157 + "entryFilename" 158 + ] 159 + [ 160 + "boot" 161 + "loader" 162 + "systemd-boot" 163 + "netbootxyz" 164 + "sortKey" 165 + ] 166 + (config: lib.strings.removeSuffix ".conf" config.boot.loader.systemd-boot.netbootxyz.entryFilename) 167 + ) 168 + ]; 169 170 options.boot.loader.systemd-boot = { 171 enable = mkOption { ··· 182 183 sortKey = mkOption { 184 default = "nixos"; 185 + type = types.str; 186 description = '' 187 The sort key used for the NixOS bootloader entries. 188 This key determines sorting relative to non-NixOS entries. ··· 276 consoleMode = mkOption { 277 default = "keep"; 278 279 + type = types.enum [ 280 + "0" 281 + "1" 282 + "2" 283 + "5" 284 + "auto" 285 + "max" 286 + "keep" 287 + ]; 288 289 description = '' 290 The resolution of the console. The following values are valid: ··· 347 }; 348 }; 349 350 + edk2-uefi-shell = { 351 + enable = mkOption { 352 + type = types.bool; 353 + default = false; 354 + description = '' 355 + Make the EDK2 UEFI Shell available from the systemd-boot menu. 356 + It can be used to manually boot other operating systems or for debugging. 357 + ''; 358 + }; 359 + 360 + sortKey = mkOption { 361 + type = types.str; 362 + default = "o_edk2-uefi-shell"; 363 + description = '' 364 + `systemd-boot` orders the menu entries by their sort keys, 365 + so if you want something to appear after all the NixOS entries, 366 + it should start with {file}`o` or onwards. 367 + 368 + See also {option}`boot.loader.systemd-boot.sortKey`.. 369 + ''; 370 + }; 371 + }; 372 + 373 extraEntries = mkOption { 374 type = types.attrsOf types.lines; 375 + default = { }; 376 example = literalExpression '' 377 { "memtest86.conf" = ''' 378 title Memtest86+ ··· 395 396 extraFiles = mkOption { 397 type = types.attrsOf types.path; 398 + default = { }; 399 example = literalExpression '' 400 { "efi/memtest86/memtest.efi" = "''${pkgs.memtest86plus}/memtest.efi"; } 401 ''; ··· 438 Windows can unseal the encryption key. 439 ''; 440 }; 441 + 442 + windows = mkOption { 443 + default = { }; 444 + description = '' 445 + Make Windows bootable from systemd-boot. This option is not necessary when Windows and 446 + NixOS use the same EFI System Partition (ESP). In that case, Windows will automatically be 447 + detected by systemd-boot. 448 + 449 + However, if Windows is installed on a separate drive or ESP, you can use this option to add 450 + a menu entry for each installation manually. 451 + 452 + The attribute name is used for the title of the menu entry and internal file names. 453 + ''; 454 + example = literalExpression '' 455 + { 456 + "10".efiDeviceHandle = "HD0c3"; 457 + "11-ame" = { 458 + title = "Windows 11 Ameliorated Edition"; 459 + efiDeviceHandle = "HD0b1"; 460 + }; 461 + "11-home" = { 462 + title = "Windows 11 Home"; 463 + efiDeviceHandle = "FS1"; 464 + sortKey = "z_windows"; 465 + }; 466 + } 467 + ''; 468 + type = types.attrsOf ( 469 + types.submodule ( 470 + { name, ... }: 471 + { 472 + options = { 473 + efiDeviceHandle = mkOption { 474 + type = types.str; 475 + example = "HD1b3"; 476 + description = '' 477 + The device handle of the EFI System Partition (ESP) where the Windows bootloader is 478 + located. This is the device handle that the EDK2 UEFI Shell uses to load the 479 + bootloader. 480 + 481 + To find this handle, follow these steps: 482 + 1. Set {option}`boot.loader.systemd-boot.edk2-uefi-shell.enable` to `true` 483 + 2. Run `nixos-rebuild boot` 484 + 3. Reboot and select "EDK2 UEFI Shell" from the systemd-boot menu 485 + 4. Run `map -c` to list all consistent device handles 486 + 5. For each device handle (for example, `HD0c1`), run `ls HD0c1:\EFI` 487 + 6. If the output contains the directory `Microsoft`, you might have found the correct device handle 488 + 7. Run `HD0c1:\EFI\Microsoft\Boot\Bootmgfw.efi` to check if Windows boots correctly 489 + 8. If it does, this device handle is the one you need (in this example, `HD0c1`) 490 + 491 + This option is required, there is no useful default. 492 + ''; 493 + }; 494 + 495 + title = mkOption { 496 + type = types.str; 497 + example = "Michaelsoft Binbows"; 498 + default = "Windows ${name}"; 499 + defaultText = ''attribute name of this entry, prefixed with "Windows "''; 500 + description = '' 501 + The title of the boot menu entry. 502 + ''; 503 + }; 504 + 505 + sortKey = mkOption { 506 + type = types.str; 507 + default = "o_windows_${name}"; 508 + defaultText = ''attribute name of this entry, prefixed with "o_windows_"''; 509 + description = '' 510 + `systemd-boot` orders the menu entries by their sort keys, 511 + so if you want something to appear after all the NixOS entries, 512 + it should start with {file}`o` or onwards. 513 + 514 + See also {option}`boot.loader.systemd-boot.sortKey`.. 515 + ''; 516 + }; 517 + }; 518 + } 519 + ) 520 + ); 521 + }; 522 }; 523 524 config = mkIf cfg.enable { 525 + assertions = 526 + [ 527 + { 528 + assertion = (hasPrefix "/" efi.efiSysMountPoint); 529 + message = "The ESP mount point '${toString efi.efiSysMountPoint}' must be an absolute path"; 530 + } 531 + { 532 + assertion = cfg.xbootldrMountPoint == null || (hasPrefix "/" cfg.xbootldrMountPoint); 533 + message = "The XBOOTLDR mount point '${toString cfg.xbootldrMountPoint}' must be an absolute path"; 534 + } 535 + { 536 + assertion = cfg.xbootldrMountPoint != efi.efiSysMountPoint; 537 + message = "The XBOOTLDR mount point '${toString cfg.xbootldrMountPoint}' cannot be the same as the ESP mount point '${toString efi.efiSysMountPoint}'"; 538 + } 539 + { 540 + assertion = (config.boot.kernelPackages.kernel.features or { efiBootStub = true; }) ? efiBootStub; 541 + message = "This kernel does not support the EFI boot stub"; 542 + } 543 + { 544 + assertion = 545 + cfg.installDeviceTree 546 + -> config.hardware.deviceTree.enable 547 + -> config.hardware.deviceTree.name != null; 548 + message = "Cannot install devicetree without 'config.hardware.deviceTree.enable' enabled and 'config.hardware.deviceTree.name' set"; 549 + } 550 + ] 551 + ++ concatMap (filename: [ 552 + { 553 + assertion = !(hasInfix "/" filename); 554 + message = "boot.loader.systemd-boot.extraEntries.${lib.strings.escapeNixIdentifier filename} is invalid: entries within folders are not supported"; 555 + } 556 + { 557 + assertion = hasSuffix ".conf" filename; 558 + message = "boot.loader.systemd-boot.extraEntries.${lib.strings.escapeNixIdentifier filename} is invalid: entries must have a .conf file extension"; 559 + } 560 + ]) (builtins.attrNames cfg.extraEntries) 561 ++ concatMap (filename: [ 562 { 563 assertion = !(hasPrefix "/" filename); ··· 571 assertion = !(hasInfix "nixos/.extra-files" (toLower filename)); 572 message = "boot.loader.systemd-boot.extraFiles.${lib.strings.escapeNixIdentifier filename} is invalid: files cannot be placed in the nixos/.extra-files directory"; 573 } 574 + ]) (builtins.attrNames cfg.extraFiles) 575 + ++ concatMap (winVersion: [ 576 + { 577 + assertion = lib.match "^[-_0-9A-Za-z]+$" winVersion != null; 578 + message = "boot.loader.systemd-boot.windows.${winVersion} is invalid: key must only contain alphanumeric characters, hyphens, and underscores"; 579 + } 580 + ]) (builtins.attrNames cfg.windows); 581 582 boot.loader.grub.enable = mkDefault false; 583 ··· 589 }) 590 (mkIf cfg.netbootxyz.enable { 591 "efi/netbootxyz/netboot.xyz.efi" = "${pkgs.netbootxyz-efi}"; 592 + }) 593 + (mkIf (cfg.edk2-uefi-shell.enable || cfg.windows != { }) { 594 + ${edk2ShellEspPath} = "${pkgs.edk2-uefi-shell}/shell.efi"; 595 }) 596 ]; 597 598 + boot.loader.systemd-boot.extraEntries = mkMerge ( 599 + [ 600 + (mkIf cfg.memtest86.enable { 601 + "memtest86.conf" = '' 602 + title Memtest86+ 603 + efi /efi/memtest86/memtest.efi 604 + sort-key ${cfg.memtest86.sortKey} 605 + ''; 606 + }) 607 + (mkIf cfg.netbootxyz.enable { 608 + "netbootxyz.conf" = '' 609 + title netboot.xyz 610 + efi /efi/netbootxyz/netboot.xyz.efi 611 + sort-key ${cfg.netbootxyz.sortKey} 612 + ''; 613 + }) 614 + (mkIf cfg.edk2-uefi-shell.enable { 615 + "edk2-uefi-shell.conf" = '' 616 + title EDK2 UEFI Shell 617 + efi /${edk2ShellEspPath} 618 + sort-key ${cfg.edk2-uefi-shell.sortKey} 619 + ''; 620 + }) 621 + ] 622 + ++ (mapAttrsToList (winVersion: cfg: { 623 + "windows_${winVersion}.conf" = '' 624 + title ${cfg.title} 625 + efi /${edk2ShellEspPath} 626 + options -nointerrupt -nomap -noversion ${cfg.efiDeviceHandle}:EFI\Microsoft\Boot\Bootmgfw.efi 627 + sort-key ${cfg.sortKey} 628 ''; 629 + }) cfg.windows) 630 + ); 631 632 boot.bootspec.extensions."org.nixos.systemd-boot" = { 633 inherit (config.boot.loader.systemd-boot) sortKey;
+298 -180
nixos/tests/systemd-boot.nix
··· 1 - { system ? builtins.currentSystem, 2 - config ? {}, 3 - pkgs ? import ../.. { inherit system config; } 4 }: 5 6 with import ../lib/testing-python.nix { inherit system pkgs; }; ··· 16 system.switch.enable = true; 17 }; 18 19 - commonXbootldr = { config, lib, pkgs, ... }: 20 let 21 diskImage = import ../lib/make-disk-image.nix { 22 inherit config lib pkgs; ··· 85 { 86 basic = makeTest { 87 name = "systemd-boot"; 88 - meta.maintainers = with pkgs.lib.maintainers; [ danielfullmer julienmalka ]; 89 90 nodes.machine = common; 91 ··· 117 virtualisation.useSecureBoot = true; 118 }; 119 120 - testScript = let 121 - efiArch = pkgs.stdenv.hostPlatform.efiArch; 122 - in { nodes, ... }: '' 123 - machine.start(allow_reboot=True) 124 - machine.wait_for_unit("multi-user.target") 125 126 - machine.succeed("sbctl create-keys") 127 - machine.succeed("sbctl enroll-keys --yes-this-might-brick-my-machine") 128 - machine.succeed('sbctl sign /boot/EFI/systemd/systemd-boot${efiArch}.efi') 129 - machine.succeed('sbctl sign /boot/EFI/BOOT/BOOT${toUpper efiArch}.EFI') 130 - machine.succeed('sbctl sign /boot/EFI/nixos/*${nodes.machine.system.boot.loader.kernelFile}.efi') 131 132 - machine.reboot() 133 134 - assert "Secure Boot: enabled (user)" in machine.succeed("bootctl status") 135 - ''; 136 }; 137 138 basicXbootldr = makeTest { ··· 141 142 nodes.machine = commonXbootldr; 143 144 - testScript = { nodes, ... }: '' 145 - ${customDiskImage nodes} 146 147 - machine.start() 148 - machine.wait_for_unit("multi-user.target") 149 150 - machine.succeed("test -e /efi/EFI/systemd/systemd-bootx64.efi") 151 - machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf") 152 153 - # Ensure we actually booted using systemd-boot 154 - # Magic number is the vendor UUID used by systemd-boot. 155 - machine.succeed( 156 - "test -e /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f" 157 - ) 158 159 - # "bootctl install" should have created an EFI entry 160 - machine.succeed('efibootmgr | grep "Linux Boot Manager"') 161 - ''; 162 }; 163 164 # Check that specialisations create corresponding boot entries. 165 specialisation = makeTest { 166 name = "systemd-boot-specialisation"; 167 - meta.maintainers = with pkgs.lib.maintainers; [ lukegb julienmalka ]; 168 169 - nodes.machine = { pkgs, lib, ... }: { 170 - imports = [ common ]; 171 - specialisation.something.configuration = { 172 - boot.loader.systemd-boot.sortKey = "something"; 173 174 - # Since qemu will dynamically create a devicetree blob when starting 175 - # up, it is not straight forward to create an export of that devicetree 176 - # blob without knowing before-hand all the flags we would pass to qemu 177 - # (we would then be able to use `dumpdtb`). Thus, the following config 178 - # will not boot, but it does allow us to assert that the boot entry has 179 - # the correct contents. 180 - boot.loader.systemd-boot.installDeviceTree = pkgs.stdenv.hostPlatform.isAarch64; 181 - hardware.deviceTree.name = "dummy.dtb"; 182 - hardware.deviceTree.package = lib.mkForce (pkgs.runCommand "dummy-devicetree-package" { } '' 183 - mkdir -p $out 184 - cp ${pkgs.emptyFile} $out/dummy.dtb 185 - ''); 186 }; 187 - }; 188 189 - testScript = { nodes, ... }: '' 190 - machine.start() 191 - machine.wait_for_unit("multi-user.target") 192 193 - machine.succeed( 194 - "test -e /boot/loader/entries/nixos-generation-1-specialisation-something.conf" 195 - ) 196 - machine.succeed( 197 - "grep -q 'title NixOS (something)' /boot/loader/entries/nixos-generation-1-specialisation-something.conf" 198 - ) 199 - machine.succeed( 200 - "grep 'sort-key something' /boot/loader/entries/nixos-generation-1-specialisation-something.conf" 201 - ) 202 - '' + pkgs.lib.optionalString pkgs.stdenv.hostPlatform.isAarch64 '' 203 - machine.succeed( 204 - r"grep 'devicetree /EFI/nixos/[a-z0-9]\{32\}.*dummy' /boot/loader/entries/nixos-generation-1-specialisation-something.conf" 205 - ) 206 - ''; 207 }; 208 209 # Boot without having created an EFI entry--instead using default "/EFI/BOOT/BOOTX64.EFI" 210 fallback = makeTest { 211 name = "systemd-boot-fallback"; 212 - meta.maintainers = with pkgs.lib.maintainers; [ danielfullmer julienmalka ]; 213 214 - nodes.machine = { pkgs, lib, ... }: { 215 - imports = [ common ]; 216 - boot.loader.efi.canTouchEfiVariables = mkForce false; 217 - }; 218 219 testScript = '' 220 machine.start() ··· 235 236 update = makeTest { 237 name = "systemd-boot-update"; 238 - meta.maintainers = with pkgs.lib.maintainers; [ danielfullmer julienmalka ]; 239 240 nodes.machine = common; 241 ··· 270 ''; 271 }; 272 273 - memtest86 = with pkgs.lib; optionalAttrs (meta.availableOn { inherit system; } pkgs.memtest86plus) (makeTest { 274 - name = "systemd-boot-memtest86"; 275 - meta.maintainers = with maintainers; [ julienmalka ]; 276 277 - nodes.machine = { pkgs, lib, ... }: { 278 imports = [ common ]; 279 - boot.loader.systemd-boot.memtest86.enable = true; 280 }; 281 282 testScript = '' 283 - machine.succeed("test -e /boot/loader/entries/memtest86.conf") 284 - machine.succeed("test -e /boot/efi/memtest86/memtest.efi") 285 ''; 286 - }); 287 288 - netbootxyz = makeTest { 289 - name = "systemd-boot-netbootxyz"; 290 - meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ]; 291 292 - nodes.machine = { pkgs, lib, ... }: { 293 imports = [ common ]; 294 - boot.loader.systemd-boot.netbootxyz.enable = true; 295 }; 296 297 testScript = '' 298 - machine.succeed("test -e /boot/loader/entries/netbootxyz.conf") 299 - machine.succeed("test -e /boot/efi/netbootxyz/netboot.xyz.efi") 300 ''; 301 }; 302 ··· 304 name = "systemd-boot-memtest-sortkey"; 305 meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ]; 306 307 - nodes.machine = { pkgs, lib, ... }: { 308 - imports = [ common ]; 309 - boot.loader.systemd-boot.memtest86.enable = true; 310 - boot.loader.systemd-boot.memtest86.sortKey = "apple"; 311 - }; 312 313 testScript = '' 314 machine.succeed("test -e /boot/loader/entries/memtest86.conf") ··· 321 name = "systemd-boot-entry-filename-xbootldr"; 322 meta.maintainers = with pkgs.lib.maintainers; [ sdht0 ]; 323 324 - nodes.machine = { pkgs, lib, ... }: { 325 - imports = [ commonXbootldr ]; 326 - boot.loader.systemd-boot.memtest86.enable = true; 327 - }; 328 329 - testScript = { nodes, ... }: '' 330 - ${customDiskImage nodes} 331 332 - machine.start() 333 - machine.wait_for_unit("multi-user.target") 334 335 - machine.succeed("test -e /efi/EFI/systemd/systemd-bootx64.efi") 336 - machine.succeed("test -e /boot/loader/entries/memtest86.conf") 337 - machine.succeed("test -e /boot/EFI/memtest86/memtest.efi") 338 - ''; 339 }; 340 341 extraEntries = makeTest { 342 name = "systemd-boot-extra-entries"; 343 meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ]; 344 345 - nodes.machine = { pkgs, lib, ... }: { 346 - imports = [ common ]; 347 - boot.loader.systemd-boot.extraEntries = { 348 - "banana.conf" = '' 349 - title banana 350 - ''; 351 }; 352 - }; 353 354 testScript = '' 355 machine.succeed("test -e /boot/loader/entries/banana.conf") ··· 361 name = "systemd-boot-extra-files"; 362 meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ]; 363 364 - nodes.machine = { pkgs, lib, ... }: { 365 - imports = [ common ]; 366 - boot.loader.systemd-boot.extraFiles = { 367 - "efi/fruits/tomato.efi" = pkgs.netbootxyz-efi; 368 }; 369 - }; 370 371 testScript = '' 372 machine.succeed("test -e /boot/efi/fruits/tomato.efi") ··· 381 nodes = { 382 inherit common; 383 384 - machine = { pkgs, nodes, ... }: { 385 - imports = [ common ]; 386 - boot.loader.systemd-boot.extraFiles = { 387 - "efi/fruits/tomato.efi" = pkgs.netbootxyz-efi; 388 }; 389 390 - # These are configs for different nodes, but we'll use them here in `machine` 391 - system.extraDependencies = [ 392 - nodes.common.system.build.toplevel 393 - nodes.with_netbootxyz.system.build.toplevel 394 - ]; 395 - }; 396 - 397 - with_netbootxyz = { pkgs, ... }: { 398 - imports = [ common ]; 399 - boot.loader.systemd-boot.netbootxyz.enable = true; 400 - }; 401 }; 402 403 - testScript = { nodes, ... }: let 404 - originalSystem = nodes.machine.system.build.toplevel; 405 - baseSystem = nodes.common.system.build.toplevel; 406 - finalSystem = nodes.with_netbootxyz.system.build.toplevel; 407 - in '' 408 - machine.succeed("test -e /boot/efi/fruits/tomato.efi") 409 - machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") 410 411 - with subtest("remove files when no longer needed"): 412 - machine.succeed("${baseSystem}/bin/switch-to-configuration boot") 413 - machine.fail("test -e /boot/efi/fruits/tomato.efi") 414 - machine.fail("test -d /boot/efi/fruits") 415 - machine.succeed("test -d /boot/efi/nixos/.extra-files") 416 - machine.fail("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") 417 - machine.fail("test -d /boot/efi/nixos/.extra-files/efi/fruits") 418 419 - with subtest("files are added back when needed again"): 420 - machine.succeed("${originalSystem}/bin/switch-to-configuration boot") 421 - machine.succeed("test -e /boot/efi/fruits/tomato.efi") 422 - machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") 423 424 - with subtest("simultaneously removing and adding files works"): 425 - machine.succeed("${finalSystem}/bin/switch-to-configuration boot") 426 - machine.fail("test -e /boot/efi/fruits/tomato.efi") 427 - machine.fail("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") 428 - machine.succeed("test -e /boot/loader/entries/netbootxyz.conf") 429 - machine.succeed("test -e /boot/efi/netbootxyz/netboot.xyz.efi") 430 - machine.succeed("test -e /boot/efi/nixos/.extra-files/loader/entries/netbootxyz.conf") 431 - machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/netbootxyz/netboot.xyz.efi") 432 - ''; 433 }; 434 435 garbage-collect-entry = makeTest { ··· 438 439 nodes = { 440 inherit common; 441 - machine = { pkgs, nodes, ... }: { 442 - imports = [ common ]; 443 444 - # These are configs for different nodes, but we'll use them here in `machine` 445 - system.extraDependencies = [ 446 - nodes.common.system.build.toplevel 447 - ]; 448 - }; 449 }; 450 451 - testScript = { nodes, ... }: 452 let 453 baseSystem = nodes.common.system.build.toplevel; 454 in ··· 461 ''; 462 }; 463 464 - no-bootspec = makeTest 465 - { 466 - name = "systemd-boot-no-bootspec"; 467 - meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ]; 468 469 - nodes.machine = { 470 - imports = [ common ]; 471 - boot.bootspec.enable = false; 472 - }; 473 474 - testScript = '' 475 - machine.start() 476 - machine.wait_for_unit("multi-user.target") 477 - ''; 478 - }; 479 }
··· 1 + { 2 + system ? builtins.currentSystem, 3 + config ? { }, 4 + pkgs ? import ../.. { inherit system config; }, 5 }: 6 7 with import ../lib/testing-python.nix { inherit system pkgs; }; ··· 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; ··· 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 ··· 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 { ··· 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() ··· 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 ··· 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 imports = [ common ]; 348 + boot.loader.systemd-boot.edk2-uefi-shell.enable = true; 349 }; 350 351 testScript = '' 352 + machine.succeed("test -e /boot/loader/entries/edk2-uefi-shell.conf") 353 + machine.succeed("test -e /boot/efi/edk2-uefi-shell/shell.efi") 354 ''; 355 + }; 356 357 + windows = makeTest { 358 + name = "systemd-boot-windows"; 359 + meta.maintainers = with pkgs.lib.maintainers; [ iFreilicht ]; 360 361 + nodes.machine = { ... }: { 362 imports = [ common ]; 363 + boot.loader.systemd-boot.windows = { 364 + "7" = { 365 + efiDeviceHandle = "HD0c1"; 366 + sortKey = "before_all_others"; 367 + }; 368 + "Ten".efiDeviceHandle = "FS0"; 369 + "11" = { 370 + title = "Title with-_-punctuation ...?!"; 371 + efiDeviceHandle = "HD0d4"; 372 + sortKey = "zzz"; 373 + }; 374 + }; 375 }; 376 377 testScript = '' 378 + machine.succeed("test -e /boot/efi/edk2-uefi-shell/shell.efi") 379 + 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") 383 + 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") 387 + 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") 391 + 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") 395 + 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') 399 ''; 400 }; 401 ··· 403 name = "systemd-boot-memtest-sortkey"; 404 meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ]; 405 406 + nodes.machine = 407 + { pkgs, lib, ... }: 408 + { 409 + imports = [ common ]; 410 + boot.loader.systemd-boot.memtest86.enable = true; 411 + boot.loader.systemd-boot.memtest86.sortKey = "apple"; 412 + }; 413 414 testScript = '' 415 machine.succeed("test -e /boot/loader/entries/memtest86.conf") ··· 422 name = "systemd-boot-entry-filename-xbootldr"; 423 meta.maintainers = with pkgs.lib.maintainers; [ sdht0 ]; 424 425 + nodes.machine = 426 + { pkgs, lib, ... }: 427 + { 428 + imports = [ commonXbootldr ]; 429 + boot.loader.systemd-boot.memtest86.enable = true; 430 + }; 431 432 + testScript = 433 + { nodes, ... }: 434 + '' 435 + ${customDiskImage nodes} 436 437 + machine.start() 438 + machine.wait_for_unit("multi-user.target") 439 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") 443 + ''; 444 }; 445 446 extraEntries = makeTest { 447 name = "systemd-boot-extra-entries"; 448 meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ]; 449 450 + nodes.machine = 451 + { pkgs, lib, ... }: 452 + { 453 + imports = [ common ]; 454 + boot.loader.systemd-boot.extraEntries = { 455 + "banana.conf" = '' 456 + title banana 457 + ''; 458 + }; 459 }; 460 461 testScript = '' 462 machine.succeed("test -e /boot/loader/entries/banana.conf") ··· 468 name = "systemd-boot-extra-files"; 469 meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ]; 470 471 + nodes.machine = 472 + { pkgs, lib, ... }: 473 + { 474 + imports = [ common ]; 475 + boot.loader.systemd-boot.extraFiles = { 476 + "efi/fruits/tomato.efi" = pkgs.netbootxyz-efi; 477 + }; 478 }; 479 480 testScript = '' 481 machine.succeed("test -e /boot/efi/fruits/tomato.efi") ··· 490 nodes = { 491 inherit common; 492 493 + machine = 494 + { pkgs, nodes, ... }: 495 + { 496 + imports = [ common ]; 497 + boot.loader.systemd-boot.extraFiles = { 498 + "efi/fruits/tomato.efi" = pkgs.netbootxyz-efi; 499 + }; 500 + 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 505 + ]; 506 }; 507 508 + with_netbootxyz = 509 + { pkgs, ... }: 510 + { 511 + imports = [ common ]; 512 + boot.loader.systemd-boot.netbootxyz.enable = true; 513 + }; 514 }; 515 516 + testScript = 517 + { nodes, ... }: 518 + let 519 + originalSystem = nodes.machine.system.build.toplevel; 520 + baseSystem = nodes.common.system.build.toplevel; 521 + finalSystem = nodes.with_netbootxyz.system.build.toplevel; 522 + in 523 + '' 524 + machine.succeed("test -e /boot/efi/fruits/tomato.efi") 525 + machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") 526 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") 534 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") 539 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") 548 + ''; 549 }; 550 551 garbage-collect-entry = makeTest { ··· 554 555 nodes = { 556 inherit common; 557 + machine = 558 + { pkgs, nodes, ... }: 559 + { 560 + imports = [ common ]; 561 562 + # These are configs for different nodes, but we'll use them here in `machine` 563 + system.extraDependencies = [ 564 + nodes.common.system.build.toplevel 565 + ]; 566 + }; 567 }; 568 569 + testScript = 570 + { nodes, ... }: 571 let 572 baseSystem = nodes.common.system.build.toplevel; 573 in ··· 580 ''; 581 }; 582 583 + no-bootspec = makeTest { 584 + name = "systemd-boot-no-bootspec"; 585 + meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ]; 586 587 + nodes.machine = { 588 + imports = [ common ]; 589 + boot.bootspec.enable = false; 590 + }; 591 592 + testScript = '' 593 + machine.start() 594 + machine.wait_for_unit("multi-user.target") 595 + ''; 596 + }; 597 }