at 24.11-pre 16 kB view raw
1{ system ? builtins.currentSystem, 2 config ? {}, 3 pkgs ? import ../.. { inherit system config; } 4}: 5 6with import ../lib/testing-python.nix { inherit system pkgs; }; 7with pkgs.lib; 8 9let 10 common = { 11 virtualisation.useBootLoader = true; 12 virtualisation.useEFIBoot = true; 13 boot.loader.systemd-boot.enable = true; 14 boot.loader.efi.canTouchEfiVariables = true; 15 environment.systemPackages = [ pkgs.efibootmgr ]; 16 }; 17 18 commonXbootldr = { config, lib, pkgs, ... }: 19 let 20 diskImage = import ../lib/make-disk-image.nix { 21 inherit config lib pkgs; 22 label = "nixos"; 23 format = "qcow2"; 24 partitionTableType = "efixbootldr"; 25 touchEFIVars = true; 26 installBootLoader = true; 27 }; 28 in 29 { 30 imports = [ common ]; 31 virtualisation.useBootLoader = lib.mkForce false; # Only way to tell qemu-vm not to create the default system image 32 virtualisation.directBoot.enable = false; # But don't direct boot either because we're testing systemd-boot 33 34 system.build.diskImage = diskImage; # Use custom disk image with an XBOOTLDR partition 35 virtualisation.efi.variables = "${diskImage}/efi-vars.fd"; 36 37 virtualisation.useDefaultFilesystems = false; # Needs custom setup for `diskImage` 38 virtualisation.bootPartition = null; 39 virtualisation.fileSystems = { 40 "/" = { 41 device = "/dev/vda3"; 42 fsType = "ext4"; 43 }; 44 "/boot" = { 45 device = "/dev/vda2"; 46 fsType = "vfat"; 47 noCheck = true; 48 }; 49 "/efi" = { 50 device = "/dev/vda1"; 51 fsType = "vfat"; 52 noCheck = true; 53 }; 54 }; 55 56 boot.loader.systemd-boot.enable = true; 57 boot.loader.efi.efiSysMountPoint = "/efi"; 58 boot.loader.systemd-boot.xbootldrMountPoint = "/boot"; 59 }; 60 61 customDiskImage = nodes: '' 62 import os 63 import subprocess 64 import tempfile 65 66 tmp_disk_image = tempfile.NamedTemporaryFile() 67 68 subprocess.run([ 69 "${nodes.machine.virtualisation.qemu.package}/bin/qemu-img", 70 "create", 71 "-f", 72 "qcow2", 73 "-b", 74 "${nodes.machine.system.build.diskImage}/nixos.qcow2", 75 "-F", 76 "qcow2", 77 tmp_disk_image.name, 78 ]) 79 80 # Set NIX_DISK_IMAGE so that the qemu script finds the right disk image. 81 os.environ['NIX_DISK_IMAGE'] = tmp_disk_image.name 82 ''; 83in 84{ 85 basic = makeTest { 86 name = "systemd-boot"; 87 meta.maintainers = with pkgs.lib.maintainers; [ danielfullmer julienmalka ]; 88 89 nodes.machine = common; 90 91 testScript = '' 92 machine.start() 93 machine.wait_for_unit("multi-user.target") 94 95 machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf") 96 machine.succeed("grep 'sort-key nixos' /boot/loader/entries/nixos-generation-1.conf") 97 98 # Ensure we actually booted using systemd-boot 99 # Magic number is the vendor UUID used by systemd-boot. 100 machine.succeed( 101 "test -e /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f" 102 ) 103 104 # "bootctl install" should have created an EFI entry 105 machine.succeed('efibootmgr | grep "Linux Boot Manager"') 106 ''; 107 }; 108 109 # Test that systemd-boot works with secure boot 110 secureBoot = makeTest { 111 name = "systemd-boot-secure-boot"; 112 113 nodes.machine = { 114 imports = [ common ]; 115 environment.systemPackages = [ pkgs.sbctl ]; 116 virtualisation.useSecureBoot = true; 117 }; 118 119 testScript = let 120 efiArch = pkgs.stdenv.hostPlatform.efiArch; 121 in { nodes, ... }: '' 122 machine.start(allow_reboot=True) 123 machine.wait_for_unit("multi-user.target") 124 125 machine.succeed("sbctl create-keys") 126 machine.succeed("sbctl enroll-keys --yes-this-might-brick-my-machine") 127 machine.succeed('sbctl sign /boot/EFI/systemd/systemd-boot${efiArch}.efi') 128 machine.succeed('sbctl sign /boot/EFI/BOOT/BOOT${toUpper efiArch}.EFI') 129 machine.succeed('sbctl sign /boot/EFI/nixos/*${nodes.machine.system.boot.loader.kernelFile}.efi') 130 131 machine.reboot() 132 133 assert "Secure Boot: enabled (user)" in machine.succeed("bootctl status") 134 ''; 135 }; 136 137 basicXbootldr = makeTest { 138 name = "systemd-boot-xbootldr"; 139 meta.maintainers = with pkgs.lib.maintainers; [ sdht0 ]; 140 141 nodes.machine = commonXbootldr; 142 143 testScript = { nodes, ... }: '' 144 ${customDiskImage nodes} 145 146 machine.start() 147 machine.wait_for_unit("multi-user.target") 148 149 machine.succeed("test -e /efi/EFI/systemd/systemd-bootx64.efi") 150 machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf") 151 152 # Ensure we actually booted using systemd-boot 153 # Magic number is the vendor UUID used by systemd-boot. 154 machine.succeed( 155 "test -e /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f" 156 ) 157 158 # "bootctl install" should have created an EFI entry 159 machine.succeed('efibootmgr | grep "Linux Boot Manager"') 160 ''; 161 }; 162 163 # Check that specialisations create corresponding boot entries. 164 specialisation = makeTest { 165 name = "systemd-boot-specialisation"; 166 meta.maintainers = with pkgs.lib.maintainers; [ lukegb julienmalka ]; 167 168 nodes.machine = { pkgs, lib, ... }: { 169 imports = [ common ]; 170 specialisation.something.configuration = { 171 boot.loader.systemd-boot.sortKey = "something"; 172 }; 173 }; 174 175 testScript = '' 176 machine.start() 177 machine.wait_for_unit("multi-user.target") 178 179 machine.succeed( 180 "test -e /boot/loader/entries/nixos-generation-1-specialisation-something.conf" 181 ) 182 machine.succeed( 183 "grep -q 'title NixOS (something)' /boot/loader/entries/nixos-generation-1-specialisation-something.conf" 184 ) 185 machine.succeed( 186 "grep 'sort-key something' /boot/loader/entries/nixos-generation-1-specialisation-something.conf" 187 ) 188 ''; 189 }; 190 191 # Boot without having created an EFI entry--instead using default "/EFI/BOOT/BOOTX64.EFI" 192 fallback = makeTest { 193 name = "systemd-boot-fallback"; 194 meta.maintainers = with pkgs.lib.maintainers; [ danielfullmer julienmalka ]; 195 196 nodes.machine = { pkgs, lib, ... }: { 197 imports = [ common ]; 198 boot.loader.efi.canTouchEfiVariables = mkForce false; 199 }; 200 201 testScript = '' 202 machine.start() 203 machine.wait_for_unit("multi-user.target") 204 205 machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf") 206 207 # Ensure we actually booted using systemd-boot 208 # Magic number is the vendor UUID used by systemd-boot. 209 machine.succeed( 210 "test -e /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f" 211 ) 212 213 # "bootctl install" should _not_ have created an EFI entry 214 machine.fail('efibootmgr | grep "Linux Boot Manager"') 215 ''; 216 }; 217 218 update = makeTest { 219 name = "systemd-boot-update"; 220 meta.maintainers = with pkgs.lib.maintainers; [ danielfullmer julienmalka ]; 221 222 nodes.machine = common; 223 224 testScript = '' 225 machine.succeed("mount -o remount,rw /boot") 226 227 # Replace version inside sd-boot with something older. See magic[] string in systemd src/boot/efi/boot.c 228 machine.succeed( 229 """ 230 find /boot -iname '*boot*.efi' -print0 | \ 231 xargs -0 -I '{}' sed -i 's/#### LoaderInfo: systemd-boot .* ####/#### LoaderInfo: systemd-boot 000.0-1-notnixos ####/' '{}' 232 """ 233 ) 234 235 output = machine.succeed("/run/current-system/bin/switch-to-configuration boot") 236 assert "updating systemd-boot from 000.0-1-notnixos to " in output, "Couldn't find systemd-boot update message" 237 ''; 238 }; 239 240 memtest86 = makeTest { 241 name = "systemd-boot-memtest86"; 242 meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ]; 243 244 nodes.machine = { pkgs, lib, ... }: { 245 imports = [ common ]; 246 boot.loader.systemd-boot.memtest86.enable = true; 247 }; 248 249 testScript = '' 250 machine.succeed("test -e /boot/loader/entries/memtest86.conf") 251 machine.succeed("test -e /boot/efi/memtest86/memtest.efi") 252 ''; 253 }; 254 255 netbootxyz = makeTest { 256 name = "systemd-boot-netbootxyz"; 257 meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ]; 258 259 nodes.machine = { pkgs, lib, ... }: { 260 imports = [ common ]; 261 boot.loader.systemd-boot.netbootxyz.enable = true; 262 }; 263 264 testScript = '' 265 machine.succeed("test -e /boot/loader/entries/netbootxyz.conf") 266 machine.succeed("test -e /boot/efi/netbootxyz/netboot.xyz.efi") 267 ''; 268 }; 269 270 memtestSortKey = makeTest { 271 name = "systemd-boot-memtest-sortkey"; 272 meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ]; 273 274 nodes.machine = { pkgs, lib, ... }: { 275 imports = [ common ]; 276 boot.loader.systemd-boot.memtest86.enable = true; 277 boot.loader.systemd-boot.memtest86.sortKey = "apple"; 278 }; 279 280 testScript = '' 281 machine.succeed("test -e /boot/loader/entries/memtest86.conf") 282 machine.succeed("test -e /boot/efi/memtest86/memtest.efi") 283 machine.succeed("grep 'sort-key apple' /boot/loader/entries/memtest86.conf") 284 ''; 285 }; 286 287 entryFilenameXbootldr = makeTest { 288 name = "systemd-boot-entry-filename-xbootldr"; 289 meta.maintainers = with pkgs.lib.maintainers; [ sdht0 ]; 290 291 nodes.machine = { pkgs, lib, ... }: { 292 imports = [ commonXbootldr ]; 293 boot.loader.systemd-boot.memtest86.enable = true; 294 }; 295 296 testScript = { nodes, ... }: '' 297 ${customDiskImage nodes} 298 299 machine.start() 300 machine.wait_for_unit("multi-user.target") 301 302 machine.succeed("test -e /efi/EFI/systemd/systemd-bootx64.efi") 303 machine.succeed("test -e /boot/loader/entries/memtest86.conf") 304 machine.succeed("test -e /boot/EFI/memtest86/memtest.efi") 305 ''; 306 }; 307 308 extraEntries = makeTest { 309 name = "systemd-boot-extra-entries"; 310 meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ]; 311 312 nodes.machine = { pkgs, lib, ... }: { 313 imports = [ common ]; 314 boot.loader.systemd-boot.extraEntries = { 315 "banana.conf" = '' 316 title banana 317 ''; 318 }; 319 }; 320 321 testScript = '' 322 machine.succeed("test -e /boot/loader/entries/banana.conf") 323 machine.succeed("test -e /boot/efi/nixos/.extra-files/loader/entries/banana.conf") 324 ''; 325 }; 326 327 extraFiles = makeTest { 328 name = "systemd-boot-extra-files"; 329 meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ]; 330 331 nodes.machine = { pkgs, lib, ... }: { 332 imports = [ common ]; 333 boot.loader.systemd-boot.extraFiles = { 334 "efi/fruits/tomato.efi" = pkgs.netbootxyz-efi; 335 }; 336 }; 337 338 testScript = '' 339 machine.succeed("test -e /boot/efi/fruits/tomato.efi") 340 machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") 341 ''; 342 }; 343 344 switch-test = makeTest { 345 name = "systemd-boot-switch-test"; 346 meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ]; 347 348 nodes = { 349 inherit common; 350 351 machine = { pkgs, nodes, ... }: { 352 imports = [ common ]; 353 boot.loader.systemd-boot.extraFiles = { 354 "efi/fruits/tomato.efi" = pkgs.netbootxyz-efi; 355 }; 356 357 # These are configs for different nodes, but we'll use them here in `machine` 358 system.extraDependencies = [ 359 nodes.common.system.build.toplevel 360 nodes.with_netbootxyz.system.build.toplevel 361 ]; 362 }; 363 364 with_netbootxyz = { pkgs, ... }: { 365 imports = [ common ]; 366 boot.loader.systemd-boot.netbootxyz.enable = true; 367 }; 368 }; 369 370 testScript = { nodes, ... }: let 371 originalSystem = nodes.machine.system.build.toplevel; 372 baseSystem = nodes.common.system.build.toplevel; 373 finalSystem = nodes.with_netbootxyz.system.build.toplevel; 374 in '' 375 machine.succeed("test -e /boot/efi/fruits/tomato.efi") 376 machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") 377 378 with subtest("remove files when no longer needed"): 379 machine.succeed("${baseSystem}/bin/switch-to-configuration boot") 380 machine.fail("test -e /boot/efi/fruits/tomato.efi") 381 machine.fail("test -d /boot/efi/fruits") 382 machine.succeed("test -d /boot/efi/nixos/.extra-files") 383 machine.fail("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") 384 machine.fail("test -d /boot/efi/nixos/.extra-files/efi/fruits") 385 386 with subtest("files are added back when needed again"): 387 machine.succeed("${originalSystem}/bin/switch-to-configuration boot") 388 machine.succeed("test -e /boot/efi/fruits/tomato.efi") 389 machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") 390 391 with subtest("simultaneously removing and adding files works"): 392 machine.succeed("${finalSystem}/bin/switch-to-configuration boot") 393 machine.fail("test -e /boot/efi/fruits/tomato.efi") 394 machine.fail("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") 395 machine.succeed("test -e /boot/loader/entries/netbootxyz.conf") 396 machine.succeed("test -e /boot/efi/netbootxyz/netboot.xyz.efi") 397 machine.succeed("test -e /boot/efi/nixos/.extra-files/loader/entries/netbootxyz.conf") 398 machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/netbootxyz/netboot.xyz.efi") 399 ''; 400 }; 401 402 garbage-collect-entry = makeTest { 403 name = "systemd-boot-garbage-collect-entry"; 404 meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ]; 405 406 nodes = { 407 inherit common; 408 machine = { pkgs, nodes, ... }: { 409 imports = [ common ]; 410 411 # These are configs for different nodes, but we'll use them here in `machine` 412 system.extraDependencies = [ 413 nodes.common.system.build.toplevel 414 ]; 415 }; 416 }; 417 418 testScript = { nodes, ... }: 419 let 420 baseSystem = nodes.common.system.build.toplevel; 421 in 422 '' 423 machine.succeed("nix-env -p /nix/var/nix/profiles/system --set ${baseSystem}") 424 machine.succeed("nix-env -p /nix/var/nix/profiles/system --delete-generations 1") 425 machine.succeed("${baseSystem}/bin/switch-to-configuration boot") 426 machine.fail("test -e /boot/loader/entries/nixos-generation-1.conf") 427 machine.succeed("test -e /boot/loader/entries/nixos-generation-2.conf") 428 ''; 429 }; 430 431 # Some UEFI firmwares fail on large reads. Now that systemd-boot loads initrd 432 # itself, systems with such firmware won't boot without this fix 433 uefiLargeFileWorkaround = makeTest { 434 name = "uefi-large-file-workaround"; 435 meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ]; 436 nodes.machine = { pkgs, ... }: { 437 imports = [common]; 438 virtualisation.efi.OVMF = pkgs.OVMF.overrideAttrs (old: { 439 # This patch deliberately breaks the FAT driver in EDK2 to 440 # exhibit (part of) the firmware bug that we are testing 441 # for. Files greater than 10MiB will fail to be read in a 442 # single Read() call, so systemd-boot will fail to load the 443 # initrd without a workaround. The number 10MiB was chosen 444 # because if it were smaller than the kernel size, even the 445 # LoadImage call would fail, which is not the failure mode 446 # we're testing for. It needs to be between the kernel size 447 # and the initrd size. 448 patches = old.patches or [] ++ [ ./systemd-boot-ovmf-broken-fat-driver.patch ]; 449 }); 450 }; 451 452 testScript = '' 453 machine.wait_for_unit("multi-user.target") 454 ''; 455 }; 456 457 no-bootspec = makeTest 458 { 459 name = "systemd-boot-no-bootspec"; 460 meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ]; 461 462 nodes.machine = { 463 imports = [ common ]; 464 boot.bootspec.enable = false; 465 }; 466 467 testScript = '' 468 machine.start() 469 machine.wait_for_unit("multi-user.target") 470 ''; 471 }; 472}