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