notes: 2.3.0 -> 2.3.1 (#352950)
[NixPkgs.git] / nixos / tests / installer.nix
blobd4caf3ceaf1fb0142cd29561dfcfeecfde446515
1 { system ? builtins.currentSystem,
2   config ? {},
3   pkgs ? import ../.. { inherit system config; },
4   systemdStage1 ? false
5 }:
7 with import ../lib/testing-python.nix { inherit system pkgs; };
8 with pkgs.lib;
10 let
12   # The configuration to install.
13   makeConfig = { bootLoader, grubDevice, grubIdentifier, grubUseEfi
14                , extraConfig, forceGrubReinstallCount ? 0, withTestInstrumentation ? true
15                , clevisTest
16                }:
17     pkgs.writeText "configuration.nix" ''
18       { config, lib, pkgs, modulesPath, ... }:
20       { imports =
21           [ ./hardware-configuration.nix
22             ${if !withTestInstrumentation
23               then "" # Still included, but via installer/flake.nix
24               else "<nixpkgs/nixos/modules/testing/test-instrumentation.nix>"}
25           ];
27         networking.hostName = "thatworked";
29         documentation.enable = false;
31         # To ensure that we can rebuild the grub configuration on the nixos-rebuild
32         system.extraDependencies = with pkgs; [ stdenvNoCC ];
34         ${optionalString systemdStage1 "boot.initrd.systemd.enable = true;"}
36         ${optionalString (bootLoader == "grub") ''
37           boot.loader.grub.extraConfig = "serial; terminal_output serial";
38           ${if grubUseEfi then ''
39             boot.loader.grub.device = "nodev";
40             boot.loader.grub.efiSupport = true;
41             boot.loader.grub.efiInstallAsRemovable = true; # XXX: needed for OVMF?
42           '' else ''
43             boot.loader.grub.device = "${grubDevice}";
44             boot.loader.grub.fsIdentifier = "${grubIdentifier}";
45           ''}
47           boot.loader.grub.configurationLimit = 100 + ${toString forceGrubReinstallCount};
48         ''}
50         ${optionalString (bootLoader == "systemd-boot") ''
51           boot.loader.systemd-boot.enable = true;
52         ''}
54         boot.initrd.secrets."/etc/secret" = "/etc/nixos/secret";
56         ${optionalString clevisTest ''
57           boot.kernelParams = [ "console=tty0" "ip=192.168.1.1:::255.255.255.0::eth1:none" ];
58           boot.initrd = {
59             availableKernelModules = [ "tpm_tis" ];
60             clevis = { enable = true; useTang = true; };
61             network.enable = true;
62           };
63           ''}
65         users.users.alice = {
66           isNormalUser = true;
67           home = "/home/alice";
68           description = "Alice Foobar";
69         };
71         hardware.enableAllFirmware = lib.mkForce false;
73         ${replaceStrings ["\n"] ["\n  "] extraConfig}
74       }
75     '';
78   # The test script boots a NixOS VM, installs NixOS on an empty hard
79   # disk, and then reboot from the hard disk.  It's parameterized with
80   # a test script fragment `createPartitions', which must create
81   # partitions and filesystems.
82   testScriptFun = { bootLoader, createPartitions, grubDevice, grubUseEfi, grubIdentifier
83                   , postInstallCommands, postBootCommands, extraConfig
84                   , testSpecialisationConfig, testFlakeSwitch, testByAttrSwitch, clevisTest, clevisFallbackTest
85                   , disableFileSystems
86                   }:
87     let
88       startTarget = ''
89         ${optionalString clevisTest "tpm.start()"}
90         target.start()
91         ${postBootCommands}
92         target.wait_for_unit("multi-user.target")
93       '';
94     in ''
95       ${optionalString clevisTest ''
96       import os
97       import subprocess
99       tpm_folder = os.environ['NIX_BUILD_TOP']
101       class Tpm:
102             def __init__(self):
103                 self.start()
105             def start(self):
106                 self.proc = subprocess.Popen(["${pkgs.swtpm}/bin/swtpm",
107                     "socket",
108                     "--tpmstate", f"dir={tpm_folder}/swtpm",
109                     "--ctrl", f"type=unixio,path={tpm_folder}/swtpm-sock",
110                     "--tpm2"
111                     ])
113                 # Check whether starting swtpm failed
114                 try:
115                     exit_code = self.proc.wait(timeout=0.2)
116                     if exit_code is not None and exit_code != 0:
117                         raise Exception("failed to start swtpm")
118                 except subprocess.TimeoutExpired:
119                     pass
121             """Check whether the swtpm process exited due to an error"""
122             def check(self):
123                 exit_code = self.proc.poll()
124                 if exit_code is not None and exit_code != 0:
125                     raise Exception("swtpm process died")
128       os.mkdir(f"{tpm_folder}/swtpm")
129       tpm = Tpm()
130       tpm.check()
131       ''}
133       installer.start()
134       ${optionalString clevisTest ''
135       tang.start()
136       tang.wait_for_unit("sockets.target")
137       tang.systemctl("start network-online.target")
138       tang.wait_for_unit("network-online.target")
139       installer.systemctl("start network-online.target")
140       installer.wait_for_unit("network-online.target")
141       ''}
142       installer.wait_for_unit("multi-user.target")
144       with subtest("Assert readiness of login prompt"):
145           installer.succeed("echo hello")
147       with subtest("Wait for hard disks to appear in /dev"):
148           installer.succeed("udevadm settle")
150       ${createPartitions}
152       with subtest("Create the NixOS configuration"):
153           installer.succeed("nixos-generate-config ${optionalString disableFileSystems "--no-filesystems"} --root /mnt")
154           installer.succeed("cat /mnt/etc/nixos/hardware-configuration.nix >&2")
155           installer.copy_from_host(
156               "${ makeConfig {
157                     inherit bootLoader grubDevice grubIdentifier
158                             grubUseEfi extraConfig clevisTest;
159                   }
160               }",
161               "/mnt/etc/nixos/configuration.nix",
162           )
163           installer.copy_from_host("${pkgs.writeText "secret" "secret"}", "/mnt/etc/nixos/secret")
165       ${optionalString clevisTest ''
166         with subtest("Create the Clevis secret with Tang"):
167              installer.systemctl("start network-online.target")
168              installer.wait_for_unit("network-online.target")
169              installer.succeed('echo -n password | clevis encrypt sss \'{"t": 2, "pins": {"tpm2": {}, "tang": {"url": "http://192.168.1.2"}}}\' -y > /mnt/etc/nixos/clevis-secret.jwe')''}
171       ${optionalString clevisFallbackTest ''
172         with subtest("Shutdown Tang to check fallback to interactive prompt"):
173             tang.shutdown()
174       ''}
176       with subtest("Perform the installation"):
177           installer.succeed("nixos-install < /dev/null >&2")
179       with subtest("Do it again to make sure it's idempotent"):
180           installer.succeed("nixos-install < /dev/null >&2")
182       with subtest("Check that we can build things in nixos-enter"):
183           installer.succeed(
184               """
185               nixos-enter -- nix-build --option substitute false -E 'derivation {
186                   name = "t";
187                   builder = "/bin/sh";
188                   args = ["-c" "echo nixos-enter build > $out"];
189                   system = builtins.currentSystem;
190                   preferLocalBuild = true;
191               }'
192               """
193           )
195       ${postInstallCommands}
197       with subtest("Shutdown system after installation"):
198           installer.succeed("umount -R /mnt")
199           installer.succeed("sync")
200           installer.shutdown()
202       # We're actually the same machine, just booting differently this time.
203       target.state_dir = installer.state_dir
205       # Now see if we can boot the installation.
206       ${startTarget}
208       with subtest("Assert that /boot get mounted"):
209           target.wait_for_unit("local-fs.target")
210           ${if bootLoader == "grub"
211               then ''target.succeed("test -e /boot/grub")''
212               else ''target.succeed("test -e /boot/loader/loader.conf")''
213           }
215       with subtest("Check whether /root has correct permissions"):
216           assert "700" in target.succeed("stat -c '%a' /root")
218       with subtest("Assert swap device got activated"):
219           # uncomment once https://bugs.freedesktop.org/show_bug.cgi?id=86930 is resolved
220           target.wait_for_unit("swap.target")
221           target.succeed("cat /proc/swaps | grep -q /dev")
223       with subtest("Check that the store is in good shape"):
224           target.succeed("nix-store --verify --check-contents >&2")
226       with subtest("Check whether the channel works"):
227           target.succeed("nix-env -iA nixos.procps >&2")
228           assert ".nix-profile" in target.succeed("type -tP ps | tee /dev/stderr")
230       with subtest(
231           "Check that the daemon works, and that non-root users can run builds "
232           "(this will build a new profile generation through the daemon)"
233       ):
234           target.succeed("su alice -l -c 'nix-env -iA nixos.procps' >&2")
236       with subtest("Configure system with writable Nix store on next boot"):
237           # we're not using copy_from_host here because the installer image
238           # doesn't know about the host-guest sharing mechanism.
239           target.copy_from_host_via_shell(
240               "${ makeConfig {
241                     inherit bootLoader grubDevice grubIdentifier
242                             grubUseEfi extraConfig clevisTest;
243                     forceGrubReinstallCount = 1;
244                   }
245               }",
246               "/etc/nixos/configuration.nix",
247           )
249       with subtest("Check whether nixos-rebuild works"):
250           target.succeed("nixos-rebuild switch >&2")
252       with subtest("Test nixos-option"):
253           kernel_modules = target.succeed("nixos-option boot.initrd.kernelModules")
254           assert "virtio_console" in kernel_modules
255           assert "List of modules" in kernel_modules
256           assert "qemu-guest.nix" in kernel_modules
258       target.shutdown()
260       # Check whether a writable store build works
261       ${startTarget}
263       # we're not using copy_from_host here because the installer image
264       # doesn't know about the host-guest sharing mechanism.
265       target.copy_from_host_via_shell(
266           "${ makeConfig {
267                 inherit bootLoader grubDevice grubIdentifier
268                 grubUseEfi extraConfig clevisTest;
269                 forceGrubReinstallCount = 2;
270               }
271           }",
272           "/etc/nixos/configuration.nix",
273       )
274       target.succeed("nixos-rebuild boot >&2")
275       target.shutdown()
277       # And just to be sure, check that the target still boots after "nixos-rebuild switch".
278       ${startTarget}
279       target.wait_for_unit("network.target")
281       # Sanity check, is it the configuration.nix we generated?
282       hostname = target.succeed("hostname").strip()
283       assert hostname == "thatworked"
285       target.shutdown()
287       # Tests for validating clone configuration entries in grub menu
288     ''
289     + optionalString testSpecialisationConfig ''
290       # Reboot target
291       ${startTarget}
293       with subtest("Booted configuration name should be 'Home'"):
294           # This is not the name that shows in the grub menu.
295           # The default configuration is always shown as "Default"
296           target.succeed("cat /run/booted-system/configuration-name >&2")
297           assert "Home" in target.succeed("cat /run/booted-system/configuration-name")
299       with subtest("We should **not** find a file named /etc/gitconfig"):
300           target.fail("test -e /etc/gitconfig")
302       with subtest("Set grub to boot the second configuration"):
303           target.succeed("grub-reboot 1")
305       target.shutdown()
307       # Reboot target
308       ${startTarget}
310       with subtest("Booted configuration name should be Work"):
311           target.succeed("cat /run/booted-system/configuration-name >&2")
312           assert "Work" in target.succeed("cat /run/booted-system/configuration-name")
314       with subtest("We should find a file named /etc/gitconfig"):
315           target.succeed("test -e /etc/gitconfig")
317       target.shutdown()
318     ''
319     + optionalString testByAttrSwitch ''
320       with subtest("Configure system with attribute set"):
321         target.succeed("""
322           mkdir /root/my-config
323           mv /etc/nixos/hardware-configuration.nix /root/my-config/
324           rm /etc/nixos/configuration.nix
325         """)
326         target.copy_from_host_via_shell(
327           "${makeConfig {
328                inherit bootLoader grubDevice grubIdentifier grubUseEfi extraConfig clevisTest;
329                forceGrubReinstallCount = 1;
330                withTestInstrumentation = false;
331             }}",
332           "/root/my-config/configuration.nix",
333         )
334         target.copy_from_host_via_shell(
335           "${./installer/byAttrWithChannel.nix}",
336           "/root/my-config/default.nix",
337         )
338       with subtest("Switch to attribute set based config with channels"):
339         target.succeed("nixos-rebuild switch --file /root/my-config/default.nix")
341       target.shutdown()
343       ${startTarget}
345       target.succeed("""
346         rm /root/my-config/default.nix
347       """)
348       target.copy_from_host_via_shell(
349         "${./installer/byAttrNoChannel.nix}",
350         "/root/my-config/default.nix",
351       )
353       target.succeed("""
354         pkgs=$(readlink -f /nix/var/nix/profiles/per-user/root/channels)/nixos
355         if ! [[ -e $pkgs/pkgs/top-level/default.nix ]]; then
356           echo 1>&2 "$pkgs does not seem to be a nixpkgs source. Please fix the test so that pkgs points to a nixpkgs source.";
357           exit 1;
358         fi
359         sed -e s^@nixpkgs@^$pkgs^ -i /root/my-config/default.nix
361       """)
363       with subtest("Switch to attribute set based config without channels"):
364         target.succeed("nixos-rebuild switch --file /root/my-config/default.nix")
366       target.shutdown()
368       ${startTarget}
370       with subtest("nix-channel command is not available anymore"):
371         target.succeed("! which nix-channel")
373       with subtest("builtins.nixPath is now empty"):
374         target.succeed("""
375           [[ "[ ]" == "$(nix-instantiate builtins.nixPath --eval --expr)" ]]
376         """)
378       with subtest("<nixpkgs> does not resolve"):
379         target.succeed("""
380           ! nix-instantiate '<nixpkgs>' --eval --expr
381         """)
383       with subtest("Evaluate attribute set based config in fresh env without nix-channel"):
384         target.succeed("nixos-rebuild switch --file /root/my-config/default.nix")
386       with subtest("Evaluate attribute set based config in fresh env without channel profiles"):
387         target.succeed("""
388           (
389             exec 1>&2
390             mkdir -p /root/restore
391             mv -v /root/.nix-channels /root/restore/
392             mv -v ~/.nix-defexpr /root/restore/
393             mkdir -p /root/restore/channels
394             mv -v /nix/var/nix/profiles/per-user/root/channels* /root/restore/channels/
395           )
396         """)
397         target.succeed("nixos-rebuild switch --file /root/my-config/default.nix")
398     ''
399     + optionalString (testByAttrSwitch && testFlakeSwitch) ''
400       with subtest("Restore channel profiles"):
401         target.succeed("""
402           (
403             exec 1>&2
404             mv -v /root/restore/.nix-channels /root/
405             mv -v /root/restore/.nix-defexpr ~/.nix-defexpr
406             mv -v /root/restore/channels/* /nix/var/nix/profiles/per-user/root/
407             rm -vrf /root/restore
408           )
409         """)
411       with subtest("Restore /etc/nixos"):
412         target.succeed("""
413           mv -v /root/my-config/hardware-configuration.nix /etc/nixos/
414         """)
415         target.copy_from_host_via_shell(
416           "${makeConfig {
417                inherit bootLoader grubDevice grubIdentifier grubUseEfi extraConfig clevisTest;
418                forceGrubReinstallCount = 1;
419             }}",
420           "/etc/nixos/configuration.nix",
421         )
423       with subtest("Restore /root/my-config"):
424         target.succeed("""
425           rm -vrf /root/my-config
426         """)
428     ''
429     + optionalString (testByAttrSwitch && !testFlakeSwitch) ''
430       target.shutdown()
431     ''
432     + optionalString testFlakeSwitch ''
433       ${startTarget}
435       with subtest("Configure system with flake"):
436         # TODO: evaluate as user?
437         target.succeed("""
438           mkdir /root/my-config
439           mv /etc/nixos/hardware-configuration.nix /root/my-config/
440           rm /etc/nixos/configuration.nix
441         """)
442         target.copy_from_host_via_shell(
443           "${makeConfig {
444                inherit bootLoader grubDevice grubIdentifier grubUseEfi extraConfig clevisTest;
445                forceGrubReinstallCount = 1;
446                withTestInstrumentation = false;
447             }}",
448           "/root/my-config/configuration.nix",
449         )
450         target.copy_from_host_via_shell(
451           "${./installer/flake.nix}",
452           "/root/my-config/flake.nix",
453         )
454         target.succeed("""
455           # for some reason the image does not have `pkgs.path`, so
456           # we use readlink to find a Nixpkgs source.
457           pkgs=$(readlink -f /nix/var/nix/profiles/per-user/root/channels)/nixos
458           if ! [[ -e $pkgs/pkgs/top-level/default.nix ]]; then
459             echo 1>&2 "$pkgs does not seem to be a nixpkgs source. Please fix the test so that pkgs points to a nixpkgs source.";
460             exit 1;
461           fi
462           sed -e s^@nixpkgs@^$pkgs^ -i /root/my-config/flake.nix
463         """)
465       with subtest("Switch to flake based config"):
466         target.succeed("nixos-rebuild switch --flake /root/my-config#xyz 2>&1 | tee activation-log >&2")
468         target.succeed("""
469           cat -n activation-log >&2
470         """)
472         target.succeed("""
473           grep -F '/root/.nix-defexpr/channels exists, but channels have been disabled.' activation-log
474         """)
475         target.succeed("""
476           grep -F '/nix/var/nix/profiles/per-user/root/channels exists, but channels have been disabled.' activation-log
477         """)
478         target.succeed("""
479           grep -F '/root/.nix-defexpr/channels exists, but channels have been disabled.' activation-log
480         """)
481         target.succeed("""
482           grep -F 'Due to https://github.com/NixOS/nix/issues/9574, Nix may still use these channels when NIX_PATH is unset.' activation-log
483         """)
484         target.succeed("rm activation-log")
486         # Perform the suggested cleanups we've just seen in the log
487         # TODO after https://github.com/NixOS/nix/issues/9574: don't remove them yet
488         target.succeed("""
489           rm -rf /root/.nix-defexpr/channels /nix/var/nix/profiles/per-user/root/channels /root/.nix-defexpr/channels
490         """)
493       target.shutdown()
495       ${startTarget}
497       with subtest("nix-channel command is not available anymore"):
498         target.succeed("! which nix-channel")
500       # Note that the channel profile is still present on disk, but configured
501       # not to be used.
502       # TODO after issue https://github.com/NixOS/nix/issues/9574: re-enable this assertion
503       # I believe what happens is
504       #   - because of the issue, we've removed the `nix-path =` line from nix.conf
505       #   - the "backdoor" shell is not a proper session and does not have `NIX_PATH=""` set
506       #   - seeing no nix path settings at all, Nix loads its hardcoded default value,
507       #     which is unfortunately non-empty
508       # Or maybe it's the new default NIX_PATH?? :(
509       # with subtest("builtins.nixPath is now empty"):
510       #   target.succeed("""
511       #     (
512       #       set -x;
513       #       [[ "[ ]" == "$(nix-instantiate builtins.nixPath --eval --expr)" ]];
514       #     )
515       #   """)
517       with subtest("<nixpkgs> does not resolve"):
518         target.succeed("""
519           ! nix-instantiate '<nixpkgs>' --eval --expr
520         """)
522       with subtest("Evaluate flake config in fresh env without nix-channel"):
523         target.succeed("nixos-rebuild switch --flake /root/my-config#xyz")
525       with subtest("Evaluate flake config in fresh env without channel profiles"):
526         target.succeed("""
527           (
528             exec 1>&2
529             rm -vf /root/.nix-channels
530             rm -vrf ~/.nix-defexpr
531             rm -vrf /nix/var/nix/profiles/per-user/root/channels*
532           )
533         """)
534         target.succeed("nixos-rebuild switch --flake /root/my-config#xyz | tee activation-log >&2")
535         target.succeed("cat -n activation-log >&2")
536         target.succeed("! grep -F '/root/.nix-defexpr/channels' activation-log")
537         target.succeed("! grep -F 'but channels have been disabled' activation-log")
538         target.succeed("! grep -F 'https://github.com/NixOS/nix/issues/9574' activation-log")
540       target.shutdown()
541     '';
544   makeInstallerTest = name:
545     { createPartitions
546     , postInstallCommands ? "", postBootCommands ? ""
547     , extraConfig ? ""
548     , extraInstallerConfig ? {}
549     , bootLoader ? "grub" # either "grub" or "systemd-boot"
550     , grubDevice ? "/dev/vda", grubIdentifier ? "uuid", grubUseEfi ? false
551     , enableOCR ? false, meta ? {}
552     , testSpecialisationConfig ? false
553     , testFlakeSwitch ? false
554     , testByAttrSwitch ? false
555     , clevisTest ? false
556     , clevisFallbackTest ? false
557     , disableFileSystems ? false
558     }:
559     let
560       isEfi = bootLoader == "systemd-boot" || (bootLoader == "grub" && grubUseEfi);
561     in makeTest {
562       inherit enableOCR;
563       name = "installer-" + name;
564       meta = {
565         # put global maintainers here, individuals go into makeInstallerTest fkt call
566         maintainers = (meta.maintainers or []);
567         # non-EFI tests can only run on x86
568         platforms = if isEfi then platforms.linux else [ "x86_64-linux" "i686-linux" ];
569       };
570       nodes = let
571         commonConfig = {
572           # builds stuff in the VM, needs more juice
573           virtualisation.diskSize = 8 * 1024;
574           virtualisation.cores = 8;
575           virtualisation.memorySize = 2048;
577           # both installer and target need to use the same drive
578           virtualisation.diskImage = "./target.qcow2";
580           # and the same TPM options
581           virtualisation.qemu.options = mkIf (clevisTest) [
582             "-chardev socket,id=chrtpm,path=$NIX_BUILD_TOP/swtpm-sock"
583             "-tpmdev emulator,id=tpm0,chardev=chrtpm"
584             "-device tpm-tis,tpmdev=tpm0"
585           ];
586         };
587       in {
588         # The configuration of the system used to run "nixos-install".
589         installer = {
590           imports = [
591             commonConfig
592             ../modules/profiles/installation-device.nix
593             ../modules/profiles/base.nix
594             extraInstallerConfig
595             ./common/auto-format-root-device.nix
596           ];
598           # In systemdStage1, also automatically format the device backing the
599           # root filesystem.
600           virtualisation.fileSystems."/".autoFormat = systemdStage1;
602           boot.initrd.systemd.enable = systemdStage1;
604           # Use a small /dev/vdb as the root disk for the
605           # installer. This ensures the target disk (/dev/vda) is
606           # the same during and after installation.
607           virtualisation.emptyDiskImages = [ 512 ];
608           virtualisation.rootDevice = "/dev/vdb";
610           hardware.enableAllFirmware = mkForce false;
612           # The test cannot access the network, so any packages we
613           # need must be included in the VM.
614           system.extraDependencies = with pkgs; [
615             bintools
616             brotli
617             brotli.dev
618             brotli.lib
619             desktop-file-utils
620             docbook5
621             docbook_xsl_ns
622             kbd.dev
623             kmod.dev
624             libarchive.dev
625             libxml2.bin
626             libxslt.bin
627             nixos-artwork.wallpapers.simple-dark-gray-bottom
628             ntp
629             perlPackages.ConfigIniFiles
630             perlPackages.FileSlurp
631             perlPackages.JSON
632             perlPackages.ListCompare
633             perlPackages.XMLLibXML
634             # make-options-doc/default.nix
635             (python3.withPackages (p: [ p.mistune ]))
636             shared-mime-info
637             sudo
638             switch-to-configuration-ng
639             texinfo
640             unionfs-fuse
641             xorg.lndir
643             # add curl so that rather than seeing the test attempt to download
644             # curl's tarball, we see what it's trying to download
645             curl
646           ]
647           ++ optionals (bootLoader == "grub") (let
648             zfsSupport = extraInstallerConfig.boot.supportedFilesystems.zfs or false;
649           in [
650             (pkgs.grub2.override { inherit zfsSupport; })
651             (pkgs.grub2_efi.override { inherit zfsSupport; })
652             pkgs.nixos-artwork.wallpapers.simple-dark-gray-bootloader
653             pkgs.perlPackages.FileCopyRecursive
654             pkgs.perlPackages.XMLSAX
655             pkgs.perlPackages.XMLSAXBase
656           ])
657           ++ optionals (bootLoader == "systemd-boot") [
658             pkgs.zstd.bin
659             pkgs.mypy
660             pkgs.bootspec
661           ]
662           ++ optionals clevisTest [ pkgs.klibc ];
664           nix.settings = {
665             substituters = mkForce [];
666             hashed-mirrors = null;
667             connect-timeout = 1;
668           };
669         };
671         target = {
672           imports = [ commonConfig ];
673           virtualisation.useBootLoader = true;
674           virtualisation.useEFIBoot = isEfi;
675           virtualisation.useDefaultFilesystems = false;
676           virtualisation.efi.keepVariables = false;
678           virtualisation.fileSystems."/" = {
679             device = "/dev/disk/by-label/this-is-not-real-and-will-never-be-used";
680             fsType = "ext4";
681           };
682         };
683       } // optionalAttrs clevisTest {
684         tang = {
685           services.tang = {
686             enable = true;
687             listenStream = [ "80" ];
688             ipAddressAllow = [ "192.168.1.0/24" ];
689           };
690           networking.firewall.allowedTCPPorts = [ 80 ];
691         };
692       };
694       testScript = testScriptFun {
695         inherit bootLoader createPartitions postInstallCommands postBootCommands
696                 grubDevice grubIdentifier grubUseEfi extraConfig
697                 testSpecialisationConfig testFlakeSwitch testByAttrSwitch clevisTest clevisFallbackTest
698                 disableFileSystems;
699       };
700     };
702     makeLuksRootTest = name: luksFormatOpts: makeInstallerTest name {
703       createPartitions = ''
704         installer.succeed(
705             "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
706             + " mkpart primary ext2 1M 100MB"  # /boot
707             + " mkpart primary linux-swap 100M 1024M"
708             + " mkpart primary 1024M -1s",  # LUKS
709             "udevadm settle",
710             "mkswap /dev/vda2 -L swap",
711             "swapon -L swap",
712             "modprobe dm_mod dm_crypt",
713             "echo -n supersecret | cryptsetup luksFormat ${luksFormatOpts} -q /dev/vda3 -",
714             "echo -n supersecret | cryptsetup luksOpen --key-file - /dev/vda3 cryptroot",
715             "mkfs.ext3 -L nixos /dev/mapper/cryptroot",
716             "mount LABEL=nixos /mnt",
717             "mkfs.ext3 -L boot /dev/vda1",
718             "mkdir -p /mnt/boot",
719             "mount LABEL=boot /mnt/boot",
720         )
721       '';
722       extraConfig = ''
723         boot.kernelParams = lib.mkAfter [ "console=tty0" ];
724       '';
725       enableOCR = true;
726       postBootCommands = ''
727         target.wait_for_text("[Pp]assphrase for")
728         target.send_chars("supersecret\n")
729       '';
730     };
732   # The (almost) simplest partitioning scheme: a swap partition and
733   # one big filesystem partition.
734   simple-test-config = {
735     createPartitions = ''
736       installer.succeed(
737           "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
738           + " mkpart primary linux-swap 1M 1024M"
739           + " mkpart primary ext2 1024M -1s",
740           "udevadm settle",
741           "mkswap /dev/vda1 -L swap",
742           "swapon -L swap",
743           "mkfs.ext3 -L nixos /dev/vda2",
744           "mount LABEL=nixos /mnt",
745       )
746     '';
747   };
749   simple-test-config-flake = simple-test-config // {
750     testFlakeSwitch = true;
751   };
753   simple-test-config-by-attr = simple-test-config // {
754     testByAttrSwitch = true;
755   };
757   simple-test-config-from-by-attr-to-flake = simple-test-config // {
758     testByAttrSwitch = true;
759     testFlakeSwitch = true;
760   };
762   simple-uefi-grub-config = {
763     createPartitions = ''
764       installer.succeed(
765           "flock /dev/vda parted --script /dev/vda -- mklabel gpt"
766           + " mkpart ESP fat32 1M 100MiB"  # /boot
767           + " set 1 boot on"
768           + " mkpart primary linux-swap 100MiB 1024MiB"
769           + " mkpart primary ext2 1024MiB -1MiB",  # /
770           "udevadm settle",
771           "mkswap /dev/vda2 -L swap",
772           "swapon -L swap",
773           "mkfs.ext3 -L nixos /dev/vda3",
774           "mount LABEL=nixos /mnt",
775           "mkfs.vfat -n BOOT /dev/vda1",
776           "mkdir -p /mnt/boot",
777           "mount LABEL=BOOT /mnt/boot",
778       )
779     '';
780     bootLoader = "grub";
781     grubUseEfi = true;
782   };
784   specialisation-test-extraconfig = {
785     extraConfig = ''
786       environment.systemPackages = [ pkgs.grub2 ];
787       boot.loader.grub.configurationName = "Home";
788       specialisation.work.configuration = {
789         boot.loader.grub.configurationName = lib.mkForce "Work";
791         environment.etc = {
792           "gitconfig".text = "
793             [core]
794               gitproxy = none for work.com
795               ";
796         };
797       };
798     '';
799     testSpecialisationConfig = true;
800   };
801   # disable zfs so we can support latest kernel if needed
802   no-zfs-module = {
803     nixpkgs.overlays = [(final: super: {
804       zfs = super.zfs.overrideAttrs(_: {meta.platforms = [];});}
805     )];
806   };
808  mkClevisBcachefsTest = { fallback ? false }: makeInstallerTest "clevis-bcachefs${optionalString fallback "-fallback"}" {
809     clevisTest = true;
810     clevisFallbackTest = fallback;
811     enableOCR = fallback;
812     extraInstallerConfig = {
813       imports = [ no-zfs-module ];
814       boot.supportedFilesystems = [ "bcachefs" ];
815       environment.systemPackages = with pkgs; [ keyutils clevis ];
816     };
817     createPartitions = ''
818       installer.succeed(
819         "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
820         + " mkpart primary ext2 1M 100MB"
821         + " mkpart primary linux-swap 100M 1024M"
822         + " mkpart primary 1024M -1s",
823         "udevadm settle",
824         "mkswap /dev/vda2 -L swap",
825         "swapon -L swap",
826         "keyctl link @u @s",
827         "echo -n password | mkfs.bcachefs -L root --encrypted /dev/vda3",
828         "echo -n password | bcachefs unlock /dev/vda3",
829         "echo -n password | mount -t bcachefs /dev/vda3 /mnt",
830         "mkfs.ext3 -L boot /dev/vda1",
831         "mkdir -p /mnt/boot",
832         "mount LABEL=boot /mnt/boot",
833         "udevadm settle")
834     '';
835     extraConfig = ''
836       boot.initrd.clevis.devices."/dev/vda3".secretFile = "/etc/nixos/clevis-secret.jwe";
838       # We override what nixos-generate-config has generated because we do
839       # not know the UUID in advance.
840       fileSystems."/" = lib.mkForce { device = "/dev/vda3"; fsType = "bcachefs"; };
841     '';
842     postBootCommands = optionalString fallback ''
843       target.wait_for_text("enter passphrase for")
844       target.send_chars("password\n")
845     '';
846   };
848   mkClevisLuksTest = { fallback ? false }: makeInstallerTest "clevis-luks${optionalString fallback "-fallback"}" {
849     clevisTest = true;
850     clevisFallbackTest = fallback;
851     enableOCR = fallback;
852     extraInstallerConfig = {
853       environment.systemPackages = with pkgs; [ clevis ];
854     };
855     createPartitions = ''
856       installer.succeed(
857         "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
858         + " mkpart primary ext2 1M 100MB"
859         + " mkpart primary linux-swap 100M 1024M"
860         + " mkpart primary 1024M -1s",
861         "udevadm settle",
862         "mkswap /dev/vda2 -L swap",
863         "swapon -L swap",
864         "modprobe dm_mod dm_crypt",
865         "echo -n password | cryptsetup luksFormat -q /dev/vda3 -",
866         "echo -n password | cryptsetup luksOpen --key-file - /dev/vda3 crypt-root",
867         "mkfs.ext3 -L nixos /dev/mapper/crypt-root",
868         "mount LABEL=nixos /mnt",
869         "mkfs.ext3 -L boot /dev/vda1",
870         "mkdir -p /mnt/boot",
871         "mount LABEL=boot /mnt/boot",
872         "udevadm settle")
873     '';
874     extraConfig = ''
875       boot.initrd.clevis.devices."crypt-root".secretFile = "/etc/nixos/clevis-secret.jwe";
876     '';
877     postBootCommands = optionalString fallback ''
878       ${if systemdStage1 then ''
879       target.wait_for_text("Please enter")
880       '' else ''
881       target.wait_for_text("Passphrase for")
882       ''}
883       target.send_chars("password\n")
884     '';
885   };
887   mkClevisZfsTest = { fallback ? false, parentDataset ? false }: makeInstallerTest "clevis-zfs${optionalString parentDataset "-parent-dataset"}${optionalString fallback "-fallback"}" {
888     clevisTest = true;
889     clevisFallbackTest = fallback;
890     enableOCR = fallback;
891     extraInstallerConfig = {
892       boot.supportedFilesystems = [ "zfs" ];
893       environment.systemPackages = with pkgs; [ clevis ];
894     };
895     createPartitions = ''
896       installer.succeed(
897         "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
898         + " mkpart primary ext2 1M 100MB"
899         + " mkpart primary linux-swap 100M 1024M"
900         + " mkpart primary 1024M -1s",
901         "udevadm settle",
902         "mkswap /dev/vda2 -L swap",
903         "swapon -L swap",
904     '' + optionalString (!parentDataset) ''
905         "zpool create -O mountpoint=legacy rpool /dev/vda3",
906         "echo -n password | zfs create"
907         + " -o encryption=aes-256-gcm -o keyformat=passphrase rpool/root",
908     '' + optionalString (parentDataset) ''
909         "echo -n password | zpool create -O mountpoint=none -O encryption=on -O keyformat=passphrase rpool /dev/vda3",
910         "zfs create -o mountpoint=legacy rpool/root",
911     '' +
912     ''
913         "mount -t zfs rpool/root /mnt",
914         "mkfs.ext3 -L boot /dev/vda1",
915         "mkdir -p /mnt/boot",
916         "mount LABEL=boot /mnt/boot",
917         "udevadm settle")
918     '';
919     extraConfig = optionalString (!parentDataset) ''
920       boot.initrd.clevis.devices."rpool/root".secretFile = "/etc/nixos/clevis-secret.jwe";
921     '' + optionalString (parentDataset) ''
922       boot.initrd.clevis.devices."rpool".secretFile = "/etc/nixos/clevis-secret.jwe";
923     '' +
924     ''
925       boot.zfs.requestEncryptionCredentials = true;
928       # Using by-uuid overrides the default of by-id, and is unique
929       # to the qemu disks, as they don't produce by-id paths for
930       # some reason.
931       boot.zfs.devNodes = "/dev/disk/by-uuid/";
932       networking.hostId = "00000000";
933     '';
934     postBootCommands = optionalString fallback ''
935       ${if systemdStage1 then ''
936       target.wait_for_text("Enter key for rpool/root")
937       '' else ''
938       target.wait_for_text("Key load error")
939       ''}
940       target.send_chars("password\n")
941     '';
942   };
944 in {
946   # !!! `parted mkpart' seems to silently create overlapping partitions.
949   # The (almost) simplest partitioning scheme: a swap partition and
950   # one big filesystem partition.
951   simple = makeInstallerTest "simple" simple-test-config;
953   switchToFlake = makeInstallerTest "switch-to-flake" simple-test-config-flake;
955   switchToByAttr = makeInstallerTest "switch-to-by-attr" simple-test-config-by-attr;
957   switchFromByAttrToFlake = makeInstallerTest "switch-from-by-attr-to-flake" simple-test-config-from-by-attr-to-flake;
959   # Test cloned configurations with the simple grub configuration
960   simpleSpecialised = makeInstallerTest "simpleSpecialised" (simple-test-config // specialisation-test-extraconfig);
962   # Simple GPT/UEFI configuration using systemd-boot with 3 partitions: ESP, swap & root filesystem
963   simpleUefiSystemdBoot = makeInstallerTest "simpleUefiSystemdBoot" {
964     createPartitions = ''
965       installer.succeed(
966           "flock /dev/vda parted --script /dev/vda -- mklabel gpt"
967           + " mkpart ESP fat32 1M 100MiB"  # /boot
968           + " set 1 boot on"
969           + " mkpart primary linux-swap 100MiB 1024MiB"
970           + " mkpart primary ext2 1024MiB -1MiB",  # /
971           "udevadm settle",
972           "mkswap /dev/vda2 -L swap",
973           "swapon -L swap",
974           "mkfs.ext3 -L nixos /dev/vda3",
975           "mount LABEL=nixos /mnt",
976           "mkfs.vfat -n BOOT /dev/vda1",
977           "mkdir -p /mnt/boot",
978           "mount LABEL=BOOT /mnt/boot",
979       )
980     '';
981     bootLoader = "systemd-boot";
982   };
984   simpleUefiGrub = makeInstallerTest "simpleUefiGrub" simple-uefi-grub-config;
986   # Test cloned configurations with the uefi grub configuration
987   simpleUefiGrubSpecialisation = makeInstallerTest "simpleUefiGrubSpecialisation" (simple-uefi-grub-config // specialisation-test-extraconfig);
989   # Same as the previous, but now with a separate /boot partition.
990   separateBoot = makeInstallerTest "separateBoot" {
991     createPartitions = ''
992       installer.succeed(
993           "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
994           + " mkpart primary ext2 1M 100MB"  # /boot
995           + " mkpart primary linux-swap 100MB 1024M"
996           + " mkpart primary ext2 1024M -1s",  # /
997           "udevadm settle",
998           "mkswap /dev/vda2 -L swap",
999           "swapon -L swap",
1000           "mkfs.ext3 -L nixos /dev/vda3",
1001           "mount LABEL=nixos /mnt",
1002           "mkfs.ext3 -L boot /dev/vda1",
1003           "mkdir -p /mnt/boot",
1004           "mount LABEL=boot /mnt/boot",
1005       )
1006     '';
1007   };
1009   # Same as the previous, but with fat32 /boot.
1010   separateBootFat = makeInstallerTest "separateBootFat" {
1011     createPartitions = ''
1012       installer.succeed(
1013           "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
1014           + " mkpart primary ext2 1M 100MB"  # /boot
1015           + " mkpart primary linux-swap 100MB 1024M"
1016           + " mkpart primary ext2 1024M -1s",  # /
1017           "udevadm settle",
1018           "mkswap /dev/vda2 -L swap",
1019           "swapon -L swap",
1020           "mkfs.ext3 -L nixos /dev/vda3",
1021           "mount LABEL=nixos /mnt",
1022           "mkfs.vfat -n BOOT /dev/vda1",
1023           "mkdir -p /mnt/boot",
1024           "mount LABEL=BOOT /mnt/boot",
1025       )
1026     '';
1027   };
1029   # Same as the previous, but with ZFS /boot.
1030   separateBootZfs = makeInstallerTest "separateBootZfs" {
1031     extraInstallerConfig = {
1032       boot.supportedFilesystems = [ "zfs" ];
1033     };
1035     extraConfig = ''
1036       # Using by-uuid overrides the default of by-id, and is unique
1037       # to the qemu disks, as they don't produce by-id paths for
1038       # some reason.
1039       boot.zfs.devNodes = "/dev/disk/by-uuid/";
1040       networking.hostId = "00000000";
1041     '';
1043     createPartitions = ''
1044       installer.succeed(
1045           "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
1046           + " mkpart primary ext2 1M 256MB"   # /boot
1047           + " mkpart primary linux-swap 256MB 1280M"
1048           + " mkpart primary ext2 1280M -1s", # /
1049           "udevadm settle",
1051           "mkswap /dev/vda2 -L swap",
1052           "swapon -L swap",
1054           "mkfs.ext4 -L nixos /dev/vda3",
1055           "mount LABEL=nixos /mnt",
1057           # Use as many ZFS features as possible to verify that GRUB can handle them
1058           "zpool create"
1059             " -o compatibility=grub2"
1060             " -O utf8only=on"
1061             " -O normalization=formD"
1062             " -O compression=lz4"      # Activate the lz4_compress feature
1063             " -O xattr=sa"
1064             " -O acltype=posixacl"
1065             " bpool /dev/vda1",
1066           "zfs create"
1067             " -o recordsize=1M"        # Prepare activating the large_blocks feature
1068             " -o mountpoint=legacy"
1069             " -o relatime=on"
1070             " -o quota=1G"
1071             " -o filesystem_limit=100" # Activate the filesystem_limits features
1072             " bpool/boot",
1074           # Snapshotting the top-level dataset would trigger a bug in GRUB2: https://github.com/openzfs/zfs/issues/13873
1075           "zfs snapshot bpool/boot@snap-1",                     # Prepare activating the livelist and bookmarks features
1076           "zfs clone bpool/boot@snap-1 bpool/test",             # Activate the livelist feature
1077           "zfs bookmark bpool/boot@snap-1 bpool/boot#bookmark", # Activate the bookmarks feature
1078           "zpool checkpoint bpool",                             # Activate the zpool_checkpoint feature
1079           "mkdir -p /mnt/boot",
1080           "mount -t zfs bpool/boot /mnt/boot",
1081           "touch /mnt/boot/empty",                              # Activate zilsaxattr feature
1082           "dd if=/dev/urandom of=/mnt/boot/test bs=1M count=1", # Activate the large_blocks feature
1084           # Print out all enabled and active ZFS features (and some other stuff)
1085           "sync /mnt/boot",
1086           "zpool get all bpool >&2",
1088           # Abort early if GRUB2 doesn't like the disks
1089           "grub-probe --target=device /mnt/boot >&2",
1090       )
1091     '';
1093     # umount & export bpool before shutdown
1094     # this is a fix for "cannot import 'bpool': pool was previously in use from another system."
1095     postInstallCommands = ''
1096       installer.succeed("umount /mnt/boot")
1097       installer.succeed("zpool export bpool")
1098     '';
1099   };
1101   # zfs on / with swap
1102   zfsroot = makeInstallerTest "zfs-root" {
1103     extraInstallerConfig = {
1104       boot.supportedFilesystems = [ "zfs" ];
1105     };
1107     extraConfig = ''
1108       boot.supportedFilesystems = [ "zfs" ];
1110       # Using by-uuid overrides the default of by-id, and is unique
1111       # to the qemu disks, as they don't produce by-id paths for
1112       # some reason.
1113       boot.zfs.devNodes = "/dev/disk/by-uuid/";
1114       networking.hostId = "00000000";
1115     '';
1117     createPartitions = ''
1118       installer.succeed(
1119           "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
1120           + " mkpart primary 1M 100MB"  # /boot
1121           + " mkpart primary linux-swap 100M 1024M"
1122           + " mkpart primary 1024M -1s", # rpool
1123           "udevadm settle",
1124           "mkswap /dev/vda2 -L swap",
1125           "swapon -L swap",
1126           "zpool create rpool /dev/vda3",
1127           "zfs create -o mountpoint=legacy rpool/root",
1128           "mount -t zfs rpool/root /mnt",
1129           "zfs create -o mountpoint=legacy rpool/root/usr",
1130           "mkdir /mnt/usr",
1131           "mount -t zfs rpool/root/usr /mnt/usr",
1132           "mkfs.vfat -n BOOT /dev/vda1",
1133           "mkdir /mnt/boot",
1134           "mount LABEL=BOOT /mnt/boot",
1135           "udevadm settle",
1136       )
1137     '';
1138   };
1140   # Create two physical LVM partitions combined into one volume group
1141   # that contains the logical swap and root partitions.
1142   lvm = makeInstallerTest "lvm" {
1143     createPartitions = ''
1144       installer.succeed(
1145           "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
1146           + " mkpart primary 1M 2048M"  # PV1
1147           + " set 1 lvm on"
1148           + " mkpart primary 2048M -1s"  # PV2
1149           + " set 2 lvm on",
1150           "udevadm settle",
1151           "pvcreate /dev/vda1 /dev/vda2",
1152           "vgcreate MyVolGroup /dev/vda1 /dev/vda2",
1153           "lvcreate --size 1G --name swap MyVolGroup",
1154           "lvcreate --size 6G --name nixos MyVolGroup",
1155           "mkswap -f /dev/MyVolGroup/swap -L swap",
1156           "swapon -L swap",
1157           "mkfs.xfs -L nixos /dev/MyVolGroup/nixos",
1158           "mount LABEL=nixos /mnt",
1159       )
1160     '';
1161     extraConfig = optionalString systemdStage1 ''
1162       boot.initrd.services.lvm.enable = true;
1163     '';
1164   };
1166   # Boot off an encrypted root partition with the default LUKS header format
1167   luksroot = makeLuksRootTest "luksroot-format1" "";
1169   # Boot off an encrypted root partition with LUKS1 format
1170   luksroot-format1 = makeLuksRootTest "luksroot-format1" "--type=LUKS1";
1172   # Boot off an encrypted root partition with LUKS2 format
1173   luksroot-format2 = makeLuksRootTest "luksroot-format2" "--type=LUKS2";
1175   # Test whether opening encrypted filesystem with keyfile
1176   # Checks for regression of missing cryptsetup, when no luks device without
1177   # keyfile is configured
1178   encryptedFSWithKeyfile = makeInstallerTest "encryptedFSWithKeyfile" {
1179     createPartitions = ''
1180       installer.succeed(
1181           "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
1182           + " mkpart primary ext2 1M 100MB"  # /boot
1183           + " mkpart primary linux-swap 100M 1024M"
1184           + " mkpart primary 1024M 1280M"  # LUKS with keyfile
1185           + " mkpart primary 1280M -1s",
1186           "udevadm settle",
1187           "mkswap /dev/vda2 -L swap",
1188           "swapon -L swap",
1189           "mkfs.ext3 -L nixos /dev/vda4",
1190           "mount LABEL=nixos /mnt",
1191           "mkfs.ext3 -L boot /dev/vda1",
1192           "mkdir -p /mnt/boot",
1193           "mount LABEL=boot /mnt/boot",
1194           "modprobe dm_mod dm_crypt",
1195           "echo -n supersecret > /mnt/keyfile",
1196           "cryptsetup luksFormat -q /dev/vda3 --key-file /mnt/keyfile",
1197           "cryptsetup luksOpen --key-file /mnt/keyfile /dev/vda3 crypt",
1198           "mkfs.ext3 -L test /dev/mapper/crypt",
1199           "cryptsetup luksClose crypt",
1200           "mkdir -p /mnt/test",
1201       )
1202     '';
1203     extraConfig = ''
1204       fileSystems."/test" = {
1205         device = "/dev/disk/by-label/test";
1206         fsType = "ext3";
1207         encrypted.enable = true;
1208         encrypted.blkDev = "/dev/vda3";
1209         encrypted.label = "crypt";
1210         encrypted.keyFile = "/${if systemdStage1 then "sysroot" else "mnt-root"}/keyfile";
1211       };
1212     '';
1213   };
1215   # Full disk encryption (root, kernel and initrd encrypted) using GRUB, GPT/UEFI,
1216   # LVM-on-LUKS and a keyfile in initrd.secrets to enter the passphrase once
1217   fullDiskEncryption = makeInstallerTest "fullDiskEncryption" {
1218     createPartitions = ''
1219       installer.succeed(
1220           "flock /dev/vda parted --script /dev/vda -- mklabel gpt"
1221           + " mkpart ESP fat32 1M 100MiB"  # /boot/efi
1222           + " set 1 boot on"
1223           + " mkpart primary ext2 1024MiB -1MiB",  # LUKS
1224           "udevadm settle",
1225           "modprobe dm_mod dm_crypt",
1226           "dd if=/dev/random of=luks.key bs=256 count=1",
1227           "echo -n supersecret | cryptsetup luksFormat -q --pbkdf-force-iterations 1000 --type luks1 /dev/vda2 -",
1228           "echo -n supersecret | cryptsetup luksAddKey -q --pbkdf-force-iterations 1000 --key-file - /dev/vda2 luks.key",
1229           "echo -n supersecret | cryptsetup luksOpen --key-file - /dev/vda2 crypt",
1230           "pvcreate /dev/mapper/crypt",
1231           "vgcreate crypt /dev/mapper/crypt",
1232           "lvcreate -L 100M -n swap crypt",
1233           "lvcreate -l '100%FREE' -n nixos crypt",
1234           "mkfs.vfat -n efi /dev/vda1",
1235           "mkfs.ext4 -L nixos /dev/crypt/nixos",
1236           "mkswap -L swap /dev/crypt/swap",
1237           "mount LABEL=nixos /mnt",
1238           "mkdir -p /mnt/{etc/nixos,boot/efi}",
1239           "mount LABEL=efi /mnt/boot/efi",
1240           "swapon -L swap",
1241           "mv luks.key /mnt/etc/nixos/"
1242       )
1243     '';
1244     bootLoader = "grub";
1245     grubUseEfi = true;
1246     extraConfig = ''
1247       boot.loader.grub.enableCryptodisk = true;
1248       boot.loader.efi.efiSysMountPoint = "/boot/efi";
1250       boot.initrd.secrets."/luks.key" = "/etc/nixos/luks.key";
1251       boot.initrd.luks.devices.crypt =
1252         { device  = "/dev/vda2";
1253           keyFile = "/luks.key";
1254         };
1255     '';
1256     enableOCR = true;
1257     postBootCommands = ''
1258       target.wait_for_text("Enter passphrase for")
1259       target.send_chars("supersecret\n")
1260     '';
1261   };
1263   swraid = makeInstallerTest "swraid" {
1264     createPartitions = ''
1265       installer.succeed(
1266           "flock /dev/vda parted --script /dev/vda --"
1267           + " mklabel msdos"
1268           + " mkpart primary ext2 1M 100MB"  # /boot
1269           + " mkpart extended 100M -1s"
1270           + " mkpart logical 102M 3102M"  # md0 (root), first device
1271           + " mkpart logical 3103M 6103M"  # md0 (root), second device
1272           + " mkpart logical 6104M 6360M"  # md1 (swap), first device
1273           + " mkpart logical 6361M 6617M",  # md1 (swap), second device
1274           "udevadm settle",
1275           "ls -l /dev/vda* >&2",
1276           "cat /proc/partitions >&2",
1277           "udevadm control --stop-exec-queue",
1278           "mdadm --create --force /dev/md0 --metadata 1.2 --level=raid1 "
1279           + "--raid-devices=2 /dev/vda5 /dev/vda6",
1280           "mdadm --create --force /dev/md1 --metadata 1.2 --level=raid1 "
1281           + "--raid-devices=2 /dev/vda7 /dev/vda8",
1282           "udevadm control --start-exec-queue",
1283           "udevadm settle",
1284           "mkswap -f /dev/md1 -L swap",
1285           "swapon -L swap",
1286           "mkfs.ext3 -L nixos /dev/md0",
1287           "mount LABEL=nixos /mnt",
1288           "mkfs.ext3 -L boot /dev/vda1",
1289           "mkdir /mnt/boot",
1290           "mount LABEL=boot /mnt/boot",
1291           "udevadm settle",
1292       )
1293     '';
1294     postBootCommands = ''
1295       target.fail("dmesg | grep 'immediate safe mode'")
1296     '';
1297   };
1299   bcache = makeInstallerTest "bcache" {
1300     createPartitions = ''
1301       installer.succeed(
1302           "flock /dev/vda parted --script /dev/vda --"
1303           + " mklabel msdos"
1304           + " mkpart primary ext2 1M 100MB"  # /boot
1305           + " mkpart primary 100MB 512MB  "  # swap
1306           + " mkpart primary 512MB 1024MB"  # Cache (typically SSD)
1307           + " mkpart primary 1024MB -1s ",  # Backing device (typically HDD)
1308           "modprobe bcache",
1309           "udevadm settle",
1310           "make-bcache -B /dev/vda4 -C /dev/vda3",
1311           "udevadm settle",
1312           "mkfs.ext3 -L nixos /dev/bcache0",
1313           "mount LABEL=nixos /mnt",
1314           "mkfs.ext3 -L boot /dev/vda1",
1315           "mkdir /mnt/boot",
1316           "mount LABEL=boot /mnt/boot",
1317           "mkswap -f /dev/vda2 -L swap",
1318           "swapon -L swap",
1319       )
1320     '';
1321   };
1323   bcachefsSimple = makeInstallerTest "bcachefs-simple" {
1324     extraInstallerConfig = {
1325       boot.supportedFilesystems = [ "bcachefs" ];
1326       imports = [ no-zfs-module ];
1327     };
1329     createPartitions = ''
1330       installer.succeed(
1331         "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
1332         + " mkpart primary ext2 1M 100MB"          # /boot
1333         + " mkpart primary linux-swap 100M 1024M"  # swap
1334         + " mkpart primary 1024M -1s",             # /
1335         "udevadm settle",
1336         "mkswap /dev/vda2 -L swap",
1337         "swapon -L swap",
1338         "mkfs.bcachefs -L root /dev/vda3",
1339         "mount -t bcachefs /dev/vda3 /mnt",
1340         "mkfs.ext3 -L boot /dev/vda1",
1341         "mkdir -p /mnt/boot",
1342         "mount /dev/vda1 /mnt/boot",
1343       )
1344     '';
1345   };
1347   bcachefsEncrypted = makeInstallerTest "bcachefs-encrypted" {
1348     extraInstallerConfig = {
1349       boot.supportedFilesystems = [ "bcachefs" ];
1351       # disable zfs so we can support latest kernel if needed
1352       imports = [ no-zfs-module ];
1354       environment.systemPackages = with pkgs; [ keyutils ];
1355     };
1357     extraConfig = ''
1358       boot.kernelParams = lib.mkAfter [ "console=tty0" ];
1359     '';
1361     enableOCR = true;
1362     postBootCommands = ''
1363       # Enter it wrong once
1364       target.wait_for_text("enter passphrase for ")
1365       target.send_chars("wrong\n")
1366       # Then enter it right.
1367       target.wait_for_text("enter passphrase for ")
1368       target.send_chars("password\n")
1369     '';
1371     createPartitions = ''
1372       installer.succeed(
1373         "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
1374         + " mkpart primary ext2 1M 100MB"          # /boot
1375         + " mkpart primary linux-swap 100M 1024M"  # swap
1376         + " mkpart primary 1024M -1s",             # /
1377         "udevadm settle",
1378         "mkswap /dev/vda2 -L swap",
1379         "swapon -L swap",
1380         "echo password | mkfs.bcachefs -L root --encrypted /dev/vda3",
1381         "echo password | bcachefs unlock -k session /dev/vda3",
1382         "echo password | mount -t bcachefs /dev/vda3 /mnt",
1383         "mkfs.ext3 -L boot /dev/vda1",
1384         "mkdir -p /mnt/boot",
1385         "mount /dev/vda1 /mnt/boot",
1386       )
1387     '';
1388   };
1390   bcachefsMulti = makeInstallerTest "bcachefs-multi" {
1391     extraInstallerConfig = {
1392       boot.supportedFilesystems = [ "bcachefs" ];
1394       # disable zfs so we can support latest kernel if needed
1395       imports = [ no-zfs-module ];
1396     };
1398     createPartitions = ''
1399       installer.succeed(
1400         "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
1401         + " mkpart primary ext2 1M 100MB"          # /boot
1402         + " mkpart primary linux-swap 100M 1024M"  # swap
1403         + " mkpart primary 1024M 4096M"            # /
1404         + " mkpart primary 4096M -1s",             # /
1405         "udevadm settle",
1406         "mkswap /dev/vda2 -L swap",
1407         "swapon -L swap",
1408         "mkfs.bcachefs -L root --metadata_replicas 2 --foreground_target ssd --promote_target ssd --background_target hdd --label ssd /dev/vda3 --label hdd /dev/vda4",
1409         "mount -t bcachefs /dev/vda3:/dev/vda4 /mnt",
1410         "mkfs.ext3 -L boot /dev/vda1",
1411         "mkdir -p /mnt/boot",
1412         "mount /dev/vda1 /mnt/boot",
1413       )
1414     '';
1415   };
1417   # Test using labels to identify volumes in grub
1418   simpleLabels = makeInstallerTest "simpleLabels" {
1419     createPartitions = ''
1420       installer.succeed(
1421           "sgdisk -Z /dev/vda",
1422           "sgdisk -n 1:0:+1M -n 2:0:+1G -N 3 -t 1:ef02 -t 2:8200 -t 3:8300 -c 3:root /dev/vda",
1423           "mkswap /dev/vda2 -L swap",
1424           "swapon -L swap",
1425           "mkfs.ext4 -L root /dev/vda3",
1426           "mount LABEL=root /mnt",
1427       )
1428     '';
1429     grubIdentifier = "label";
1430   };
1432   # Test using the provided disk name within grub
1433   # TODO: Fix udev so the symlinks are unneeded in /dev/disks
1434   simpleProvided = makeInstallerTest "simpleProvided" {
1435     createPartitions = ''
1436       uuid = "$(blkid -s UUID -o value /dev/vda2)"
1437       installer.succeed(
1438           "sgdisk -Z /dev/vda",
1439           "sgdisk -n 1:0:+1M -n 2:0:+100M -n 3:0:+1G -N 4 -t 1:ef02 -t 2:8300 "
1440           + "-t 3:8200 -t 4:8300 -c 2:boot -c 4:root /dev/vda",
1441           "mkswap /dev/vda3 -L swap",
1442           "swapon -L swap",
1443           "mkfs.ext4 -L boot /dev/vda2",
1444           "mkfs.ext4 -L root /dev/vda4",
1445       )
1446       installer.execute(f"ln -s ../../vda2 /dev/disk/by-uuid/{uuid}")
1447       installer.execute("ln -s ../../vda4 /dev/disk/by-label/root")
1448       installer.succeed(
1449           "mount /dev/disk/by-label/root /mnt",
1450           "mkdir /mnt/boot",
1451           f"mount /dev/disk/by-uuid/{uuid} /mnt/boot",
1452       )
1453     '';
1454     grubIdentifier = "provided";
1455   };
1457   # Simple btrfs grub testing
1458   btrfsSimple = makeInstallerTest "btrfsSimple" {
1459     createPartitions = ''
1460       installer.succeed(
1461           "sgdisk -Z /dev/vda",
1462           "sgdisk -n 1:0:+1M -n 2:0:+1G -N 3 -t 1:ef02 -t 2:8200 -t 3:8300 -c 3:root /dev/vda",
1463           "mkswap /dev/vda2 -L swap",
1464           "swapon -L swap",
1465           "mkfs.btrfs -L root /dev/vda3",
1466           "mount LABEL=root /mnt",
1467       )
1468     '';
1469   };
1471   # Test to see if we can detect /boot and /nix on subvolumes
1472   btrfsSubvols = makeInstallerTest "btrfsSubvols" {
1473     createPartitions = ''
1474       installer.succeed(
1475           "sgdisk -Z /dev/vda",
1476           "sgdisk -n 1:0:+1M -n 2:0:+1G -N 3 -t 1:ef02 -t 2:8200 -t 3:8300 -c 3:root /dev/vda",
1477           "mkswap /dev/vda2 -L swap",
1478           "swapon -L swap",
1479           "mkfs.btrfs -L root /dev/vda3",
1480           "btrfs device scan",
1481           "mount LABEL=root /mnt",
1482           "btrfs subvol create /mnt/boot",
1483           "btrfs subvol create /mnt/nixos",
1484           "btrfs subvol create /mnt/nixos/default",
1485           "umount /mnt",
1486           "mount -o defaults,subvol=nixos/default LABEL=root /mnt",
1487           "mkdir /mnt/boot",
1488           "mount -o defaults,subvol=boot LABEL=root /mnt/boot",
1489       )
1490     '';
1491   };
1493   # Test to see if we can detect default and aux subvolumes correctly
1494   btrfsSubvolDefault = makeInstallerTest "btrfsSubvolDefault" {
1495     createPartitions = ''
1496       installer.succeed(
1497           "sgdisk -Z /dev/vda",
1498           "sgdisk -n 1:0:+1M -n 2:0:+1G -N 3 -t 1:ef02 -t 2:8200 -t 3:8300 -c 3:root /dev/vda",
1499           "mkswap /dev/vda2 -L swap",
1500           "swapon -L swap",
1501           "mkfs.btrfs -L root /dev/vda3",
1502           "btrfs device scan",
1503           "mount LABEL=root /mnt",
1504           "btrfs subvol create /mnt/badpath",
1505           "btrfs subvol create /mnt/badpath/boot",
1506           "btrfs subvol create /mnt/nixos",
1507           "btrfs subvol set-default "
1508           + "$(btrfs subvol list /mnt | grep 'nixos' | awk '{print $2}') /mnt",
1509           "umount /mnt",
1510           "mount -o defaults LABEL=root /mnt",
1511           "mkdir -p /mnt/badpath/boot",  # Help ensure the detection mechanism
1512           # is actually looking up subvolumes
1513           "mkdir /mnt/boot",
1514           "mount -o defaults,subvol=badpath/boot LABEL=root /mnt/boot",
1515       )
1516     '';
1517   };
1519   # Test to see if we can deal with subvols that need to be escaped in fstab
1520   btrfsSubvolEscape = makeInstallerTest "btrfsSubvolEscape" {
1521     createPartitions = ''
1522       installer.succeed(
1523           "sgdisk -Z /dev/vda",
1524           "sgdisk -n 1:0:+1M -n 2:0:+1G -N 3 -t 1:ef02 -t 2:8200 -t 3:8300 -c 3:root /dev/vda",
1525           "mkswap /dev/vda2 -L swap",
1526           "swapon -L swap",
1527           "mkfs.btrfs -L root /dev/vda3",
1528           "btrfs device scan",
1529           "mount LABEL=root /mnt",
1530           "btrfs subvol create '/mnt/nixos in space'",
1531           "btrfs subvol create /mnt/boot",
1532           "umount /mnt",
1533           "mount -o 'defaults,subvol=nixos in space' LABEL=root /mnt",
1534           "mkdir /mnt/boot",
1535           "mount -o defaults,subvol=boot LABEL=root /mnt/boot",
1536       )
1537     '';
1538   };
1539 } // {
1540   clevisBcachefs = mkClevisBcachefsTest { };
1541   clevisBcachefsFallback = mkClevisBcachefsTest { fallback = true; };
1542   clevisLuks = mkClevisLuksTest { };
1543   clevisLuksFallback = mkClevisLuksTest { fallback = true; };
1544   clevisZfs = mkClevisZfsTest { };
1545   clevisZfsFallback = mkClevisZfsTest { fallback = true; };
1546   clevisZfsParentDataset = mkClevisZfsTest { parentDataset = true; };
1547   clevisZfsParentDatasetFallback = mkClevisZfsTest { parentDataset = true; fallback = true; };
1548 } // optionalAttrs systemdStage1 {
1549   stratisRoot = makeInstallerTest "stratisRoot" {
1550     createPartitions = ''
1551       installer.succeed(
1552         "sgdisk --zap-all /dev/vda",
1553         "sgdisk --new=1:0:+100M --typecode=0:ef00 /dev/vda", # /boot
1554         "sgdisk --new=2:0:+1G --typecode=0:8200 /dev/vda", # swap
1555         "sgdisk --new=3:0:+5G --typecode=0:8300 /dev/vda", # /
1556         "udevadm settle",
1558         "mkfs.vfat /dev/vda1",
1559         "mkswap /dev/vda2 -L swap",
1560         "swapon -L swap",
1561         "stratis pool create my-pool /dev/vda3",
1562         "stratis filesystem create my-pool nixos",
1563         "udevadm settle",
1565         "mount /dev/stratis/my-pool/nixos /mnt",
1566         "mkdir -p /mnt/boot",
1567         "mount /dev/vda1 /mnt/boot"
1568       )
1569     '';
1570     bootLoader = "systemd-boot";
1571     extraInstallerConfig = { modulesPath, ...}: {
1572       config = {
1573         services.stratis.enable = true;
1574         environment.systemPackages = [
1575           pkgs.stratis-cli
1576           pkgs.thin-provisioning-tools
1577           pkgs.lvm2.bin
1578           pkgs.stratisd.initrd
1579         ];
1580       };
1581     };
1582   };
1584   gptAutoRoot = let
1585     rootPartType = {
1586       ia32 = "44479540-F297-41B2-9AF7-D131D5F0458A";
1587       x64 = "4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709";
1588       arm = "69DAD710-2CE4-4E3C-B16C-21A1D49ABED3";
1589       aa64 = "B921B045-1DF0-41C3-AF44-4C6F280D3FAE";
1590     }.${pkgs.stdenv.hostPlatform.efiArch};
1591   in makeInstallerTest "gptAutoRoot" {
1592     disableFileSystems = true;
1593     createPartitions = ''
1594       installer.succeed(
1595         "sgdisk --zap-all /dev/vda",
1596         "sgdisk --new=1:0:+100M --typecode=0:ef00 /dev/vda", # /boot
1597         "sgdisk --new=2:0:+1G --typecode=0:8200 /dev/vda", # swap
1598         "sgdisk --new=3:0:+5G --typecode=0:${rootPartType} /dev/vda", # /
1599         "udevadm settle",
1601         "mkfs.vfat /dev/vda1",
1602         "mkswap /dev/vda2 -L swap",
1603         "swapon -L swap",
1604         "mkfs.ext4 -L root /dev/vda3",
1605         "udevadm settle",
1607         "mount /dev/vda3 /mnt",
1608         "mkdir -p /mnt/boot",
1609         "mount /dev/vda1 /mnt/boot"
1610       )
1611     '';
1612     bootLoader = "systemd-boot";
1613     extraConfig = ''
1614       boot.initrd.systemd.root = "gpt-auto";
1615       boot.initrd.supportedFilesystems = ["ext4"];
1616     '';
1617   };