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 = mkIf (!isEfi) [
655 "x86_64-linux"
656 "x86_64-darwin"
657 "i686-linux"
658 ];
659 };
660 nodes =
661 let
662 commonConfig = {
663 # builds stuff in the VM, needs more juice
664 virtualisation.diskSize = 8 * 1024;
665 virtualisation.cores = 8;
666 virtualisation.memorySize = 2048;
667
668 # both installer and target need to use the same drive
669 virtualisation.diskImage = "./target.qcow2";
670
671 # and the same TPM options
672 virtualisation.qemu.options = mkIf (clevisTest) [
673 "-chardev socket,id=chrtpm,path=$NIX_BUILD_TOP/swtpm-sock"
674 "-tpmdev emulator,id=tpm0,chardev=chrtpm"
675 "-device tpm-tis,tpmdev=tpm0"
676 ];
677 };
678 in
679 {
680 # The configuration of the system used to run "nixos-install".
681 installer =
682 { config, pkgs, ... }:
683 {
684 imports = [
685 commonConfig
686 ../modules/profiles/installation-device.nix
687 ../modules/profiles/base.nix
688 extraInstallerConfig
689 ./common/auto-format-root-device.nix
690 ];
691
692 # In systemdStage1, also automatically format the device backing the
693 # root filesystem.
694 virtualisation.fileSystems."/".autoFormat = systemdStage1;
695
696 boot.initrd.systemd.enable = systemdStage1;
697
698 # Use a small /dev/vdb as the root disk for the
699 # installer. This ensures the target disk (/dev/vda) is
700 # the same during and after installation.
701 virtualisation.emptyDiskImages = [ 512 ];
702 virtualisation.rootDevice = "/dev/vdb";
703
704 nix.package = selectNixPackage pkgs;
705 hardware.enableAllFirmware = mkForce false;
706
707 # The test cannot access the network, so any packages we
708 # need must be included in the VM.
709 system.extraDependencies =
710 with pkgs;
711 [
712 # TODO: Remove this when we can install systems
713 # without `stdenv`.
714 stdenv
715
716 bintools
717 brotli
718 brotli.dev
719 brotli.lib
720 desktop-file-utils
721 docbook5
722 docbook_xsl_ns
723 kbd.dev
724 kmod.dev
725 libarchive.dev
726 libxml2.bin
727 libxslt.bin
728 nixos-artwork.wallpapers.simple-dark-gray-bottom
729 (nixos-rebuild-ng.override {
730 withNgSuffix = false;
731 withReexec = true;
732 })
733 ntp
734 perlPackages.ConfigIniFiles
735 perlPackages.FileSlurp
736 perlPackages.JSON
737 perlPackages.ListCompare
738 perlPackages.XMLLibXML
739 # make-options-doc/default.nix
740 (python3.withPackages (p: [ p.mistune ]))
741 shared-mime-info
742 sudo
743 switch-to-configuration-ng
744 texinfo
745 unionfs-fuse
746 xorg.lndir
747 shellcheck-minimal
748
749 # Only the out output is included here, which is what is
750 # required to build the NixOS udev rules
751 # See the comment in services/hardware/udev.nix
752 systemdMinimal.out
753
754 # add curl so that rather than seeing the test attempt to download
755 # curl's tarball, we see what it's trying to download
756 curl
757 ]
758 ++ optionals (bootLoader == "grub") (
759 let
760 zfsSupport = extraInstallerConfig.boot.supportedFilesystems.zfs or false;
761 in
762 [
763 (pkgs.grub2.override { inherit zfsSupport; })
764 (pkgs.grub2_efi.override { inherit zfsSupport; })
765 pkgs.nixos-artwork.wallpapers.simple-dark-gray-bootloader
766 pkgs.perlPackages.FileCopyRecursive
767 pkgs.perlPackages.XMLSAX
768 pkgs.perlPackages.XMLSAXBase
769 ]
770 )
771 ++ optionals (bootLoader == "systemd-boot") [
772 pkgs.zstd.bin
773 pkgs.mypy
774 config.boot.bootspec.package
775 ]
776 ++ optionals clevisTest [ pkgs.klibc ]
777 ++ optional systemdStage1 config.system.nixos-init.package;
778
779 nix.settings = {
780 substituters = mkForce [ ];
781 hashed-mirrors = null;
782 connect-timeout = 1;
783 };
784 };
785
786 target = {
787 imports = [ commonConfig ];
788 virtualisation.useBootLoader = true;
789 virtualisation.useEFIBoot = isEfi;
790 virtualisation.useDefaultFilesystems = false;
791 virtualisation.efi.keepVariables = false;
792
793 virtualisation.fileSystems."/" = {
794 device = "/dev/disk/by-label/this-is-not-real-and-will-never-be-used";
795 fsType = "ext4";
796 };
797 };
798 }
799 // optionalAttrs clevisTest {
800 tang = {
801 services.tang = {
802 enable = true;
803 listenStream = [ "80" ];
804 ipAddressAllow = [ "192.168.1.0/24" ];
805 };
806 networking.firewall.allowedTCPPorts = [ 80 ];
807 };
808 };
809
810 testScript = testScriptFun {
811 inherit
812 bootLoader
813 createPartitions
814 postInstallCommands
815 postBootCommands
816 grubDevice
817 grubIdentifier
818 grubUseEfi
819 extraConfig
820 testSpecialisationConfig
821 testFlakeSwitch
822 testByAttrSwitch
823 clevisTest
824 clevisFallbackTest
825 disableFileSystems
826 ;
827 };
828 };
829
830 makeLuksRootTest =
831 name: luksFormatOpts:
832 makeInstallerTest name {
833 createPartitions = ''
834 installer.succeed(
835 "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
836 + " mkpart primary ext2 1M 100MB" # /boot
837 + " mkpart primary linux-swap 100M 1024M"
838 + " mkpart primary 1024M -1s", # LUKS
839 "udevadm settle",
840 "mkswap /dev/vda2 -L swap",
841 "swapon -L swap",
842 "modprobe dm_mod dm_crypt",
843 "echo -n supersecret | cryptsetup luksFormat ${luksFormatOpts} -q /dev/vda3 -",
844 "echo -n supersecret | cryptsetup luksOpen --key-file - /dev/vda3 cryptroot",
845 "mkfs.ext3 -L nixos /dev/mapper/cryptroot",
846 "mount LABEL=nixos /mnt",
847 "mkfs.ext3 -L boot /dev/vda1",
848 "mkdir -p /mnt/boot",
849 "mount LABEL=boot /mnt/boot",
850 )
851 '';
852 extraConfig = ''
853 boot.kernelParams = lib.mkAfter [ "console=tty0" ];
854 '';
855 enableOCR = true;
856 postBootCommands = ''
857 target.wait_for_text("[Pp]assphrase for")
858 target.send_chars("supersecret\n")
859 '';
860 };
861
862 # The (almost) simplest partitioning scheme: a swap partition and
863 # one big filesystem partition.
864 simple-test-config = {
865 createPartitions = ''
866 installer.succeed(
867 "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
868 + " mkpart primary linux-swap 1M 1024M"
869 + " mkpart primary ext2 1024M -1s",
870 "udevadm settle",
871 "mkswap /dev/vda1 -L swap",
872 "swapon -L swap",
873 "mkfs.ext3 -L nixos /dev/vda2",
874 "mount LABEL=nixos /mnt",
875 )
876 '';
877 };
878
879 simple-test-config-flake = simple-test-config // {
880 testFlakeSwitch = true;
881 };
882
883 simple-test-config-by-attr = simple-test-config // {
884 testByAttrSwitch = true;
885 };
886
887 simple-test-config-from-by-attr-to-flake = simple-test-config // {
888 testByAttrSwitch = true;
889 testFlakeSwitch = true;
890 };
891
892 simple-uefi-grub-config = {
893 createPartitions = ''
894 installer.succeed(
895 "flock /dev/vda parted --script /dev/vda -- mklabel gpt"
896 + " mkpart ESP fat32 1M 100MiB" # /boot
897 + " set 1 boot on"
898 + " mkpart primary linux-swap 100MiB 1024MiB"
899 + " mkpart primary ext2 1024MiB -1MiB", # /
900 "udevadm settle",
901 "mkswap /dev/vda2 -L swap",
902 "swapon -L swap",
903 "mkfs.ext3 -L nixos /dev/vda3",
904 "mount LABEL=nixos /mnt",
905 "mkfs.vfat -n BOOT /dev/vda1",
906 "mkdir -p /mnt/boot",
907 "mount LABEL=BOOT /mnt/boot",
908 )
909 '';
910 bootLoader = "grub";
911 grubUseEfi = true;
912 };
913
914 specialisation-test-extraconfig = {
915 extraConfig = ''
916 environment.systemPackages = [ pkgs.grub2 ];
917 boot.loader.grub.configurationName = "Home";
918 specialisation.work.configuration = {
919 boot.loader.grub.configurationName = lib.mkForce "Work";
920
921 environment.etc = {
922 "gitconfig".text = "
923 [core]
924 gitproxy = none for work.com
925 ";
926 };
927 };
928 '';
929 testSpecialisationConfig = true;
930 };
931 # disable zfs so we can support latest kernel if needed
932 no-zfs-module = {
933 nixpkgs.overlays = [
934 (final: super: {
935 zfs = super.zfs.overrideAttrs (_: {
936 meta.platforms = [ ];
937 });
938 })
939 ];
940 };
941
942 mkClevisBcachefsTest =
943 {
944 fallback ? false,
945 }:
946 makeInstallerTest "clevis-bcachefs${optionalString fallback "-fallback"}" {
947 clevisTest = true;
948 clevisFallbackTest = fallback;
949 enableOCR = fallback;
950 extraInstallerConfig = {
951 imports = [ no-zfs-module ];
952 boot.supportedFilesystems = [ "bcachefs" ];
953 environment.systemPackages = with pkgs; [
954 keyutils
955 clevis
956 ];
957 };
958 createPartitions = ''
959 installer.succeed(
960 "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
961 + " mkpart primary ext2 1M 100MB"
962 + " mkpart primary linux-swap 100M 1024M"
963 + " mkpart primary 1024M -1s",
964 "udevadm settle",
965 "mkswap /dev/vda2 -L swap",
966 "swapon -L swap",
967 "keyctl link @u @s",
968 "echo -n password | mkfs.bcachefs -L root --encrypted /dev/vda3",
969 "echo -n password | bcachefs unlock /dev/vda3",
970 "echo -n password | mount -t bcachefs /dev/vda3 /mnt",
971 "mkfs.ext3 -L boot /dev/vda1",
972 "mkdir -p /mnt/boot",
973 "mount LABEL=boot /mnt/boot",
974 "udevadm settle")
975 '';
976 extraConfig = ''
977 boot.initrd.clevis.devices."/dev/vda3".secretFile = "/etc/nixos/clevis-secret.jwe";
978
979 # We override what nixos-generate-config has generated because we do
980 # not know the UUID in advance.
981 fileSystems."/" = lib.mkForce { device = "/dev/vda3"; fsType = "bcachefs"; };
982 '';
983 postBootCommands = optionalString fallback ''
984 target.wait_for_text("enter passphrase for")
985 target.send_chars("password\n")
986 '';
987 };
988
989 mkClevisLuksTest =
990 {
991 fallback ? false,
992 }:
993 makeInstallerTest "clevis-luks${optionalString fallback "-fallback"}" {
994 clevisTest = true;
995 clevisFallbackTest = fallback;
996 enableOCR = fallback;
997 extraInstallerConfig = {
998 environment.systemPackages = with pkgs; [ clevis ];
999 };
1000 createPartitions = ''
1001 installer.succeed(
1002 "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
1003 + " mkpart primary ext2 1M 100MB"
1004 + " mkpart primary linux-swap 100M 1024M"
1005 + " mkpart primary 1024M -1s",
1006 "udevadm settle",
1007 "mkswap /dev/vda2 -L swap",
1008 "swapon -L swap",
1009 "modprobe dm_mod dm_crypt",
1010 "echo -n password | cryptsetup luksFormat -q /dev/vda3 -",
1011 "echo -n password | cryptsetup luksOpen --key-file - /dev/vda3 crypt-root",
1012 "mkfs.ext3 -L nixos /dev/mapper/crypt-root",
1013 "mount LABEL=nixos /mnt",
1014 "mkfs.ext3 -L boot /dev/vda1",
1015 "mkdir -p /mnt/boot",
1016 "mount LABEL=boot /mnt/boot",
1017 "udevadm settle")
1018 '';
1019 extraConfig = ''
1020 boot.initrd.clevis.devices."crypt-root".secretFile = "/etc/nixos/clevis-secret.jwe";
1021 '';
1022 postBootCommands = optionalString fallback ''
1023 ${
1024 if systemdStage1 then
1025 ''
1026 target.wait_for_text("Please enter")
1027 ''
1028 else
1029 ''
1030 target.wait_for_text("Passphrase for")
1031 ''
1032 }
1033 target.send_chars("password\n")
1034 '';
1035 };
1036
1037 mkClevisZfsTest =
1038 {
1039 fallback ? false,
1040 parentDataset ? false,
1041 }:
1042 makeInstallerTest
1043 "clevis-zfs${optionalString parentDataset "-parent-dataset"}${optionalString fallback "-fallback"}"
1044 {
1045 clevisTest = true;
1046 clevisFallbackTest = fallback;
1047 enableOCR = fallback;
1048 extraInstallerConfig = {
1049 boot.supportedFilesystems = [ "zfs" ];
1050 environment.systemPackages = with pkgs; [ clevis ];
1051 };
1052 createPartitions = ''
1053 installer.succeed(
1054 "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
1055 + " mkpart primary ext2 1M 100MB"
1056 + " mkpart primary linux-swap 100M 1024M"
1057 + " mkpart primary 1024M -1s",
1058 "udevadm settle",
1059 "mkswap /dev/vda2 -L swap",
1060 "swapon -L swap",
1061 ''
1062 + optionalString (!parentDataset) ''
1063 "zpool create -O mountpoint=legacy rpool /dev/vda3",
1064 "echo -n password | zfs create"
1065 + " -o encryption=aes-256-gcm -o keyformat=passphrase rpool/root",
1066 ''
1067 + optionalString (parentDataset) ''
1068 "echo -n password | zpool create -O mountpoint=none -O encryption=on -O keyformat=passphrase rpool /dev/vda3",
1069 "zfs create -o mountpoint=legacy rpool/root",
1070 ''
1071 + ''
1072 "mount -t zfs rpool/root /mnt",
1073 "mkfs.ext3 -L boot /dev/vda1",
1074 "mkdir -p /mnt/boot",
1075 "mount LABEL=boot /mnt/boot",
1076 "udevadm settle")
1077 '';
1078 extraConfig =
1079 optionalString (!parentDataset) ''
1080 boot.initrd.clevis.devices."rpool/root".secretFile = "/etc/nixos/clevis-secret.jwe";
1081 ''
1082 + optionalString (parentDataset) ''
1083 boot.initrd.clevis.devices."rpool".secretFile = "/etc/nixos/clevis-secret.jwe";
1084 ''
1085 + ''
1086 boot.zfs.requestEncryptionCredentials = true;
1087
1088
1089 # Using by-uuid overrides the default of by-id, and is unique
1090 # to the qemu disks, as they don't produce by-id paths for
1091 # some reason.
1092 boot.zfs.devNodes = "/dev/disk/by-uuid/";
1093 networking.hostId = "00000000";
1094 '';
1095 postBootCommands = optionalString fallback ''
1096 ${
1097 if systemdStage1 then
1098 ''
1099 target.wait_for_text("Enter key for rpool${optionalString (!parentDataset) "/root"}")
1100 ''
1101 else
1102 ''
1103 target.wait_for_text("Key load error")
1104 ''
1105 }
1106 target.send_chars("password\n")
1107 '';
1108 };
1109
1110in
1111{
1112
1113 # !!! `parted mkpart' seems to silently create overlapping partitions.
1114
1115 # The (almost) simplest partitioning scheme: a swap partition and
1116 # one big filesystem partition.
1117 simple = makeInstallerTest "simple" (
1118 simple-test-config
1119 // {
1120 passthru.override = args: makeInstallerTest "simple" (simple-test-config // args);
1121 }
1122 );
1123
1124 switchToFlake = makeInstallerTest "switch-to-flake" simple-test-config-flake;
1125
1126 switchToByAttr = makeInstallerTest "switch-to-by-attr" simple-test-config-by-attr;
1127
1128 switchFromByAttrToFlake = makeInstallerTest "switch-from-by-attr-to-flake" simple-test-config-from-by-attr-to-flake;
1129
1130 # Test cloned configurations with the simple grub configuration
1131 simpleSpecialised = makeInstallerTest "simpleSpecialised" (
1132 simple-test-config // specialisation-test-extraconfig
1133 );
1134
1135 # Simple GPT/UEFI configuration using systemd-boot with 3 partitions: ESP, swap & root filesystem
1136 simpleUefiSystemdBoot = makeInstallerTest "simpleUefiSystemdBoot" {
1137 createPartitions = ''
1138 installer.succeed(
1139 "flock /dev/vda parted --script /dev/vda -- mklabel gpt"
1140 + " mkpart ESP fat32 1M 100MiB" # /boot
1141 + " set 1 boot on"
1142 + " mkpart primary linux-swap 100MiB 1024MiB"
1143 + " mkpart primary ext2 1024MiB -1MiB", # /
1144 "udevadm settle",
1145 "mkswap /dev/vda2 -L swap",
1146 "swapon -L swap",
1147 "mkfs.ext3 -L nixos /dev/vda3",
1148 "mount LABEL=nixos /mnt",
1149 "mkfs.vfat -n BOOT /dev/vda1",
1150 "mkdir -p /mnt/boot",
1151 "mount LABEL=BOOT /mnt/boot",
1152 )
1153 '';
1154 bootLoader = "systemd-boot";
1155 };
1156
1157 simpleUefiGrub = makeInstallerTest "simpleUefiGrub" simple-uefi-grub-config;
1158
1159 # Test cloned configurations with the uefi grub configuration
1160 simpleUefiGrubSpecialisation = makeInstallerTest "simpleUefiGrubSpecialisation" (
1161 simple-uefi-grub-config // specialisation-test-extraconfig
1162 );
1163
1164 # Same as the previous, but now with a separate /boot partition.
1165 separateBoot = makeInstallerTest "separateBoot" {
1166 createPartitions = ''
1167 installer.succeed(
1168 "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
1169 + " mkpart primary ext2 1M 100MB" # /boot
1170 + " mkpart primary linux-swap 100MB 1024M"
1171 + " mkpart primary ext2 1024M -1s", # /
1172 "udevadm settle",
1173 "mkswap /dev/vda2 -L swap",
1174 "swapon -L swap",
1175 "mkfs.ext3 -L nixos /dev/vda3",
1176 "mount LABEL=nixos /mnt",
1177 "mkfs.ext3 -L boot /dev/vda1",
1178 "mkdir -p /mnt/boot",
1179 "mount LABEL=boot /mnt/boot",
1180 )
1181 '';
1182 };
1183
1184 # Same as the previous, but with fat32 /boot.
1185 separateBootFat = makeInstallerTest "separateBootFat" {
1186 createPartitions = ''
1187 installer.succeed(
1188 "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
1189 + " mkpart primary ext2 1M 100MB" # /boot
1190 + " mkpart primary linux-swap 100MB 1024M"
1191 + " mkpart primary ext2 1024M -1s", # /
1192 "udevadm settle",
1193 "mkswap /dev/vda2 -L swap",
1194 "swapon -L swap",
1195 "mkfs.ext3 -L nixos /dev/vda3",
1196 "mount LABEL=nixos /mnt",
1197 "mkfs.vfat -n BOOT /dev/vda1",
1198 "mkdir -p /mnt/boot",
1199 "mount LABEL=BOOT /mnt/boot",
1200 )
1201 '';
1202 };
1203
1204 # Same as the previous, but with ZFS /boot.
1205 separateBootZfs = makeInstallerTest "separateBootZfs" {
1206 extraInstallerConfig = {
1207 boot.supportedFilesystems = [ "zfs" ];
1208 };
1209
1210 extraConfig = ''
1211 # Using by-uuid overrides the default of by-id, and is unique
1212 # to the qemu disks, as they don't produce by-id paths for
1213 # some reason.
1214 boot.zfs.devNodes = "/dev/disk/by-uuid/";
1215 networking.hostId = "00000000";
1216 '';
1217
1218 createPartitions = ''
1219 installer.succeed(
1220 "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
1221 + " mkpart primary ext2 1M 256MB" # /boot
1222 + " mkpart primary linux-swap 256MB 1280M"
1223 + " mkpart primary ext2 1280M -1s", # /
1224 "udevadm settle",
1225
1226 "mkswap /dev/vda2 -L swap",
1227 "swapon -L swap",
1228
1229 "mkfs.ext4 -L nixos /dev/vda3",
1230 "mount LABEL=nixos /mnt",
1231
1232 # Use as many ZFS features as possible to verify that GRUB can handle them
1233 "zpool create"
1234 " -o compatibility=grub2"
1235 " -O utf8only=on"
1236 " -O normalization=formD"
1237 " -O compression=lz4" # Activate the lz4_compress feature
1238 " -O xattr=sa"
1239 " -O acltype=posixacl"
1240 " bpool /dev/vda1",
1241 "zfs create"
1242 " -o recordsize=1M" # Prepare activating the large_blocks feature
1243 " -o mountpoint=legacy"
1244 " -o relatime=on"
1245 " -o quota=1G"
1246 " -o filesystem_limit=100" # Activate the filesystem_limits features
1247 " bpool/boot",
1248
1249 # Snapshotting the top-level dataset would trigger a bug in GRUB2: https://github.com/openzfs/zfs/issues/13873
1250 "zfs snapshot bpool/boot@snap-1", # Prepare activating the livelist and bookmarks features
1251 "zfs clone bpool/boot@snap-1 bpool/test", # Activate the livelist feature
1252 "zfs bookmark bpool/boot@snap-1 bpool/boot#bookmark", # Activate the bookmarks feature
1253 "zpool checkpoint bpool", # Activate the zpool_checkpoint feature
1254 "mkdir -p /mnt/boot",
1255 "mount -t zfs bpool/boot /mnt/boot",
1256 "touch /mnt/boot/empty", # Activate zilsaxattr feature
1257 "dd if=/dev/urandom of=/mnt/boot/test bs=1M count=1", # Activate the large_blocks feature
1258
1259 # Print out all enabled and active ZFS features (and some other stuff)
1260 "sync /mnt/boot",
1261 "zpool get all bpool >&2",
1262
1263 # Abort early if GRUB2 doesn't like the disks
1264 "grub-probe --target=device /mnt/boot >&2",
1265 )
1266 '';
1267
1268 # umount & export bpool before shutdown
1269 # this is a fix for "cannot import 'bpool': pool was previously in use from another system."
1270 postInstallCommands = ''
1271 installer.succeed("umount /mnt/boot")
1272 installer.succeed("zpool export bpool")
1273 '';
1274 };
1275
1276 # zfs on / with swap
1277 zfsroot = makeInstallerTest "zfs-root" {
1278 extraInstallerConfig = {
1279 boot.supportedFilesystems = [ "zfs" ];
1280 };
1281
1282 extraConfig = ''
1283 boot.supportedFilesystems = [ "zfs" ];
1284
1285 # Using by-uuid overrides the default of by-id, and is unique
1286 # to the qemu disks, as they don't produce by-id paths for
1287 # some reason.
1288 boot.zfs.devNodes = "/dev/disk/by-uuid/";
1289 networking.hostId = "00000000";
1290 '';
1291
1292 createPartitions = ''
1293 installer.succeed(
1294 "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
1295 + " mkpart primary 1M 100MB" # /boot
1296 + " mkpart primary linux-swap 100M 1024M"
1297 + " mkpart primary 1024M -1s", # rpool
1298 "udevadm settle",
1299 "mkswap /dev/vda2 -L swap",
1300 "swapon -L swap",
1301 "zpool create rpool /dev/vda3",
1302 "zfs create -o mountpoint=legacy rpool/root",
1303 "mount -t zfs rpool/root /mnt",
1304 "zfs create -o mountpoint=legacy rpool/root/usr",
1305 "mkdir /mnt/usr",
1306 "mount -t zfs rpool/root/usr /mnt/usr",
1307 "mkfs.vfat -n BOOT /dev/vda1",
1308 "mkdir /mnt/boot",
1309 "mount LABEL=BOOT /mnt/boot",
1310 "udevadm settle",
1311 )
1312 '';
1313 };
1314
1315 # Create two physical LVM partitions combined into one volume group
1316 # that contains the logical swap and root partitions.
1317 lvm = makeInstallerTest "lvm" {
1318 createPartitions = ''
1319 installer.succeed(
1320 "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
1321 + " mkpart primary 1M 2048M" # PV1
1322 + " set 1 lvm on"
1323 + " mkpart primary 2048M -1s" # PV2
1324 + " set 2 lvm on",
1325 "udevadm settle",
1326 "pvcreate /dev/vda1 /dev/vda2",
1327 "vgcreate MyVolGroup /dev/vda1 /dev/vda2",
1328 "lvcreate --size 1G --name swap MyVolGroup",
1329 "lvcreate --size 6G --name nixos MyVolGroup",
1330 "mkswap -f /dev/MyVolGroup/swap -L swap",
1331 "swapon -L swap",
1332 "mkfs.xfs -L nixos /dev/MyVolGroup/nixos",
1333 "mount LABEL=nixos /mnt",
1334 )
1335 '';
1336 extraConfig = optionalString systemdStage1 ''
1337 boot.initrd.services.lvm.enable = true;
1338 '';
1339 };
1340
1341 # Boot off an encrypted root partition with the default LUKS header format
1342 luksroot = makeLuksRootTest "luksroot-format1" "";
1343
1344 # Boot off an encrypted root partition with LUKS1 format
1345 luksroot-format1 = makeLuksRootTest "luksroot-format1" "--type=LUKS1";
1346
1347 # Boot off an encrypted root partition with LUKS2 format
1348 luksroot-format2 = makeLuksRootTest "luksroot-format2" "--type=LUKS2";
1349
1350 # Test whether opening encrypted filesystem with keyfile
1351 # Checks for regression of missing cryptsetup, when no luks device without
1352 # keyfile is configured
1353 encryptedFSWithKeyfile = makeInstallerTest "encryptedFSWithKeyfile" {
1354 createPartitions = ''
1355 installer.succeed(
1356 "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
1357 + " mkpart primary ext2 1M 100MB" # /boot
1358 + " mkpart primary linux-swap 100M 1024M"
1359 + " mkpart primary 1024M 1280M" # LUKS with keyfile
1360 + " mkpart primary 1280M -1s",
1361 "udevadm settle",
1362 "mkswap /dev/vda2 -L swap",
1363 "swapon -L swap",
1364 "mkfs.ext3 -L nixos /dev/vda4",
1365 "mount LABEL=nixos /mnt",
1366 "mkfs.ext3 -L boot /dev/vda1",
1367 "mkdir -p /mnt/boot",
1368 "mount LABEL=boot /mnt/boot",
1369 "modprobe dm_mod dm_crypt",
1370 "echo -n supersecret > /mnt/keyfile",
1371 "cryptsetup luksFormat -q /dev/vda3 --key-file /mnt/keyfile",
1372 "cryptsetup luksOpen --key-file /mnt/keyfile /dev/vda3 crypt",
1373 "mkfs.ext3 -L test /dev/mapper/crypt",
1374 "cryptsetup luksClose crypt",
1375 "mkdir -p /mnt/test",
1376 )
1377 '';
1378 extraConfig = ''
1379 fileSystems."/test" = {
1380 device = "/dev/disk/by-label/test";
1381 fsType = "ext3";
1382 encrypted.enable = true;
1383 encrypted.blkDev = "/dev/vda3";
1384 encrypted.label = "crypt";
1385 encrypted.keyFile = "/${if systemdStage1 then "sysroot" else "mnt-root"}/keyfile";
1386 };
1387 '';
1388 };
1389
1390 # Full disk encryption (root, kernel and initrd encrypted) using GRUB, GPT/UEFI,
1391 # LVM-on-LUKS and a keyfile in initrd.secrets to enter the passphrase once
1392 fullDiskEncryption = makeInstallerTest "fullDiskEncryption" {
1393 createPartitions = ''
1394 installer.succeed(
1395 "flock /dev/vda parted --script /dev/vda -- mklabel gpt"
1396 + " mkpart ESP fat32 1M 100MiB" # /boot/efi
1397 + " set 1 boot on"
1398 + " mkpart primary ext2 1024MiB -1MiB", # LUKS
1399 "udevadm settle",
1400 "modprobe dm_mod dm_crypt",
1401 "dd if=/dev/random of=luks.key bs=256 count=1",
1402 "echo -n supersecret | cryptsetup luksFormat -q --pbkdf-force-iterations 1000 --type luks1 /dev/vda2 -",
1403 "echo -n supersecret | cryptsetup luksAddKey -q --pbkdf-force-iterations 1000 --key-file - /dev/vda2 luks.key",
1404 "echo -n supersecret | cryptsetup luksOpen --key-file - /dev/vda2 crypt",
1405 "pvcreate /dev/mapper/crypt",
1406 "vgcreate crypt /dev/mapper/crypt",
1407 "lvcreate -L 100M -n swap crypt",
1408 "lvcreate -l '100%FREE' -n nixos crypt",
1409 "mkfs.vfat -n efi /dev/vda1",
1410 "mkfs.ext4 -L nixos /dev/crypt/nixos",
1411 "mkswap -L swap /dev/crypt/swap",
1412 "mount LABEL=nixos /mnt",
1413 "mkdir -p /mnt/{etc/nixos,boot/efi}",
1414 "mount LABEL=efi /mnt/boot/efi",
1415 "swapon -L swap",
1416 "mv luks.key /mnt/etc/nixos/"
1417 )
1418 '';
1419 bootLoader = "grub";
1420 grubUseEfi = true;
1421 extraConfig = ''
1422 boot.loader.grub.enableCryptodisk = true;
1423 boot.loader.efi.efiSysMountPoint = "/boot/efi";
1424
1425 boot.initrd.secrets."/luks.key" = "/etc/nixos/luks.key";
1426 boot.initrd.luks.devices.crypt =
1427 { device = "/dev/vda2";
1428 keyFile = "/luks.key";
1429 };
1430 '';
1431 enableOCR = true;
1432 postBootCommands = ''
1433 target.wait_for_text("Enter passphrase for")
1434 target.send_chars("supersecret\n")
1435 '';
1436 };
1437
1438 swraid = makeInstallerTest "swraid" {
1439 createPartitions = ''
1440 installer.succeed(
1441 "flock /dev/vda parted --script /dev/vda --"
1442 + " mklabel msdos"
1443 + " mkpart primary ext2 1M 100MB" # /boot
1444 + " mkpart extended 100M -1s"
1445 + " mkpart logical 102M 3102M" # md0 (root), first device
1446 + " mkpart logical 3103M 6103M" # md0 (root), second device
1447 + " mkpart logical 6104M 6360M" # md1 (swap), first device
1448 + " mkpart logical 6361M 6617M", # md1 (swap), second device
1449 "udevadm settle",
1450 "ls -l /dev/vda* >&2",
1451 "cat /proc/partitions >&2",
1452 "udevadm control --stop-exec-queue",
1453 "mdadm --create --force /dev/md0 --metadata 1.2 --level=raid1 "
1454 + "--raid-devices=2 /dev/vda5 /dev/vda6",
1455 "mdadm --create --force /dev/md1 --metadata 1.2 --level=raid1 "
1456 + "--raid-devices=2 /dev/vda7 /dev/vda8",
1457 "udevadm control --start-exec-queue",
1458 "udevadm settle",
1459 "mkswap -f /dev/md1 -L swap",
1460 "swapon -L swap",
1461 "mkfs.ext3 -L nixos /dev/md0",
1462 "mount LABEL=nixos /mnt",
1463 "mkfs.ext3 -L boot /dev/vda1",
1464 "mkdir /mnt/boot",
1465 "mount LABEL=boot /mnt/boot",
1466 "udevadm settle",
1467 )
1468 '';
1469 postBootCommands = ''
1470 target.fail("dmesg | grep 'immediate safe mode'")
1471 '';
1472 };
1473
1474 bcache = makeInstallerTest "bcache" {
1475 createPartitions = ''
1476 installer.succeed(
1477 "flock /dev/vda parted --script /dev/vda --"
1478 + " mklabel msdos"
1479 + " mkpart primary ext2 1M 100MB" # /boot
1480 + " mkpart primary 100MB 512MB " # swap
1481 + " mkpart primary 512MB 1024MB" # Cache (typically SSD)
1482 + " mkpart primary 1024MB -1s ", # Backing device (typically HDD)
1483 "modprobe bcache",
1484 "udevadm settle",
1485 "make-bcache -B /dev/vda4 -C /dev/vda3",
1486 "udevadm settle",
1487 "mkfs.ext3 -L nixos /dev/bcache0",
1488 "mount LABEL=nixos /mnt",
1489 "mkfs.ext3 -L boot /dev/vda1",
1490 "mkdir /mnt/boot",
1491 "mount LABEL=boot /mnt/boot",
1492 "mkswap -f /dev/vda2 -L swap",
1493 "swapon -L swap",
1494 )
1495 '';
1496 };
1497
1498 bcachefsSimple = makeInstallerTest "bcachefs-simple" {
1499 extraInstallerConfig = {
1500 boot.supportedFilesystems = [ "bcachefs" ];
1501 imports = [ no-zfs-module ];
1502 };
1503
1504 createPartitions = ''
1505 installer.succeed(
1506 "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
1507 + " mkpart primary ext2 1M 100MB" # /boot
1508 + " mkpart primary linux-swap 100M 1024M" # swap
1509 + " mkpart primary 1024M -1s", # /
1510 "udevadm settle",
1511 "mkswap /dev/vda2 -L swap",
1512 "swapon -L swap",
1513 "mkfs.bcachefs -L root /dev/vda3",
1514 "mount -t bcachefs /dev/vda3 /mnt",
1515 "mkfs.ext3 -L boot /dev/vda1",
1516 "mkdir -p /mnt/boot",
1517 "mount /dev/vda1 /mnt/boot",
1518 )
1519 '';
1520 };
1521
1522 bcachefsEncrypted = makeInstallerTest "bcachefs-encrypted" {
1523 extraInstallerConfig = {
1524 boot.supportedFilesystems = [ "bcachefs" ];
1525
1526 # disable zfs so we can support latest kernel if needed
1527 imports = [ no-zfs-module ];
1528
1529 environment.systemPackages = with pkgs; [ keyutils ];
1530 };
1531
1532 extraConfig = ''
1533 boot.kernelParams = lib.mkAfter [ "console=tty0" ];
1534 '';
1535
1536 enableOCR = true;
1537 postBootCommands = ''
1538 # Enter it wrong once
1539 target.wait_for_text("enter passphrase for ")
1540 target.send_chars("wrong\n")
1541 # Then enter it right.
1542 target.wait_for_text("enter passphrase for ")
1543 target.send_chars("password\n")
1544 '';
1545
1546 createPartitions = ''
1547 installer.succeed(
1548 "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
1549 + " mkpart primary ext2 1M 100MB" # /boot
1550 + " mkpart primary linux-swap 100M 1024M" # swap
1551 + " mkpart primary 1024M -1s", # /
1552 "udevadm settle",
1553 "mkswap /dev/vda2 -L swap",
1554 "swapon -L swap",
1555 "echo password | mkfs.bcachefs -L root --encrypted /dev/vda3",
1556 "echo password | bcachefs unlock -k session /dev/vda3",
1557 "echo password | mount -t bcachefs /dev/vda3 /mnt",
1558 "mkfs.ext3 -L boot /dev/vda1",
1559 "mkdir -p /mnt/boot",
1560 "mount /dev/vda1 /mnt/boot",
1561 )
1562 '';
1563 };
1564
1565 bcachefsMulti = makeInstallerTest "bcachefs-multi" {
1566 extraInstallerConfig = {
1567 boot.supportedFilesystems = [ "bcachefs" ];
1568
1569 # disable zfs so we can support latest kernel if needed
1570 imports = [ no-zfs-module ];
1571 };
1572
1573 createPartitions = ''
1574 installer.succeed(
1575 "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
1576 + " mkpart primary ext2 1M 100MB" # /boot
1577 + " mkpart primary linux-swap 100M 1024M" # swap
1578 + " mkpart primary 1024M 4096M" # /
1579 + " mkpart primary 4096M -1s", # /
1580 "udevadm settle",
1581 "mkswap /dev/vda2 -L swap",
1582 "swapon -L swap",
1583 "mkfs.bcachefs -L root --metadata_replicas 2 --foreground_target ssd --promote_target ssd --background_target hdd --label ssd /dev/vda3 --label hdd /dev/vda4",
1584 "mount -t bcachefs /dev/vda3:/dev/vda4 /mnt",
1585 "mkfs.ext3 -L boot /dev/vda1",
1586 "mkdir -p /mnt/boot",
1587 "mount /dev/vda1 /mnt/boot",
1588 )
1589 '';
1590 };
1591
1592 # Test using labels to identify volumes in grub
1593 simpleLabels = makeInstallerTest "simpleLabels" {
1594 createPartitions = ''
1595 installer.succeed(
1596 "sgdisk -Z /dev/vda",
1597 "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",
1598 "mkswap /dev/vda2 -L swap",
1599 "swapon -L swap",
1600 "mkfs.ext4 -L root /dev/vda3",
1601 "mount LABEL=root /mnt",
1602 )
1603 '';
1604 grubIdentifier = "label";
1605 };
1606
1607 # Test using the provided disk name within grub
1608 # TODO: Fix udev so the symlinks are unneeded in /dev/disks
1609 simpleProvided = makeInstallerTest "simpleProvided" {
1610 createPartitions = ''
1611 uuid = "$(blkid -s UUID -o value /dev/vda2)"
1612 installer.succeed(
1613 "sgdisk -Z /dev/vda",
1614 "sgdisk -n 1:0:+1M -n 2:0:+100M -n 3:0:+1G -N 4 -t 1:ef02 -t 2:8300 "
1615 + "-t 3:8200 -t 4:8300 -c 2:boot -c 4:root /dev/vda",
1616 "mkswap /dev/vda3 -L swap",
1617 "swapon -L swap",
1618 "mkfs.ext4 -L boot /dev/vda2",
1619 "mkfs.ext4 -L root /dev/vda4",
1620 )
1621 installer.execute(f"ln -s ../../vda2 /dev/disk/by-uuid/{uuid}")
1622 installer.execute("ln -s ../../vda4 /dev/disk/by-label/root")
1623 installer.succeed(
1624 "mount /dev/disk/by-label/root /mnt",
1625 "mkdir /mnt/boot",
1626 f"mount /dev/disk/by-uuid/{uuid} /mnt/boot",
1627 )
1628 '';
1629 grubIdentifier = "provided";
1630 };
1631
1632 # Simple btrfs grub testing
1633 btrfsSimple = makeInstallerTest "btrfsSimple" {
1634 createPartitions = ''
1635 installer.succeed(
1636 "sgdisk -Z /dev/vda",
1637 "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",
1638 "mkswap /dev/vda2 -L swap",
1639 "swapon -L swap",
1640 "mkfs.btrfs -L root /dev/vda3",
1641 "mount LABEL=root /mnt",
1642 )
1643 '';
1644 };
1645
1646 # Test to see if we can detect /boot and /nix on subvolumes
1647 btrfsSubvols = makeInstallerTest "btrfsSubvols" {
1648 createPartitions = ''
1649 installer.succeed(
1650 "sgdisk -Z /dev/vda",
1651 "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",
1652 "mkswap /dev/vda2 -L swap",
1653 "swapon -L swap",
1654 "mkfs.btrfs -L root /dev/vda3",
1655 "btrfs device scan",
1656 "mount LABEL=root /mnt",
1657 "btrfs subvol create /mnt/boot",
1658 "btrfs subvol create /mnt/nixos",
1659 "btrfs subvol create /mnt/nixos/default",
1660 "umount /mnt",
1661 "mount -o defaults,subvol=nixos/default LABEL=root /mnt",
1662 "mkdir /mnt/boot",
1663 "mount -o defaults,subvol=boot LABEL=root /mnt/boot",
1664 )
1665 '';
1666 };
1667
1668 # Test to see if we can detect default and aux subvolumes correctly
1669 btrfsSubvolDefault = makeInstallerTest "btrfsSubvolDefault" {
1670 createPartitions = ''
1671 installer.succeed(
1672 "sgdisk -Z /dev/vda",
1673 "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",
1674 "mkswap /dev/vda2 -L swap",
1675 "swapon -L swap",
1676 "mkfs.btrfs -L root /dev/vda3",
1677 "btrfs device scan",
1678 "mount LABEL=root /mnt",
1679 "btrfs subvol create /mnt/badpath",
1680 "btrfs subvol create /mnt/badpath/boot",
1681 "btrfs subvol create /mnt/nixos",
1682 "btrfs subvol set-default "
1683 + "$(btrfs subvol list /mnt | grep 'nixos' | awk '{print $2}') /mnt",
1684 "umount /mnt",
1685 "mount -o defaults LABEL=root /mnt",
1686 "mkdir -p /mnt/badpath/boot", # Help ensure the detection mechanism
1687 # is actually looking up subvolumes
1688 "mkdir /mnt/boot",
1689 "mount -o defaults,subvol=badpath/boot LABEL=root /mnt/boot",
1690 )
1691 '';
1692 };
1693
1694 # Test to see if we can deal with subvols that need to be escaped in fstab
1695 btrfsSubvolEscape = makeInstallerTest "btrfsSubvolEscape" {
1696 createPartitions = ''
1697 installer.succeed(
1698 "sgdisk -Z /dev/vda",
1699 "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",
1700 "mkswap /dev/vda2 -L swap",
1701 "swapon -L swap",
1702 "mkfs.btrfs -L root /dev/vda3",
1703 "btrfs device scan",
1704 "mount LABEL=root /mnt",
1705 "btrfs subvol create '/mnt/nixos in space'",
1706 "btrfs subvol create /mnt/boot",
1707 "umount /mnt",
1708 "mount -o 'defaults,subvol=nixos in space' LABEL=root /mnt",
1709 "mkdir /mnt/boot",
1710 "mount -o defaults,subvol=boot LABEL=root /mnt/boot",
1711 )
1712 '';
1713 };
1714}
1715// {
1716 clevisBcachefs = mkClevisBcachefsTest { };
1717 clevisBcachefsFallback = mkClevisBcachefsTest { fallback = true; };
1718 clevisLuks = mkClevisLuksTest { };
1719 clevisLuksFallback = mkClevisLuksTest { fallback = true; };
1720 clevisZfs = mkClevisZfsTest { };
1721 clevisZfsFallback = mkClevisZfsTest { fallback = true; };
1722 clevisZfsParentDataset = mkClevisZfsTest { parentDataset = true; };
1723 clevisZfsParentDatasetFallback = mkClevisZfsTest {
1724 parentDataset = true;
1725 fallback = true;
1726 };
1727}
1728// optionalAttrs systemdStage1 {
1729 stratisRoot = makeInstallerTest "stratisRoot" {
1730 createPartitions = ''
1731 installer.succeed(
1732 "sgdisk --zap-all /dev/vda",
1733 "sgdisk --new=1:0:+100M --typecode=0:ef00 /dev/vda", # /boot
1734 "sgdisk --new=2:0:+1G --typecode=0:8200 /dev/vda", # swap
1735 "sgdisk --new=3:0:+5G --typecode=0:8300 /dev/vda", # /
1736 "udevadm settle",
1737
1738 "mkfs.vfat /dev/vda1",
1739 "mkswap /dev/vda2 -L swap",
1740 "swapon -L swap",
1741 "stratis pool create my-pool /dev/vda3",
1742 "stratis filesystem create my-pool nixos",
1743 "udevadm settle",
1744
1745 "mount /dev/stratis/my-pool/nixos /mnt",
1746 "mkdir -p /mnt/boot",
1747 "mount /dev/vda1 /mnt/boot"
1748 )
1749 '';
1750 bootLoader = "systemd-boot";
1751 extraInstallerConfig =
1752 { modulesPath, ... }:
1753 {
1754 config = {
1755 services.stratis.enable = true;
1756 environment.systemPackages = [
1757 pkgs.stratis-cli
1758 pkgs.thin-provisioning-tools
1759 pkgs.lvm2.bin
1760 pkgs.stratisd.initrd
1761 ];
1762 };
1763 };
1764 };
1765
1766 gptAutoRoot =
1767 let
1768 rootPartType =
1769 {
1770 ia32 = "44479540-F297-41B2-9AF7-D131D5F0458A";
1771 x64 = "4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709";
1772 arm = "69DAD710-2CE4-4E3C-B16C-21A1D49ABED3";
1773 aa64 = "B921B045-1DF0-41C3-AF44-4C6F280D3FAE";
1774 }
1775 .${pkgs.stdenv.hostPlatform.efiArch};
1776 in
1777 makeInstallerTest "gptAutoRoot" {
1778 disableFileSystems = true;
1779 createPartitions = ''
1780 installer.succeed(
1781 "sgdisk --zap-all /dev/vda",
1782 "sgdisk --new=1:0:+100M --typecode=0:ef00 /dev/vda", # /boot
1783 "sgdisk --new=2:0:+1G --typecode=0:8200 /dev/vda", # swap
1784 "sgdisk --new=3:0:+5G --typecode=0:${rootPartType} /dev/vda", # /
1785 "udevadm settle",
1786
1787 "mkfs.vfat /dev/vda1",
1788 "mkswap /dev/vda2 -L swap",
1789 "swapon -L swap",
1790 "mkfs.ext4 -L root /dev/vda3",
1791 "udevadm settle",
1792
1793 "mount /dev/vda3 /mnt",
1794 "mkdir -p /mnt/boot",
1795 "mount /dev/vda1 /mnt/boot"
1796 )
1797 '';
1798 bootLoader = "systemd-boot";
1799 extraConfig = ''
1800 boot.initrd.systemd.root = "gpt-auto";
1801 boot.initrd.supportedFilesystems = ["ext4"];
1802 '';
1803 };
1804}