1{ system ? builtins.currentSystem,
2 config ? {},
3 pkgs ? import ../.. { inherit system config; },
4 systemdStage1 ? false
5}:
6
7with import ../lib/testing-python.nix { inherit system pkgs; };
8with pkgs.lib;
9
10let
11
12 # The configuration to install.
13 makeConfig = { bootLoader, grubDevice, grubIdentifier, grubUseEfi
14 , extraConfig, forceGrubReinstallCount ? 0, flake ? false
15 }:
16 pkgs.writeText "configuration.nix" ''
17 { config, lib, pkgs, modulesPath, ... }:
18
19 { imports =
20 [ ./hardware-configuration.nix
21 ${if flake
22 then "" # Still included, but via installer/flake.nix
23 else "<nixpkgs/nixos/modules/testing/test-instrumentation.nix>"}
24 ];
25
26 networking.hostName = "thatworked";
27
28 documentation.enable = false;
29
30 # To ensure that we can rebuild the grub configuration on the nixos-rebuild
31 system.extraDependencies = with pkgs; [ stdenvNoCC ];
32
33 ${optionalString systemdStage1 "boot.initrd.systemd.enable = true;"}
34
35 ${optionalString (bootLoader == "grub") ''
36 boot.loader.grub.extraConfig = "serial; terminal_output serial";
37 ${if grubUseEfi then ''
38 boot.loader.grub.device = "nodev";
39 boot.loader.grub.efiSupport = true;
40 boot.loader.grub.efiInstallAsRemovable = true; # XXX: needed for OVMF?
41 '' else ''
42 boot.loader.grub.device = "${grubDevice}";
43 boot.loader.grub.fsIdentifier = "${grubIdentifier}";
44 ''}
45
46 boot.loader.grub.configurationLimit = 100 + ${toString forceGrubReinstallCount};
47 ''}
48
49 ${optionalString (bootLoader == "systemd-boot") ''
50 boot.loader.systemd-boot.enable = true;
51 ''}
52
53 boot.initrd.secrets."/etc/secret" = ./secret;
54
55 users.users.alice = {
56 isNormalUser = true;
57 home = "/home/alice";
58 description = "Alice Foobar";
59 };
60
61 hardware.enableAllFirmware = lib.mkForce false;
62
63 ${replaceStrings ["\n"] ["\n "] extraConfig}
64 }
65 '';
66
67
68 # The test script boots a NixOS VM, installs NixOS on an empty hard
69 # disk, and then reboot from the hard disk. It's parameterized with
70 # a test script fragment `createPartitions', which must create
71 # partitions and filesystems.
72 testScriptFun = { bootLoader, createPartitions, grubDevice, grubUseEfi, grubIdentifier
73 , postInstallCommands, preBootCommands, postBootCommands, extraConfig
74 , testSpecialisationConfig, testFlakeSwitch
75 }:
76 let iface = "virtio";
77 isEfi = bootLoader == "systemd-boot" || (bootLoader == "grub" && grubUseEfi);
78 bios = if pkgs.stdenv.isAarch64 then "QEMU_EFI.fd" else "OVMF.fd";
79 in if !isEfi && !pkgs.stdenv.hostPlatform.isx86 then ''
80 machine.succeed("true")
81 '' else ''
82 def assemble_qemu_flags():
83 flags = "-cpu max"
84 ${if (system == "x86_64-linux" || system == "i686-linux")
85 then ''flags += " -m 1024"''
86 else ''flags += " -m 768 -enable-kvm -machine virt,gic-version=host"''
87 }
88 return flags
89
90
91 qemu_flags = {"qemuFlags": assemble_qemu_flags()}
92
93 import os
94
95 image_dir = machine.state_dir
96 disk_image = os.path.join(image_dir, "machine.qcow2")
97
98 hd_flags = {
99 "hdaInterface": "${iface}",
100 "hda": disk_image,
101 }
102 ${optionalString isEfi ''
103 hd_flags.update(
104 bios="${pkgs.OVMF.fd}/FV/${bios}"
105 )''
106 }
107 default_flags = {**hd_flags, **qemu_flags}
108
109
110 def create_machine_named(name):
111 return create_machine({**default_flags, "name": name})
112
113
114 machine.start()
115
116 with subtest("Assert readiness of login prompt"):
117 machine.succeed("echo hello")
118
119 with subtest("Wait for hard disks to appear in /dev"):
120 machine.succeed("udevadm settle")
121
122 ${createPartitions}
123
124 with subtest("Create the NixOS configuration"):
125 machine.succeed("nixos-generate-config --root /mnt")
126 machine.succeed("cat /mnt/etc/nixos/hardware-configuration.nix >&2")
127 machine.copy_from_host(
128 "${ makeConfig {
129 inherit bootLoader grubDevice grubIdentifier
130 grubUseEfi extraConfig;
131 }
132 }",
133 "/mnt/etc/nixos/configuration.nix",
134 )
135 machine.copy_from_host("${pkgs.writeText "secret" "secret"}", "/mnt/etc/nixos/secret")
136
137 with subtest("Perform the installation"):
138 machine.succeed("nixos-install < /dev/null >&2")
139
140 with subtest("Do it again to make sure it's idempotent"):
141 machine.succeed("nixos-install < /dev/null >&2")
142
143 with subtest("Check that we can build things in nixos-enter"):
144 machine.succeed(
145 """
146 nixos-enter -- nix-build --option substitute false -E 'derivation {
147 name = "t";
148 builder = "/bin/sh";
149 args = ["-c" "echo nixos-enter build > $out"];
150 system = builtins.currentSystem;
151 preferLocalBuild = true;
152 }'
153 """
154 )
155
156 ${postInstallCommands}
157
158 with subtest("Shutdown system after installation"):
159 machine.succeed("umount -R /mnt")
160 machine.succeed("sync")
161 machine.shutdown()
162
163 # Now see if we can boot the installation.
164 machine = create_machine_named("boot-after-install")
165
166 # For example to enter LUKS passphrase.
167 ${preBootCommands}
168
169 with subtest("Assert that /boot get mounted"):
170 machine.wait_for_unit("local-fs.target")
171 ${if bootLoader == "grub"
172 then ''machine.succeed("test -e /boot/grub")''
173 else ''machine.succeed("test -e /boot/loader/loader.conf")''
174 }
175
176 with subtest("Check whether /root has correct permissions"):
177 assert "700" in machine.succeed("stat -c '%a' /root")
178
179 with subtest("Assert swap device got activated"):
180 # uncomment once https://bugs.freedesktop.org/show_bug.cgi?id=86930 is resolved
181 machine.wait_for_unit("swap.target")
182 machine.succeed("cat /proc/swaps | grep -q /dev")
183
184 with subtest("Check that the store is in good shape"):
185 machine.succeed("nix-store --verify --check-contents >&2")
186
187 with subtest("Check whether the channel works"):
188 machine.succeed("nix-env -iA nixos.procps >&2")
189 assert ".nix-profile" in machine.succeed("type -tP ps | tee /dev/stderr")
190
191 with subtest(
192 "Check that the daemon works, and that non-root users can run builds "
193 "(this will build a new profile generation through the daemon)"
194 ):
195 machine.succeed("su alice -l -c 'nix-env -iA nixos.procps' >&2")
196
197 with subtest("Configure system with writable Nix store on next boot"):
198 # we're not using copy_from_host here because the installer image
199 # doesn't know about the host-guest sharing mechanism.
200 machine.copy_from_host_via_shell(
201 "${ makeConfig {
202 inherit bootLoader grubDevice grubIdentifier
203 grubUseEfi extraConfig;
204 forceGrubReinstallCount = 1;
205 }
206 }",
207 "/etc/nixos/configuration.nix",
208 )
209
210 with subtest("Check whether nixos-rebuild works"):
211 machine.succeed("nixos-rebuild switch >&2")
212
213 # FIXME: Nix 2.4 broke nixos-option, someone has to fix it.
214 # with subtest("Test nixos-option"):
215 # kernel_modules = machine.succeed("nixos-option boot.initrd.kernelModules")
216 # assert "virtio_console" in kernel_modules
217 # assert "List of modules" in kernel_modules
218 # assert "qemu-guest.nix" in kernel_modules
219
220 machine.shutdown()
221
222 # Check whether a writable store build works
223 machine = create_machine_named("rebuild-switch")
224 ${preBootCommands}
225 machine.wait_for_unit("multi-user.target")
226
227 # we're not using copy_from_host here because the installer image
228 # doesn't know about the host-guest sharing mechanism.
229 machine.copy_from_host_via_shell(
230 "${ makeConfig {
231 inherit bootLoader grubDevice grubIdentifier
232 grubUseEfi extraConfig;
233 forceGrubReinstallCount = 2;
234 }
235 }",
236 "/etc/nixos/configuration.nix",
237 )
238 machine.succeed("nixos-rebuild boot >&2")
239 machine.shutdown()
240
241 # And just to be sure, check that the machine still boots after
242 # "nixos-rebuild switch".
243 machine = create_machine_named("boot-after-rebuild-switch")
244 ${preBootCommands}
245 machine.wait_for_unit("network.target")
246
247 # Sanity check, is it the configuration.nix we generated?
248 hostname = machine.succeed("hostname").strip()
249 assert hostname == "thatworked"
250
251 ${postBootCommands}
252 machine.shutdown()
253
254 # Tests for validating clone configuration entries in grub menu
255 ''
256 + optionalString testSpecialisationConfig ''
257 # Reboot Machine
258 machine = create_machine_named("clone-default-config")
259 ${preBootCommands}
260 machine.wait_for_unit("multi-user.target")
261
262 with subtest("Booted configuration name should be 'Home'"):
263 # This is not the name that shows in the grub menu.
264 # The default configuration is always shown as "Default"
265 machine.succeed("cat /run/booted-system/configuration-name >&2")
266 assert "Home" in machine.succeed("cat /run/booted-system/configuration-name")
267
268 with subtest("We should **not** find a file named /etc/gitconfig"):
269 machine.fail("test -e /etc/gitconfig")
270
271 with subtest("Set grub to boot the second configuration"):
272 machine.succeed("grub-reboot 1")
273
274 ${postBootCommands}
275 machine.shutdown()
276
277 # Reboot Machine
278 machine = create_machine_named("clone-alternate-config")
279 ${preBootCommands}
280
281 machine.wait_for_unit("multi-user.target")
282 with subtest("Booted configuration name should be Work"):
283 machine.succeed("cat /run/booted-system/configuration-name >&2")
284 assert "Work" in machine.succeed("cat /run/booted-system/configuration-name")
285
286 with subtest("We should find a file named /etc/gitconfig"):
287 machine.succeed("test -e /etc/gitconfig")
288
289 ${postBootCommands}
290 machine.shutdown()
291 ''
292 + optionalString testFlakeSwitch ''
293 ${preBootCommands}
294 machine.start()
295
296 with subtest("Configure system with flake"):
297 # TODO: evaluate as user?
298 machine.succeed("""
299 mkdir /root/my-config
300 mv /etc/nixos/hardware-configuration.nix /root/my-config/
301 mv /etc/nixos/secret /root/my-config/
302 rm /etc/nixos/configuration.nix
303 """)
304 machine.copy_from_host_via_shell(
305 "${makeConfig {
306 inherit bootLoader grubDevice grubIdentifier grubUseEfi extraConfig;
307 forceGrubReinstallCount = 1;
308 flake = true;
309 }}",
310 "/root/my-config/configuration.nix",
311 )
312 machine.copy_from_host_via_shell(
313 "${./installer/flake.nix}",
314 "/root/my-config/flake.nix",
315 )
316 machine.succeed("""
317 # for some reason the image does not have `pkgs.path`, so
318 # we use readlink to find a Nixpkgs source.
319 pkgs=$(readlink -f /nix/var/nix/profiles/per-user/root/channels)/nixos
320 if ! [[ -e $pkgs/pkgs/top-level/default.nix ]]; then
321 echo 1>&2 "$pkgs does not seem to be a nixpkgs source. Please fix the test so that pkgs points to a nixpkgs source.";
322 exit 1;
323 fi
324 sed -e s^@nixpkgs@^$pkgs^ -i /root/my-config/flake.nix
325 """)
326
327 with subtest("Switch to flake based config"):
328 machine.succeed("nixos-rebuild switch --flake /root/my-config#xyz")
329
330 ${postBootCommands}
331 machine.shutdown()
332
333 ${preBootCommands}
334 machine.start()
335
336 machine.wait_for_unit("multi-user.target")
337
338 with subtest("nix-channel command is not available anymore"):
339 machine.succeed("! which nix-channel")
340
341 # Note that the channel profile is still present on disk, but configured
342 # not to be used.
343 with subtest("builtins.nixPath is now empty"):
344 machine.succeed("""
345 [[ "[ ]" == "$(nix-instantiate builtins.nixPath --eval --expr)" ]]
346 """)
347
348 with subtest("<nixpkgs> does not resolve"):
349 machine.succeed("""
350 ! nix-instantiate '<nixpkgs>' --eval --expr
351 """)
352
353 with subtest("Evaluate flake config in fresh env without nix-channel"):
354 machine.succeed("nixos-rebuild switch --flake /root/my-config#xyz")
355
356 with subtest("Evaluate flake config in fresh env without channel profiles"):
357 machine.succeed("""
358 (
359 exec 1>&2
360 rm -v /root/.nix-channels
361 rm -vrf ~/.nix-defexpr
362 rm -vrf /nix/var/nix/profiles/per-user/root/channels*
363 )
364 """)
365 machine.succeed("nixos-rebuild switch --flake /root/my-config#xyz")
366
367 ${postBootCommands}
368 machine.shutdown()
369 '';
370
371
372 makeInstallerTest = name:
373 { createPartitions
374 , postInstallCommands ? "", preBootCommands ? "", postBootCommands ? ""
375 , extraConfig ? ""
376 , extraInstallerConfig ? {}
377 , bootLoader ? "grub" # either "grub" or "systemd-boot"
378 , grubDevice ? "/dev/vda", grubIdentifier ? "uuid", grubUseEfi ? false
379 , enableOCR ? false, meta ? {}
380 , testSpecialisationConfig ? false
381 , testFlakeSwitch ? false
382 }:
383 makeTest {
384 inherit enableOCR;
385 name = "installer-" + name;
386 meta = {
387 # put global maintainers here, individuals go into makeInstallerTest fkt call
388 maintainers = (meta.maintainers or []);
389 };
390 nodes = {
391
392 # The configuration of the machine used to run "nixos-install".
393 machine = { pkgs, ... }: {
394 imports = [
395 ../modules/profiles/installation-device.nix
396 ../modules/profiles/base.nix
397 extraInstallerConfig
398 ./common/auto-format-root-device.nix
399 ];
400
401 # In systemdStage1, also automatically format the device backing the
402 # root filesystem.
403 virtualisation.fileSystems."/".autoFormat = systemdStage1;
404
405 # builds stuff in the VM, needs more juice
406 virtualisation.diskSize = 8 * 1024;
407 virtualisation.cores = 8;
408 virtualisation.memorySize = 1536;
409
410 boot.initrd.systemd.enable = systemdStage1;
411
412 # Use a small /dev/vdb as the root disk for the
413 # installer. This ensures the target disk (/dev/vda) is
414 # the same during and after installation.
415 virtualisation.emptyDiskImages = [ 512 ];
416 virtualisation.rootDevice = "/dev/vdb";
417 virtualisation.bootLoaderDevice = "/dev/vda";
418 virtualisation.qemu.diskInterface = "virtio";
419
420 # We don't want to have any networking in the guest whatsoever.
421 # Also, if any vlans are enabled, the guest will reboot
422 # (with a different configuration for legacy reasons),
423 # and spend 5 minutes waiting for the vlan interface to show up
424 # (which will never happen).
425 virtualisation.vlans = [];
426
427 boot.loader.systemd-boot.enable = mkIf (bootLoader == "systemd-boot") true;
428
429 hardware.enableAllFirmware = mkForce false;
430
431 # The test cannot access the network, so any packages we
432 # need must be included in the VM.
433 system.extraDependencies = with pkgs; [
434 bintools
435 brotli
436 brotli.dev
437 brotli.lib
438 desktop-file-utils
439 docbook5
440 docbook_xsl_ns
441 kbd.dev
442 kmod.dev
443 libarchive.dev
444 libxml2.bin
445 libxslt.bin
446 nixos-artwork.wallpapers.simple-dark-gray-bottom
447 ntp
448 perlPackages.ListCompare
449 perlPackages.XMLLibXML
450 python3Minimal
451 # make-options-doc/default.nix
452 (let
453 self = (pkgs.python3Minimal.override {
454 inherit self;
455 includeSiteCustomize = true;
456 });
457 in self.withPackages (p: [ p.mistune ]))
458 shared-mime-info
459 sudo
460 texinfo
461 unionfs-fuse
462 xorg.lndir
463
464 # add curl so that rather than seeing the test attempt to download
465 # curl's tarball, we see what it's trying to download
466 curl
467 ]
468 ++ optionals (bootLoader == "grub") (let
469 zfsSupport = lib.any (x: x == "zfs")
470 (extraInstallerConfig.boot.supportedFilesystems or []);
471 in [
472 (pkgs.grub2.override { inherit zfsSupport; })
473 (pkgs.grub2_efi.override { inherit zfsSupport; })
474 ]);
475
476 nix.settings = {
477 substituters = mkForce [];
478 hashed-mirrors = null;
479 connect-timeout = 1;
480 };
481 };
482
483 };
484
485 testScript = testScriptFun {
486 inherit bootLoader createPartitions postInstallCommands preBootCommands postBootCommands
487 grubDevice grubIdentifier grubUseEfi extraConfig
488 testSpecialisationConfig testFlakeSwitch;
489 };
490 };
491
492 makeLuksRootTest = name: luksFormatOpts: makeInstallerTest name {
493 createPartitions = ''
494 machine.succeed(
495 "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
496 + " mkpart primary ext2 1M 100MB" # /boot
497 + " mkpart primary linux-swap 100M 1024M"
498 + " mkpart primary 1024M -1s", # LUKS
499 "udevadm settle",
500 "mkswap /dev/vda2 -L swap",
501 "swapon -L swap",
502 "modprobe dm_mod dm_crypt",
503 "echo -n supersecret | cryptsetup luksFormat ${luksFormatOpts} -q /dev/vda3 -",
504 "echo -n supersecret | cryptsetup luksOpen --key-file - /dev/vda3 cryptroot",
505 "mkfs.ext3 -L nixos /dev/mapper/cryptroot",
506 "mount LABEL=nixos /mnt",
507 "mkfs.ext3 -L boot /dev/vda1",
508 "mkdir -p /mnt/boot",
509 "mount LABEL=boot /mnt/boot",
510 )
511 '';
512 extraConfig = ''
513 boot.kernelParams = lib.mkAfter [ "console=tty0" ];
514 '';
515 enableOCR = true;
516 preBootCommands = ''
517 machine.start()
518 machine.wait_for_text("[Pp]assphrase for")
519 machine.send_chars("supersecret\n")
520 '';
521 };
522
523 # The (almost) simplest partitioning scheme: a swap partition and
524 # one big filesystem partition.
525 simple-test-config = {
526 createPartitions = ''
527 machine.succeed(
528 "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
529 + " mkpart primary linux-swap 1M 1024M"
530 + " mkpart primary ext2 1024M -1s",
531 "udevadm settle",
532 "mkswap /dev/vda1 -L swap",
533 "swapon -L swap",
534 "mkfs.ext3 -L nixos /dev/vda2",
535 "mount LABEL=nixos /mnt",
536 )
537 '';
538 };
539
540 simple-test-config-flake = simple-test-config // {
541 testFlakeSwitch = true;
542 };
543
544 simple-uefi-grub-config = {
545 createPartitions = ''
546 machine.succeed(
547 "flock /dev/vda parted --script /dev/vda -- mklabel gpt"
548 + " mkpart ESP fat32 1M 100MiB" # /boot
549 + " set 1 boot on"
550 + " mkpart primary linux-swap 100MiB 1024MiB"
551 + " mkpart primary ext2 1024MiB -1MiB", # /
552 "udevadm settle",
553 "mkswap /dev/vda2 -L swap",
554 "swapon -L swap",
555 "mkfs.ext3 -L nixos /dev/vda3",
556 "mount LABEL=nixos /mnt",
557 "mkfs.vfat -n BOOT /dev/vda1",
558 "mkdir -p /mnt/boot",
559 "mount LABEL=BOOT /mnt/boot",
560 )
561 '';
562 bootLoader = "grub";
563 grubUseEfi = true;
564 };
565
566 specialisation-test-extraconfig = {
567 extraConfig = ''
568 environment.systemPackages = [ pkgs.grub2 ];
569 boot.loader.grub.configurationName = "Home";
570 specialisation.work.configuration = {
571 boot.loader.grub.configurationName = lib.mkForce "Work";
572
573 environment.etc = {
574 "gitconfig".text = "
575 [core]
576 gitproxy = none for work.com
577 ";
578 };
579 };
580 '';
581 testSpecialisationConfig = true;
582 };
583 # disable zfs so we can support latest kernel if needed
584 no-zfs-module = {
585 nixpkgs.overlays = [(final: super: {
586 zfs = super.zfs.overrideAttrs(_: {meta.platforms = [];});}
587 )];
588 };
589in {
590
591 # !!! `parted mkpart' seems to silently create overlapping partitions.
592
593
594 # The (almost) simplest partitioning scheme: a swap partition and
595 # one big filesystem partition.
596 simple = makeInstallerTest "simple" simple-test-config;
597
598 switchToFlake = makeInstallerTest "switch-to-flake" simple-test-config-flake;
599
600 # Test cloned configurations with the simple grub configuration
601 simpleSpecialised = makeInstallerTest "simpleSpecialised" (simple-test-config // specialisation-test-extraconfig);
602
603 # Simple GPT/UEFI configuration using systemd-boot with 3 partitions: ESP, swap & root filesystem
604 simpleUefiSystemdBoot = makeInstallerTest "simpleUefiSystemdBoot" {
605 createPartitions = ''
606 machine.succeed(
607 "flock /dev/vda parted --script /dev/vda -- mklabel gpt"
608 + " mkpart ESP fat32 1M 100MiB" # /boot
609 + " set 1 boot on"
610 + " mkpart primary linux-swap 100MiB 1024MiB"
611 + " mkpart primary ext2 1024MiB -1MiB", # /
612 "udevadm settle",
613 "mkswap /dev/vda2 -L swap",
614 "swapon -L swap",
615 "mkfs.ext3 -L nixos /dev/vda3",
616 "mount LABEL=nixos /mnt",
617 "mkfs.vfat -n BOOT /dev/vda1",
618 "mkdir -p /mnt/boot",
619 "mount LABEL=BOOT /mnt/boot",
620 )
621 '';
622 bootLoader = "systemd-boot";
623 };
624
625 simpleUefiGrub = makeInstallerTest "simpleUefiGrub" simple-uefi-grub-config;
626
627 # Test cloned configurations with the uefi grub configuration
628 simpleUefiGrubSpecialisation = makeInstallerTest "simpleUefiGrubSpecialisation" (simple-uefi-grub-config // specialisation-test-extraconfig);
629
630 # Same as the previous, but now with a separate /boot partition.
631 separateBoot = makeInstallerTest "separateBoot" {
632 createPartitions = ''
633 machine.succeed(
634 "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
635 + " mkpart primary ext2 1M 100MB" # /boot
636 + " mkpart primary linux-swap 100MB 1024M"
637 + " mkpart primary ext2 1024M -1s", # /
638 "udevadm settle",
639 "mkswap /dev/vda2 -L swap",
640 "swapon -L swap",
641 "mkfs.ext3 -L nixos /dev/vda3",
642 "mount LABEL=nixos /mnt",
643 "mkfs.ext3 -L boot /dev/vda1",
644 "mkdir -p /mnt/boot",
645 "mount LABEL=boot /mnt/boot",
646 )
647 '';
648 };
649
650 # Same as the previous, but with fat32 /boot.
651 separateBootFat = makeInstallerTest "separateBootFat" {
652 createPartitions = ''
653 machine.succeed(
654 "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
655 + " mkpart primary ext2 1M 100MB" # /boot
656 + " mkpart primary linux-swap 100MB 1024M"
657 + " mkpart primary ext2 1024M -1s", # /
658 "udevadm settle",
659 "mkswap /dev/vda2 -L swap",
660 "swapon -L swap",
661 "mkfs.ext3 -L nixos /dev/vda3",
662 "mount LABEL=nixos /mnt",
663 "mkfs.vfat -n BOOT /dev/vda1",
664 "mkdir -p /mnt/boot",
665 "mount LABEL=BOOT /mnt/boot",
666 )
667 '';
668 };
669
670 # zfs on / with swap
671 zfsroot = makeInstallerTest "zfs-root" {
672 extraInstallerConfig = {
673 boot.supportedFilesystems = [ "zfs" ];
674 };
675
676 extraConfig = ''
677 boot.supportedFilesystems = [ "zfs" ];
678
679 # Using by-uuid overrides the default of by-id, and is unique
680 # to the qemu disks, as they don't produce by-id paths for
681 # some reason.
682 boot.zfs.devNodes = "/dev/disk/by-uuid/";
683 networking.hostId = "00000000";
684 '';
685
686 createPartitions = ''
687 machine.succeed(
688 "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
689 + " mkpart primary 1M 100MB" # bpool
690 + " mkpart primary linux-swap 100M 1024M"
691 + " mkpart primary 1024M -1s", # rpool
692 "udevadm settle",
693 "mkswap /dev/vda2 -L swap",
694 "swapon -L swap",
695 "zpool create rpool /dev/vda3",
696 "zfs create -o mountpoint=legacy rpool/root",
697 "mount -t zfs rpool/root /mnt",
698 "zfs create -o mountpoint=legacy rpool/root/usr",
699 "mkdir /mnt/usr",
700 "mount -t zfs rpool/root/usr /mnt/usr",
701 "zpool create -o compatibility=grub2 bpool /dev/vda1",
702 "zfs create -o mountpoint=legacy bpool/boot",
703 "mkdir /mnt/boot",
704 "mount -t zfs bpool/boot /mnt/boot",
705 "udevadm settle",
706 )
707 '';
708
709 # umount & export bpool before shutdown
710 # this is a fix for "cannot import 'bpool': pool was previously in use from another system."
711 postInstallCommands = ''
712 machine.succeed("umount /mnt/boot")
713 machine.succeed("zpool export bpool")
714 '';
715 };
716
717 # Create two physical LVM partitions combined into one volume group
718 # that contains the logical swap and root partitions.
719 lvm = makeInstallerTest "lvm" {
720 createPartitions = ''
721 machine.succeed(
722 "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
723 + " mkpart primary 1M 2048M" # PV1
724 + " set 1 lvm on"
725 + " mkpart primary 2048M -1s" # PV2
726 + " set 2 lvm on",
727 "udevadm settle",
728 "pvcreate /dev/vda1 /dev/vda2",
729 "vgcreate MyVolGroup /dev/vda1 /dev/vda2",
730 "lvcreate --size 1G --name swap MyVolGroup",
731 "lvcreate --size 6G --name nixos MyVolGroup",
732 "mkswap -f /dev/MyVolGroup/swap -L swap",
733 "swapon -L swap",
734 "mkfs.xfs -L nixos /dev/MyVolGroup/nixos",
735 "mount LABEL=nixos /mnt",
736 )
737 '';
738 };
739
740 # Boot off an encrypted root partition with the default LUKS header format
741 luksroot = makeLuksRootTest "luksroot-format1" "";
742
743 # Boot off an encrypted root partition with LUKS1 format
744 luksroot-format1 = makeLuksRootTest "luksroot-format1" "--type=LUKS1";
745
746 # Boot off an encrypted root partition with LUKS2 format
747 luksroot-format2 = makeLuksRootTest "luksroot-format2" "--type=LUKS2";
748
749 # Test whether opening encrypted filesystem with keyfile
750 # Checks for regression of missing cryptsetup, when no luks device without
751 # keyfile is configured
752 encryptedFSWithKeyfile = makeInstallerTest "encryptedFSWithKeyfile" {
753 createPartitions = ''
754 machine.succeed(
755 "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
756 + " mkpart primary ext2 1M 100MB" # /boot
757 + " mkpart primary linux-swap 100M 1024M"
758 + " mkpart primary 1024M 1280M" # LUKS with keyfile
759 + " mkpart primary 1280M -1s",
760 "udevadm settle",
761 "mkswap /dev/vda2 -L swap",
762 "swapon -L swap",
763 "mkfs.ext3 -L nixos /dev/vda4",
764 "mount LABEL=nixos /mnt",
765 "mkfs.ext3 -L boot /dev/vda1",
766 "mkdir -p /mnt/boot",
767 "mount LABEL=boot /mnt/boot",
768 "modprobe dm_mod dm_crypt",
769 "echo -n supersecret > /mnt/keyfile",
770 "cryptsetup luksFormat -q /dev/vda3 --key-file /mnt/keyfile",
771 "cryptsetup luksOpen --key-file /mnt/keyfile /dev/vda3 crypt",
772 "mkfs.ext3 -L test /dev/mapper/crypt",
773 "cryptsetup luksClose crypt",
774 "mkdir -p /mnt/test",
775 )
776 '';
777 extraConfig = ''
778 fileSystems."/test" = {
779 device = "/dev/disk/by-label/test";
780 fsType = "ext3";
781 encrypted.enable = true;
782 encrypted.blkDev = "/dev/vda3";
783 encrypted.label = "crypt";
784 encrypted.keyFile = "/${if systemdStage1 then "sysroot" else "mnt-root"}/keyfile";
785 };
786 '';
787 };
788
789 # Full disk encryption (root, kernel and initrd encrypted) using GRUB, GPT/UEFI,
790 # LVM-on-LUKS and a keyfile in initrd.secrets to enter the passphrase once
791 fullDiskEncryption = makeInstallerTest "fullDiskEncryption" {
792 createPartitions = ''
793 machine.succeed(
794 "flock /dev/vda parted --script /dev/vda -- mklabel gpt"
795 + " mkpart ESP fat32 1M 100MiB" # /boot/efi
796 + " set 1 boot on"
797 + " mkpart primary ext2 1024MiB -1MiB", # LUKS
798 "udevadm settle",
799 "modprobe dm_mod dm_crypt",
800 "dd if=/dev/random of=luks.key bs=256 count=1",
801 "echo -n supersecret | cryptsetup luksFormat -q --pbkdf-force-iterations 1000 --type luks1 /dev/vda2 -",
802 "echo -n supersecret | cryptsetup luksAddKey -q --pbkdf-force-iterations 1000 --key-file - /dev/vda2 luks.key",
803 "echo -n supersecret | cryptsetup luksOpen --key-file - /dev/vda2 crypt",
804 "pvcreate /dev/mapper/crypt",
805 "vgcreate crypt /dev/mapper/crypt",
806 "lvcreate -L 100M -n swap crypt",
807 "lvcreate -l '100%FREE' -n nixos crypt",
808 "mkfs.vfat -n efi /dev/vda1",
809 "mkfs.ext4 -L nixos /dev/crypt/nixos",
810 "mkswap -L swap /dev/crypt/swap",
811 "mount LABEL=nixos /mnt",
812 "mkdir -p /mnt/{etc/nixos,boot/efi}",
813 "mount LABEL=efi /mnt/boot/efi",
814 "swapon -L swap",
815 "mv luks.key /mnt/etc/nixos/"
816 )
817 '';
818 bootLoader = "grub";
819 grubUseEfi = true;
820 extraConfig = ''
821 boot.loader.grub.enableCryptodisk = true;
822 boot.loader.efi.efiSysMountPoint = "/boot/efi";
823
824 boot.initrd.secrets."/luks.key" = ./luks.key;
825 boot.initrd.luks.devices.crypt =
826 { device = "/dev/vda2";
827 keyFile = "/luks.key";
828 };
829 '';
830 enableOCR = true;
831 preBootCommands = ''
832 machine.start()
833 machine.wait_for_text("Enter passphrase for")
834 machine.send_chars("supersecret\n")
835 '';
836 };
837
838 swraid = makeInstallerTest "swraid" {
839 createPartitions = ''
840 machine.succeed(
841 "flock /dev/vda parted --script /dev/vda --"
842 + " mklabel msdos"
843 + " mkpart primary ext2 1M 100MB" # /boot
844 + " mkpart extended 100M -1s"
845 + " mkpart logical 102M 3102M" # md0 (root), first device
846 + " mkpart logical 3103M 6103M" # md0 (root), second device
847 + " mkpart logical 6104M 6360M" # md1 (swap), first device
848 + " mkpart logical 6361M 6617M", # md1 (swap), second device
849 "udevadm settle",
850 "ls -l /dev/vda* >&2",
851 "cat /proc/partitions >&2",
852 "udevadm control --stop-exec-queue",
853 "mdadm --create --force /dev/md0 --metadata 1.2 --level=raid1 "
854 + "--raid-devices=2 /dev/vda5 /dev/vda6",
855 "mdadm --create --force /dev/md1 --metadata 1.2 --level=raid1 "
856 + "--raid-devices=2 /dev/vda7 /dev/vda8",
857 "udevadm control --start-exec-queue",
858 "udevadm settle",
859 "mkswap -f /dev/md1 -L swap",
860 "swapon -L swap",
861 "mkfs.ext3 -L nixos /dev/md0",
862 "mount LABEL=nixos /mnt",
863 "mkfs.ext3 -L boot /dev/vda1",
864 "mkdir /mnt/boot",
865 "mount LABEL=boot /mnt/boot",
866 "udevadm settle",
867 )
868 '';
869 preBootCommands = ''
870 machine.start()
871 machine.fail("dmesg | grep 'immediate safe mode'")
872 '';
873 };
874
875 bcache = makeInstallerTest "bcache" {
876 createPartitions = ''
877 machine.succeed(
878 "flock /dev/vda parted --script /dev/vda --"
879 + " mklabel msdos"
880 + " mkpart primary ext2 1M 100MB" # /boot
881 + " mkpart primary 100MB 512MB " # swap
882 + " mkpart primary 512MB 1024MB" # Cache (typically SSD)
883 + " mkpart primary 1024MB -1s ", # Backing device (typically HDD)
884 "modprobe bcache",
885 "udevadm settle",
886 "make-bcache -B /dev/vda4 -C /dev/vda3",
887 "udevadm settle",
888 "mkfs.ext3 -L nixos /dev/bcache0",
889 "mount LABEL=nixos /mnt",
890 "mkfs.ext3 -L boot /dev/vda1",
891 "mkdir /mnt/boot",
892 "mount LABEL=boot /mnt/boot",
893 "mkswap -f /dev/vda2 -L swap",
894 "swapon -L swap",
895 )
896 '';
897 };
898
899 bcachefsSimple = makeInstallerTest "bcachefs-simple" {
900 extraInstallerConfig = {
901 boot.supportedFilesystems = [ "bcachefs" ];
902 imports = [ no-zfs-module ];
903 };
904
905 createPartitions = ''
906 machine.succeed(
907 "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
908 + " mkpart primary ext2 1M 100MB" # /boot
909 + " mkpart primary linux-swap 100M 1024M" # swap
910 + " mkpart primary 1024M -1s", # /
911 "udevadm settle",
912 "mkswap /dev/vda2 -L swap",
913 "swapon -L swap",
914 "mkfs.bcachefs -L root /dev/vda3",
915 "mount -t bcachefs /dev/vda3 /mnt",
916 "mkfs.ext3 -L boot /dev/vda1",
917 "mkdir -p /mnt/boot",
918 "mount /dev/vda1 /mnt/boot",
919 )
920 '';
921 };
922
923 bcachefsEncrypted = makeInstallerTest "bcachefs-encrypted" {
924 extraInstallerConfig = {
925 boot.supportedFilesystems = [ "bcachefs" ];
926
927 # disable zfs so we can support latest kernel if needed
928 imports = [ no-zfs-module ];
929
930 environment.systemPackages = with pkgs; [ keyutils ];
931 };
932
933 extraConfig = ''
934 boot.kernelParams = lib.mkAfter [ "console=tty0" ];
935 '';
936
937 enableOCR = true;
938 preBootCommands = ''
939 machine.start()
940 # Enter it wrong once
941 machine.wait_for_text("enter passphrase for ")
942 machine.send_chars("wrong\n")
943 # Then enter it right.
944 machine.wait_for_text("enter passphrase for ")
945 machine.send_chars("password\n")
946 '';
947
948 createPartitions = ''
949 machine.succeed(
950 "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
951 + " mkpart primary ext2 1M 100MB" # /boot
952 + " mkpart primary linux-swap 100M 1024M" # swap
953 + " mkpart primary 1024M -1s", # /
954 "udevadm settle",
955 "mkswap /dev/vda2 -L swap",
956 "swapon -L swap",
957 "echo password | mkfs.bcachefs -L root --encrypted /dev/vda3",
958 "echo password | bcachefs unlock -k session /dev/vda3",
959 "echo password | mount -t bcachefs /dev/vda3 /mnt",
960 "mkfs.ext3 -L boot /dev/vda1",
961 "mkdir -p /mnt/boot",
962 "mount /dev/vda1 /mnt/boot",
963 )
964 '';
965 };
966
967 bcachefsMulti = makeInstallerTest "bcachefs-multi" {
968 extraInstallerConfig = {
969 boot.supportedFilesystems = [ "bcachefs" ];
970
971 # disable zfs so we can support latest kernel if needed
972 imports = [ no-zfs-module ];
973 };
974
975 createPartitions = ''
976 machine.succeed(
977 "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
978 + " mkpart primary ext2 1M 100MB" # /boot
979 + " mkpart primary linux-swap 100M 1024M" # swap
980 + " mkpart primary 1024M 4096M" # /
981 + " mkpart primary 4096M -1s", # /
982 "udevadm settle",
983 "mkswap /dev/vda2 -L swap",
984 "swapon -L swap",
985 "mkfs.bcachefs -L root --metadata_replicas 2 --foreground_target ssd --promote_target ssd --background_target hdd --label ssd /dev/vda3 --label hdd /dev/vda4",
986 "mount -t bcachefs /dev/vda3:/dev/vda4 /mnt",
987 "mkfs.ext3 -L boot /dev/vda1",
988 "mkdir -p /mnt/boot",
989 "mount /dev/vda1 /mnt/boot",
990 )
991 '';
992 };
993
994 bcachefsLinuxTesting = makeInstallerTest "bcachefs-linux-testing" {
995 extraInstallerConfig = {
996 imports = [ no-zfs-module ];
997
998 boot = {
999 supportedFilesystems = [ "bcachefs" ];
1000 kernelPackages = pkgs.linuxPackages_testing;
1001 };
1002 };
1003
1004 extraConfig = ''
1005 boot.kernelPackages = pkgs.linuxPackages_testing;
1006 '';
1007
1008 createPartitions = ''
1009 machine.succeed(
1010 "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
1011 + " mkpart primary ext2 1M 100MB" # /boot
1012 + " mkpart primary linux-swap 100M 1024M" # swap
1013 + " mkpart primary 1024M -1s", # /
1014 "udevadm settle",
1015 "mkswap /dev/vda2 -L swap",
1016 "swapon -L swap",
1017 "mkfs.bcachefs -L root /dev/vda3",
1018 "mount -t bcachefs /dev/vda3 /mnt",
1019 "mkfs.ext3 -L boot /dev/vda1",
1020 "mkdir -p /mnt/boot",
1021 "mount /dev/vda1 /mnt/boot",
1022 )
1023 '';
1024 };
1025
1026 bcachefsUpgradeToLinuxTesting = makeInstallerTest "bcachefs-upgrade-to-linux-testing" {
1027 extraInstallerConfig = {
1028 imports = [ no-zfs-module ];
1029 boot.supportedFilesystems = [ "bcachefs" ];
1030 # We don't have network access in the VM, we need this for `nixos-install`
1031 system.extraDependencies = [ pkgs.linux_testing ];
1032 };
1033
1034 extraConfig = ''
1035 boot.kernelPackages = pkgs.linuxPackages_testing;
1036 '';
1037
1038 createPartitions = ''
1039 machine.succeed(
1040 "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
1041 + " mkpart primary ext2 1M 100MB" # /boot
1042 + " mkpart primary linux-swap 100M 1024M" # swap
1043 + " mkpart primary 1024M -1s", # /
1044 "udevadm settle",
1045 "mkswap /dev/vda2 -L swap",
1046 "swapon -L swap",
1047 "mkfs.bcachefs -L root /dev/vda3",
1048 "mount -t bcachefs /dev/vda3 /mnt",
1049 "mkfs.ext3 -L boot /dev/vda1",
1050 "mkdir -p /mnt/boot",
1051 "mount /dev/vda1 /mnt/boot",
1052 )
1053 '';
1054 };
1055
1056 # Test using labels to identify volumes in grub
1057 simpleLabels = makeInstallerTest "simpleLabels" {
1058 createPartitions = ''
1059 machine.succeed(
1060 "sgdisk -Z /dev/vda",
1061 "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",
1062 "mkswap /dev/vda2 -L swap",
1063 "swapon -L swap",
1064 "mkfs.ext4 -L root /dev/vda3",
1065 "mount LABEL=root /mnt",
1066 )
1067 '';
1068 grubIdentifier = "label";
1069 };
1070
1071 # Test using the provided disk name within grub
1072 # TODO: Fix udev so the symlinks are unneeded in /dev/disks
1073 simpleProvided = makeInstallerTest "simpleProvided" {
1074 createPartitions = ''
1075 uuid = "$(blkid -s UUID -o value /dev/vda2)"
1076 machine.succeed(
1077 "sgdisk -Z /dev/vda",
1078 "sgdisk -n 1:0:+1M -n 2:0:+100M -n 3:0:+1G -N 4 -t 1:ef02 -t 2:8300 "
1079 + "-t 3:8200 -t 4:8300 -c 2:boot -c 4:root /dev/vda",
1080 "mkswap /dev/vda3 -L swap",
1081 "swapon -L swap",
1082 "mkfs.ext4 -L boot /dev/vda2",
1083 "mkfs.ext4 -L root /dev/vda4",
1084 )
1085 machine.execute(f"ln -s ../../vda2 /dev/disk/by-uuid/{uuid}")
1086 machine.execute("ln -s ../../vda4 /dev/disk/by-label/root")
1087 machine.succeed(
1088 "mount /dev/disk/by-label/root /mnt",
1089 "mkdir /mnt/boot",
1090 f"mount /dev/disk/by-uuid/{uuid} /mnt/boot",
1091 )
1092 '';
1093 grubIdentifier = "provided";
1094 };
1095
1096 # Simple btrfs grub testing
1097 btrfsSimple = makeInstallerTest "btrfsSimple" {
1098 createPartitions = ''
1099 machine.succeed(
1100 "sgdisk -Z /dev/vda",
1101 "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",
1102 "mkswap /dev/vda2 -L swap",
1103 "swapon -L swap",
1104 "mkfs.btrfs -L root /dev/vda3",
1105 "mount LABEL=root /mnt",
1106 )
1107 '';
1108 };
1109
1110 # Test to see if we can detect /boot and /nix on subvolumes
1111 btrfsSubvols = makeInstallerTest "btrfsSubvols" {
1112 createPartitions = ''
1113 machine.succeed(
1114 "sgdisk -Z /dev/vda",
1115 "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",
1116 "mkswap /dev/vda2 -L swap",
1117 "swapon -L swap",
1118 "mkfs.btrfs -L root /dev/vda3",
1119 "btrfs device scan",
1120 "mount LABEL=root /mnt",
1121 "btrfs subvol create /mnt/boot",
1122 "btrfs subvol create /mnt/nixos",
1123 "btrfs subvol create /mnt/nixos/default",
1124 "umount /mnt",
1125 "mount -o defaults,subvol=nixos/default LABEL=root /mnt",
1126 "mkdir /mnt/boot",
1127 "mount -o defaults,subvol=boot LABEL=root /mnt/boot",
1128 )
1129 '';
1130 };
1131
1132 # Test to see if we can detect default and aux subvolumes correctly
1133 btrfsSubvolDefault = makeInstallerTest "btrfsSubvolDefault" {
1134 createPartitions = ''
1135 machine.succeed(
1136 "sgdisk -Z /dev/vda",
1137 "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",
1138 "mkswap /dev/vda2 -L swap",
1139 "swapon -L swap",
1140 "mkfs.btrfs -L root /dev/vda3",
1141 "btrfs device scan",
1142 "mount LABEL=root /mnt",
1143 "btrfs subvol create /mnt/badpath",
1144 "btrfs subvol create /mnt/badpath/boot",
1145 "btrfs subvol create /mnt/nixos",
1146 "btrfs subvol set-default "
1147 + "$(btrfs subvol list /mnt | grep 'nixos' | awk '{print $2}') /mnt",
1148 "umount /mnt",
1149 "mount -o defaults LABEL=root /mnt",
1150 "mkdir -p /mnt/badpath/boot", # Help ensure the detection mechanism
1151 # is actually looking up subvolumes
1152 "mkdir /mnt/boot",
1153 "mount -o defaults,subvol=badpath/boot LABEL=root /mnt/boot",
1154 )
1155 '';
1156 };
1157
1158 # Test to see if we can deal with subvols that need to be escaped in fstab
1159 btrfsSubvolEscape = makeInstallerTest "btrfsSubvolEscape" {
1160 createPartitions = ''
1161 machine.succeed(
1162 "sgdisk -Z /dev/vda",
1163 "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",
1164 "mkswap /dev/vda2 -L swap",
1165 "swapon -L swap",
1166 "mkfs.btrfs -L root /dev/vda3",
1167 "btrfs device scan",
1168 "mount LABEL=root /mnt",
1169 "btrfs subvol create '/mnt/nixos in space'",
1170 "btrfs subvol create /mnt/boot",
1171 "umount /mnt",
1172 "mount -o 'defaults,subvol=nixos in space' LABEL=root /mnt",
1173 "mkdir /mnt/boot",
1174 "mount -o defaults,subvol=boot LABEL=root /mnt/boot",
1175 )
1176 '';
1177 };
1178} // optionalAttrs systemdStage1 {
1179 stratisRoot = makeInstallerTest "stratisRoot" {
1180 createPartitions = ''
1181 machine.succeed(
1182 "sgdisk --zap-all /dev/vda",
1183 "sgdisk --new=1:0:+100M --typecode=0:ef00 /dev/vda", # /boot
1184 "sgdisk --new=2:0:+1G --typecode=0:8200 /dev/vda", # swap
1185 "sgdisk --new=3:0:+5G --typecode=0:8300 /dev/vda", # /
1186 "udevadm settle",
1187
1188 "mkfs.vfat /dev/vda1",
1189 "mkswap /dev/vda2 -L swap",
1190 "swapon -L swap",
1191 "stratis pool create my-pool /dev/vda3",
1192 "stratis filesystem create my-pool nixos",
1193 "udevadm settle",
1194
1195 "mount /dev/stratis/my-pool/nixos /mnt",
1196 "mkdir -p /mnt/boot",
1197 "mount /dev/vda1 /mnt/boot"
1198 )
1199 '';
1200 bootLoader = "systemd-boot";
1201 extraInstallerConfig = { modulesPath, ...}: {
1202 config = {
1203 services.stratis.enable = true;
1204 environment.systemPackages = [
1205 pkgs.stratis-cli
1206 pkgs.thin-provisioning-tools
1207 pkgs.lvm2.bin
1208 pkgs.stratisd.initrd
1209 ];
1210 };
1211 };
1212 };
1213}