1{ system ? builtins.currentSystem }:
2
3with import ../lib/testing.nix { inherit system; };
4with import ../lib/qemu-flags.nix;
5with pkgs.lib;
6
7let
8
9 # The configuration to install.
10 makeConfig = { bootLoader, grubVersion, grubDevice, grubIdentifier
11 , extraConfig, forceGrubReinstallCount ? 0
12 }:
13 pkgs.writeText "configuration.nix" ''
14 { config, lib, pkgs, modulesPath, ... }:
15
16 { imports =
17 [ ./hardware-configuration.nix
18 <nixpkgs/nixos/modules/testing/test-instrumentation.nix>
19 ];
20
21 # To ensure that we can rebuild the grub configuration on the nixos-rebuild
22 system.extraDependencies = with pkgs; [ stdenvNoCC ];
23
24 ${optionalString (bootLoader == "grub") ''
25 boot.loader.grub.version = ${toString grubVersion};
26 ${optionalString (grubVersion == 1) ''
27 boot.loader.grub.splashImage = null;
28 ''}
29 boot.loader.grub.device = "${grubDevice}";
30 boot.loader.grub.extraConfig = "serial; terminal_output.serial";
31 boot.loader.grub.fsIdentifier = "${grubIdentifier}";
32
33 boot.loader.grub.configurationLimit = 100 + ${toString forceGrubReinstallCount};
34 ''}
35
36 ${optionalString (bootLoader == "systemd-boot") ''
37 boot.loader.systemd-boot.enable = true;
38 ''}
39
40 users.extraUsers.alice = {
41 isNormalUser = true;
42 home = "/home/alice";
43 description = "Alice Foobar";
44 };
45
46 hardware.enableAllFirmware = lib.mkForce false;
47
48 ${replaceChars ["\n"] ["\n "] extraConfig}
49 }
50 '';
51
52
53 channelContents = [ pkgs.rlwrap ];
54
55
56 # The test script boots a NixOS VM, installs NixOS on an empty hard
57 # disk, and then reboot from the hard disk. It's parameterized with
58 # a test script fragment `createPartitions', which must create
59 # partitions and filesystems.
60 testScriptFun = { bootLoader, createPartitions, grubVersion, grubDevice
61 , grubIdentifier, preBootCommands, extraConfig
62 }:
63 let
64 iface = if grubVersion == 1 then "ide" else "virtio";
65 qemuFlags =
66 (if system == "x86_64-linux" then "-m 768 " else "-m 512 ") +
67 (optionalString (system == "x86_64-linux") "-cpu kvm64 ");
68 hdFlags = ''hda => "vm-state-machine/machine.qcow2", hdaInterface => "${iface}", ''
69 + optionalString (bootLoader == "systemd-boot") ''bios => "${pkgs.OVMF.fd}/FV/OVMF.fd", '';
70 in
71 ''
72 $machine->start;
73
74 # Make sure that we get a login prompt etc.
75 $machine->succeed("echo hello");
76 #$machine->waitForUnit('getty@tty2');
77 #$machine->waitForUnit("rogue");
78 $machine->waitForUnit("nixos-manual");
79
80 # Wait for hard disks to appear in /dev
81 $machine->succeed("udevadm settle");
82
83 # Partition the disk.
84 ${createPartitions}
85
86 # Create the NixOS configuration.
87 $machine->succeed("nixos-generate-config --root /mnt");
88
89 $machine->succeed("cat /mnt/etc/nixos/hardware-configuration.nix >&2");
90
91 $machine->copyFileFromHost(
92 "${ makeConfig { inherit bootLoader grubVersion grubDevice grubIdentifier extraConfig; } }",
93 "/mnt/etc/nixos/configuration.nix");
94
95 # Perform the installation.
96 $machine->succeed("nixos-install < /dev/null >&2");
97
98 # Do it again to make sure it's idempotent.
99 $machine->succeed("nixos-install < /dev/null >&2");
100
101 $machine->succeed("umount /mnt/boot || true");
102 $machine->succeed("umount /mnt");
103 $machine->succeed("sync");
104
105 $machine->shutdown;
106
107 # Now see if we can boot the installation.
108 $machine = createMachine({ ${hdFlags} qemuFlags => "${qemuFlags}", name => "boot-after-install" });
109
110 # For example to enter LUKS passphrase.
111 ${preBootCommands}
112
113 # Did /boot get mounted?
114 $machine->waitForUnit("local-fs.target");
115
116 ${if bootLoader == "grub" then
117 ''$machine->succeed("test -e /boot/grub");''
118 else
119 ''$machine->succeed("test -e /boot/loader/loader.conf");''
120 }
121
122 # Check whether /root has correct permissions.
123 $machine->succeed("stat -c '%a' /root") =~ /700/ or die;
124
125 # Did the swap device get activated?
126 # uncomment once https://bugs.freedesktop.org/show_bug.cgi?id=86930 is resolved
127 $machine->waitForUnit("swap.target");
128 $machine->succeed("cat /proc/swaps | grep -q /dev");
129
130 # Check that the store is in good shape
131 $machine->succeed("nix-store --verify --check-contents >&2");
132
133 # Check whether the channel works.
134 $machine->succeed("nix-env -iA nixos.procps >&2");
135 $machine->succeed("type -tP ps | tee /dev/stderr") =~ /.nix-profile/
136 or die "nix-env failed";
137
138 # Check that the daemon works, and that non-root users can run builds (this will build a new profile generation through the daemon)
139 $machine->succeed("su alice -l -c 'nix-env -iA nixos.procps' >&2");
140
141 # We need to a writable nix-store on next boot.
142 $machine->copyFileFromHost(
143 "${ makeConfig { inherit bootLoader grubVersion grubDevice grubIdentifier extraConfig; forceGrubReinstallCount = 1; } }",
144 "/etc/nixos/configuration.nix");
145
146 # Check whether nixos-rebuild works.
147 $machine->succeed("nixos-rebuild switch >&2");
148
149 # Test nixos-option.
150 $machine->succeed("nixos-option boot.initrd.kernelModules | grep virtio_console");
151 $machine->succeed("nixos-option boot.initrd.kernelModules | grep 'List of modules'");
152 $machine->succeed("nixos-option boot.initrd.kernelModules | grep qemu-guest.nix");
153
154 $machine->shutdown;
155
156 # Check whether a writable store build works
157 $machine = createMachine({ ${hdFlags} qemuFlags => "${qemuFlags}", name => "rebuild-switch" });
158 ${preBootCommands}
159 $machine->waitForUnit("multi-user.target");
160 $machine->copyFileFromHost(
161 "${ makeConfig { inherit bootLoader grubVersion grubDevice grubIdentifier extraConfig; forceGrubReinstallCount = 2; } }",
162 "/etc/nixos/configuration.nix");
163 $machine->succeed("nixos-rebuild boot >&2");
164 $machine->shutdown;
165
166 # And just to be sure, check that the machine still boots after
167 # "nixos-rebuild switch".
168 $machine = createMachine({ ${hdFlags} qemuFlags => "${qemuFlags}", "boot-after-rebuild-switch" });
169 ${preBootCommands}
170 $machine->waitForUnit("network.target");
171 $machine->shutdown;
172 '';
173
174
175 makeInstallerTest = name:
176 { createPartitions, preBootCommands ? "", extraConfig ? ""
177 , extraInstallerConfig ? {}
178 , bootLoader ? "grub" # either "grub" or "systemd-boot"
179 , grubVersion ? 2, grubDevice ? "/dev/vda", grubIdentifier ? "uuid"
180 , enableOCR ? false, meta ? {}
181 }:
182 makeTest {
183 inherit enableOCR;
184 name = "installer-" + name;
185 meta = with pkgs.stdenv.lib.maintainers; {
186 # put global maintainers here, individuals go into makeInstallerTest fkt call
187 maintainers = [ wkennington ] ++ (meta.maintainers or []);
188 };
189 nodes = {
190
191 # The configuration of the machine used to run "nixos-install". It
192 # also has a web server that simulates cache.nixos.org.
193 machine =
194 { config, lib, pkgs, ... }:
195
196 { imports =
197 [ ../modules/profiles/installation-device.nix
198 ../modules/profiles/base.nix
199 extraInstallerConfig
200 ];
201
202 virtualisation.diskSize = 8 * 1024;
203 virtualisation.memorySize = 1024;
204 virtualisation.writableStore = true;
205
206 # Use a small /dev/vdb as the root disk for the
207 # installer. This ensures the target disk (/dev/vda) is
208 # the same during and after installation.
209 virtualisation.emptyDiskImages = [ 512 ];
210 virtualisation.bootDevice =
211 if grubVersion == 1 then "/dev/sdb" else "/dev/vdb";
212 virtualisation.qemu.diskInterface =
213 if grubVersion == 1 then "scsi" else "virtio";
214
215 boot.loader.systemd-boot.enable = mkIf (bootLoader == "systemd-boot") true;
216
217 hardware.enableAllFirmware = mkForce false;
218
219 # The test cannot access the network, so any packages we
220 # need must be included in the VM.
221 system.extraDependencies = with pkgs;
222 [ sudo
223 libxml2.bin
224 libxslt.bin
225 docbook5
226 docbook5_xsl
227 unionfs-fuse
228 ntp
229 nixos-artwork.wallpapers.gnome-dark
230 perlPackages.XMLLibXML
231 perlPackages.ListCompare
232
233 # add curl so that rather than seeing the test attempt to download
234 # curl's tarball, we see what it's trying to download
235 curl
236 ]
237 ++ optional (bootLoader == "grub" && grubVersion == 1) pkgs.grub
238 ++ optionals (bootLoader == "grub" && grubVersion == 2) [ pkgs.grub2 pkgs.grub2_efi ];
239
240 nix.binaryCaches = mkForce [ ];
241 };
242
243 };
244
245 testScript = testScriptFun {
246 inherit bootLoader createPartitions preBootCommands
247 grubVersion grubDevice grubIdentifier extraConfig;
248 };
249 };
250
251
252in {
253
254 # !!! `parted mkpart' seems to silently create overlapping partitions.
255
256
257 # The (almost) simplest partitioning scheme: a swap partition and
258 # one big filesystem partition.
259 simple = makeInstallerTest "simple"
260 { createPartitions =
261 ''
262 $machine->succeed(
263 "parted /dev/vda mklabel msdos",
264 "parted /dev/vda -- mkpart primary linux-swap 1M 1024M",
265 "parted /dev/vda -- mkpart primary ext2 1024M -1s",
266 "udevadm settle",
267 "mkswap /dev/vda1 -L swap",
268 "swapon -L swap",
269 "mkfs.ext3 -L nixos /dev/vda2",
270 "mount LABEL=nixos /mnt",
271 );
272 '';
273 };
274
275 # Simple GPT/UEFI configuration using systemd-boot with 3 partitions: ESP, swap & root filesystem
276 simpleUefiSystemdBoot = makeInstallerTest "simpleUefiSystemdBoot"
277 { createPartitions =
278 ''
279 $machine->succeed(
280 "parted /dev/vda mklabel gpt",
281 "parted -s /dev/vda -- mkpart ESP fat32 1M 50MiB", # /boot
282 "parted -s /dev/vda -- set 1 boot on",
283 "parted -s /dev/vda -- mkpart primary linux-swap 50MiB 1024MiB",
284 "parted -s /dev/vda -- mkpart primary ext2 1024MiB -1MiB", # /
285 "udevadm settle",
286 "mkswap /dev/vda2 -L swap",
287 "swapon -L swap",
288 "mkfs.ext3 -L nixos /dev/vda3",
289 "mount LABEL=nixos /mnt",
290 "mkfs.vfat -n BOOT /dev/vda1",
291 "mkdir -p /mnt/boot",
292 "mount LABEL=BOOT /mnt/boot",
293 );
294 '';
295 bootLoader = "systemd-boot";
296 };
297
298 # Same as the previous, but now with a separate /boot partition.
299 separateBoot = makeInstallerTest "separateBoot"
300 { createPartitions =
301 ''
302 $machine->succeed(
303 "parted /dev/vda mklabel msdos",
304 "parted /dev/vda -- mkpart primary ext2 1M 50MB", # /boot
305 "parted /dev/vda -- mkpart primary linux-swap 50MB 1024M",
306 "parted /dev/vda -- mkpart primary ext2 1024M -1s", # /
307 "udevadm settle",
308 "mkswap /dev/vda2 -L swap",
309 "swapon -L swap",
310 "mkfs.ext3 -L nixos /dev/vda3",
311 "mount LABEL=nixos /mnt",
312 "mkfs.ext3 -L boot /dev/vda1",
313 "mkdir -p /mnt/boot",
314 "mount LABEL=boot /mnt/boot",
315 );
316 '';
317 };
318
319 # Same as the previous, but with fat32 /boot.
320 separateBootFat = makeInstallerTest "separateBootFat"
321 { createPartitions =
322 ''
323 $machine->succeed(
324 "parted /dev/vda mklabel msdos",
325 "parted /dev/vda -- mkpart primary ext2 1M 50MB", # /boot
326 "parted /dev/vda -- mkpart primary linux-swap 50MB 1024M",
327 "parted /dev/vda -- mkpart primary ext2 1024M -1s", # /
328 "udevadm settle",
329 "mkswap /dev/vda2 -L swap",
330 "swapon -L swap",
331 "mkfs.ext3 -L nixos /dev/vda3",
332 "mount LABEL=nixos /mnt",
333 "mkfs.vfat -n BOOT /dev/vda1",
334 "mkdir -p /mnt/boot",
335 "mount LABEL=BOOT /mnt/boot",
336 );
337 '';
338 };
339
340 # zfs on / with swap
341 zfsroot = makeInstallerTest "zfs-root"
342 {
343 extraInstallerConfig = {
344 boot.supportedFilesystems = [ "zfs" ];
345 };
346
347 extraConfig = ''
348 boot.supportedFilesystems = [ "zfs" ];
349
350 # Using by-uuid overrides the default of by-id, and is unique
351 # to the qemu disks, as they don't produce by-id paths for
352 # some reason.
353 boot.zfs.devNodes = "/dev/disk/by-uuid/";
354 networking.hostId = "00000000";
355 '';
356
357 createPartitions =
358 ''
359 $machine->succeed(
360 "parted /dev/vda mklabel msdos",
361 "parted /dev/vda -- mkpart primary linux-swap 1M 1024M",
362 "parted /dev/vda -- mkpart primary 1024M -1s",
363 "udevadm settle",
364
365 "mkswap /dev/vda1 -L swap",
366 "swapon -L swap",
367
368 "zpool create rpool /dev/vda2",
369 "zfs create -o mountpoint=legacy rpool/root",
370 "mount -t zfs rpool/root /mnt",
371
372 "udevadm settle"
373 );
374 '';
375 };
376
377 # Create two physical LVM partitions combined into one volume group
378 # that contains the logical swap and root partitions.
379 lvm = makeInstallerTest "lvm"
380 { createPartitions =
381 ''
382 $machine->succeed(
383 "parted /dev/vda mklabel msdos",
384 "parted /dev/vda -- mkpart primary 1M 2048M", # PV1
385 "parted /dev/vda -- set 1 lvm on",
386 "parted /dev/vda -- mkpart primary 2048M -1s", # PV2
387 "parted /dev/vda -- set 2 lvm on",
388 "udevadm settle",
389 "pvcreate /dev/vda1 /dev/vda2",
390 "vgcreate MyVolGroup /dev/vda1 /dev/vda2",
391 "lvcreate --size 1G --name swap MyVolGroup",
392 "lvcreate --size 2G --name nixos MyVolGroup",
393 "mkswap -f /dev/MyVolGroup/swap -L swap",
394 "swapon -L swap",
395 "mkfs.xfs -L nixos /dev/MyVolGroup/nixos",
396 "mount LABEL=nixos /mnt",
397 );
398 '';
399 };
400
401 # Boot off an encrypted root partition
402 luksroot = makeInstallerTest "luksroot"
403 { createPartitions = ''
404 $machine->succeed(
405 "parted /dev/vda mklabel msdos",
406 "parted /dev/vda -- mkpart primary ext2 1M 50MB", # /boot
407 "parted /dev/vda -- mkpart primary linux-swap 50M 1024M",
408 "parted /dev/vda -- mkpart primary 1024M -1s", # LUKS
409 "udevadm settle",
410 "mkswap /dev/vda2 -L swap",
411 "swapon -L swap",
412 "modprobe dm_mod dm_crypt",
413 "echo -n supersecret | cryptsetup luksFormat -q /dev/vda3 -",
414 "echo -n supersecret | cryptsetup luksOpen --key-file - /dev/vda3 cryptroot",
415 "mkfs.ext3 -L nixos /dev/mapper/cryptroot",
416 "mount LABEL=nixos /mnt",
417 "mkfs.ext3 -L boot /dev/vda1",
418 "mkdir -p /mnt/boot",
419 "mount LABEL=boot /mnt/boot",
420 );
421 '';
422 extraConfig = ''
423 boot.kernelParams = lib.mkAfter [ "console=tty0" ];
424 '';
425 enableOCR = true;
426 preBootCommands = ''
427 $machine->start;
428 $machine->waitForText(qr/Enter passphrase/);
429 $machine->sendChars("supersecret\n");
430 '';
431 };
432
433 swraid = makeInstallerTest "swraid"
434 { createPartitions =
435 ''
436 $machine->succeed(
437 "parted /dev/vda --"
438 . " mklabel msdos"
439 . " mkpart primary ext2 1M 100MB" # /boot
440 . " mkpart extended 100M -1s"
441 . " mkpart logical 102M 1602M" # md0 (root), first device
442 . " mkpart logical 1603M 3103M" # md0 (root), second device
443 . " mkpart logical 3104M 3360M" # md1 (swap), first device
444 . " mkpart logical 3361M 3617M", # md1 (swap), second device
445 "udevadm settle",
446 "ls -l /dev/vda* >&2",
447 "cat /proc/partitions >&2",
448 "mdadm --create --force /dev/md0 --metadata 1.2 --level=raid1 --raid-devices=2 /dev/vda5 /dev/vda6",
449 "mdadm --create --force /dev/md1 --metadata 1.2 --level=raid1 --raid-devices=2 /dev/vda7 /dev/vda8",
450 "udevadm settle",
451 "mkswap -f /dev/md1 -L swap",
452 "swapon -L swap",
453 "mkfs.ext3 -L nixos /dev/md0",
454 "mount LABEL=nixos /mnt",
455 "mkfs.ext3 -L boot /dev/vda1",
456 "mkdir /mnt/boot",
457 "mount LABEL=boot /mnt/boot",
458 "udevadm settle",
459 );
460 '';
461 preBootCommands = ''
462 $machine->start;
463 $machine->fail("dmesg | grep 'immediate safe mode'");
464 '';
465 };
466
467 # Test a basic install using GRUB 1.
468 grub1 = makeInstallerTest "grub1"
469 { createPartitions =
470 ''
471 $machine->succeed(
472 "parted /dev/sda mklabel msdos",
473 "parted /dev/sda -- mkpart primary linux-swap 1M 1024M",
474 "parted /dev/sda -- mkpart primary ext2 1024M -1s",
475 "udevadm settle",
476 "mkswap /dev/sda1 -L swap",
477 "swapon -L swap",
478 "mkfs.ext3 -L nixos /dev/sda2",
479 "mount LABEL=nixos /mnt",
480 );
481 '';
482 grubVersion = 1;
483 grubDevice = "/dev/sda";
484 };
485
486 # Test using labels to identify volumes in grub
487 simpleLabels = makeInstallerTest "simpleLabels" {
488 createPartitions = ''
489 $machine->succeed(
490 "sgdisk -Z /dev/vda",
491 "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",
492 "mkswap /dev/vda2 -L swap",
493 "swapon -L swap",
494 "mkfs.ext4 -L root /dev/vda3",
495 "mount LABEL=root /mnt",
496 );
497 '';
498 grubIdentifier = "label";
499 };
500
501 # Test using the provided disk name within grub
502 # TODO: Fix udev so the symlinks are unneeded in /dev/disks
503 simpleProvided = makeInstallerTest "simpleProvided" {
504 createPartitions = ''
505 my $UUID = "\$(blkid -s UUID -o value /dev/vda2)";
506 $machine->succeed(
507 "sgdisk -Z /dev/vda",
508 "sgdisk -n 1:0:+1M -n 2:0:+100M -n 3:0:+1G -N 4 -t 1:ef02 -t 2:8300 -t 3:8200 -t 4:8300 -c 2:boot -c 4:root /dev/vda",
509 "mkswap /dev/vda3 -L swap",
510 "swapon -L swap",
511 "mkfs.ext4 -L boot /dev/vda2",
512 "mkfs.ext4 -L root /dev/vda4",
513 );
514 $machine->execute("ln -s ../../vda2 /dev/disk/by-uuid/$UUID");
515 $machine->execute("ln -s ../../vda4 /dev/disk/by-label/root");
516 $machine->succeed(
517 "mount /dev/disk/by-label/root /mnt",
518 "mkdir /mnt/boot",
519 "mount /dev/disk/by-uuid/$UUID /mnt/boot"
520 );
521 '';
522 grubIdentifier = "provided";
523 };
524
525 # Simple btrfs grub testing
526 btrfsSimple = makeInstallerTest "btrfsSimple" {
527 createPartitions = ''
528 $machine->succeed(
529 "sgdisk -Z /dev/vda",
530 "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",
531 "mkswap /dev/vda2 -L swap",
532 "swapon -L swap",
533 "mkfs.btrfs -L root /dev/vda3",
534 "mount LABEL=root /mnt",
535 );
536 '';
537 };
538
539 # Test to see if we can detect /boot and /nix on subvolumes
540 btrfsSubvols = makeInstallerTest "btrfsSubvols" {
541 createPartitions = ''
542 $machine->succeed(
543 "sgdisk -Z /dev/vda",
544 "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",
545 "mkswap /dev/vda2 -L swap",
546 "swapon -L swap",
547 "mkfs.btrfs -L root /dev/vda3",
548 "btrfs device scan",
549 "mount LABEL=root /mnt",
550 "btrfs subvol create /mnt/boot",
551 "btrfs subvol create /mnt/nixos",
552 "btrfs subvol create /mnt/nixos/default",
553 "umount /mnt",
554 "mount -o defaults,subvol=nixos/default LABEL=root /mnt",
555 "mkdir /mnt/boot",
556 "mount -o defaults,subvol=boot LABEL=root /mnt/boot",
557 );
558 '';
559 };
560
561 # Test to see if we can detect default and aux subvolumes correctly
562 btrfsSubvolDefault = makeInstallerTest "btrfsSubvolDefault" {
563 createPartitions = ''
564 $machine->succeed(
565 "sgdisk -Z /dev/vda",
566 "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",
567 "mkswap /dev/vda2 -L swap",
568 "swapon -L swap",
569 "mkfs.btrfs -L root /dev/vda3",
570 "btrfs device scan",
571 "mount LABEL=root /mnt",
572 "btrfs subvol create /mnt/badpath",
573 "btrfs subvol create /mnt/badpath/boot",
574 "btrfs subvol create /mnt/nixos",
575 "btrfs subvol set-default \$(btrfs subvol list /mnt | grep 'nixos' | awk '{print \$2}') /mnt",
576 "umount /mnt",
577 "mount -o defaults LABEL=root /mnt",
578 "mkdir -p /mnt/badpath/boot", # Help ensure the detection mechanism is actually looking up subvolumes
579 "mkdir /mnt/boot",
580 "mount -o defaults,subvol=badpath/boot LABEL=root /mnt/boot",
581 );
582 '';
583 };
584
585}