1{
2 system ? builtins.currentSystem,
3 config ? { },
4 pkgs ? import ../.. { inherit system config; },
5}:
6
7with import ../lib/testing-python.nix { inherit system pkgs; };
8with pkgs.lib;
9
10let
11 # A testScript fragment that prepares a disk with some empty, unpartitioned
12 # space. and uses it to boot the test with.
13 # Takes two arguments, `machine` from which the diskImage is extracted,
14 # as well an optional `sizeDiff` (defaulting to +32M), describing how should
15 # be resized.
16 useDiskImage =
17 {
18 machine,
19 sizeDiff ? "+32M",
20 }:
21 ''
22 import os
23 import shutil
24 import subprocess
25 import tempfile
26
27 tmp_disk_image = tempfile.NamedTemporaryFile()
28
29 shutil.copyfile("${machine.system.build.diskImage}/nixos.img", tmp_disk_image.name)
30
31 subprocess.run([
32 "${machine.virtualisation.qemu.package}/bin/qemu-img",
33 "resize",
34 "-f",
35 "raw",
36 tmp_disk_image.name,
37 "${sizeDiff}",
38 ])
39
40 # Set NIX_DISK_IMAGE so that the qemu script finds the right disk image.
41 os.environ['NIX_DISK_IMAGE'] = tmp_disk_image.name
42 '';
43
44 common =
45 {
46 config,
47 pkgs,
48 lib,
49 ...
50 }:
51 {
52 virtualisation.useDefaultFilesystems = false;
53 virtualisation.fileSystems = {
54 "/" = {
55 device = "/dev/vda2";
56 fsType = "ext4";
57 };
58 };
59
60 # systemd-repart operates on disks with a partition table. The qemu module,
61 # however, creates separate filesystem images without a partition table, so
62 # we have to create a disk image manually.
63 #
64 # This creates two partitions, an ESP available as /dev/vda1 and the root
65 # partition available as /dev/vda2.
66 system.build.diskImage = import ../lib/make-disk-image.nix {
67 inherit config pkgs lib;
68 # Use a raw format disk so that it can be resized before starting the
69 # test VM.
70 format = "raw";
71 # Keep the image as small as possible but leave some room for changes.
72 bootSize = "32M";
73 additionalSpace = "0M";
74 # GPT with an EFI System Partition is the typical use case for
75 # systemd-repart because it does not support MBR.
76 partitionTableType = "efi";
77 # We do not actually care much about the content of the partitions, so we
78 # do not need a bootloader installed.
79 installBootLoader = false;
80 # Improve determinism by not copying a channel.
81 copyChannel = false;
82 };
83 };
84in
85{
86 basic = makeTest {
87 name = "systemd-repart";
88 meta.maintainers = with maintainers; [ nikstur ];
89
90 nodes.machine =
91 { config, pkgs, ... }:
92 {
93 imports = [ common ];
94
95 boot.initrd.systemd.enable = true;
96
97 boot.initrd.systemd.repart.enable = true;
98 systemd.repart.partitions = {
99 "10-root" = {
100 Type = "linux-generic";
101 };
102 };
103 };
104
105 testScript =
106 { nodes, ... }:
107 ''
108 ${useDiskImage { inherit (nodes) machine; }}
109
110 machine.start()
111 machine.wait_for_unit("multi-user.target")
112
113 systemd_repart_logs = machine.succeed("journalctl --boot --unit systemd-repart.service")
114 assert "Growing existing partition 1." in systemd_repart_logs
115 '';
116 };
117
118 encrypt-tpm2 = makeTest {
119 name = "systemd-repart-encrypt-tpm2";
120 meta.maintainers = with maintainers; [ flokli ];
121
122 nodes.machine =
123 {
124 config,
125 pkgs,
126 lib,
127 ...
128 }:
129 {
130 imports = [ common ];
131
132 boot.initrd.systemd.enable = true;
133
134 boot.initrd.availableKernelModules = [ "dm_crypt" ];
135 boot.initrd.luks.devices = lib.mkVMOverride {
136 created-crypt = {
137 device = "/dev/disk/by-partlabel/created-crypt";
138 crypttabExtraOpts = [ "tpm2-device=auto" ];
139 };
140 };
141 boot.initrd.systemd.repart.enable = true;
142 boot.initrd.systemd.repart.extraArgs = [
143 "--tpm2-pcrs=7"
144 ];
145 systemd.repart.partitions = {
146 "10-root" = {
147 Type = "linux-generic";
148 };
149 "10-crypt" = {
150 Type = "var";
151 Label = "created-crypt";
152 Format = "ext4";
153 Encrypt = "tpm2";
154 };
155 };
156 virtualisation.tpm.enable = true;
157 virtualisation.fileSystems = {
158 "/var" = {
159 device = "/dev/mapper/created-crypt";
160 fsType = "ext4";
161 };
162 };
163 };
164
165 testScript =
166 { nodes, ... }:
167 ''
168 ${useDiskImage {
169 inherit (nodes) machine;
170 sizeDiff = "+100M";
171 }}
172
173 machine.start()
174 machine.wait_for_unit("multi-user.target")
175
176 systemd_repart_logs = machine.succeed("journalctl --boot --unit systemd-repart.service")
177 assert "Encrypting future partition 2" in systemd_repart_logs
178
179 assert "/dev/mapper/created-crypt" in machine.succeed("mount")
180 '';
181 };
182
183 after-initrd = makeTest {
184 name = "systemd-repart-after-initrd";
185 meta.maintainers = with maintainers; [ nikstur ];
186
187 nodes.machine =
188 { config, pkgs, ... }:
189 {
190 imports = [ common ];
191
192 systemd.repart.enable = true;
193 systemd.repart.partitions = {
194 "10-root" = {
195 Type = "linux-generic";
196 };
197 };
198 };
199
200 testScript =
201 { nodes, ... }:
202 ''
203 ${useDiskImage { inherit (nodes) machine; }}
204
205 machine.start()
206 machine.wait_for_unit("multi-user.target")
207
208 systemd_repart_logs = machine.succeed("journalctl --unit systemd-repart.service")
209 assert "Growing existing partition 1." in systemd_repart_logs
210 '';
211 };
212
213 create-root = makeTest {
214 name = "systemd-repart-create-root";
215 meta.maintainers = with maintainers; [ nikstur ];
216
217 nodes.machine =
218 {
219 config,
220 lib,
221 pkgs,
222 ...
223 }:
224 {
225 virtualisation.useDefaultFilesystems = false;
226 virtualisation.mountHostNixStore = false;
227 virtualisation.fileSystems = {
228 "/" = {
229 device = "/dev/disk/by-partlabel/created-root";
230 fsType = "ext4";
231 };
232 "/nix/store" = {
233 device = "/dev/vda2";
234 fsType = "ext4";
235 };
236 };
237
238 # Create an image containing only the Nix store. This enables creating
239 # the root partition with systemd-repart and then successfully booting
240 # into a working system.
241 #
242 # This creates two partitions, an ESP available as /dev/vda1 and the Nix
243 # store available as /dev/vda2.
244 system.build.diskImage = import ../lib/make-disk-image.nix {
245 inherit config pkgs lib;
246 onlyNixStore = true;
247 format = "raw";
248 bootSize = "32M";
249 additionalSpace = "0M";
250 partitionTableType = "efi";
251 installBootLoader = false;
252 copyChannel = false;
253 };
254
255 boot.initrd.systemd.enable = true;
256
257 boot.initrd.systemd.repart.enable = true;
258 boot.initrd.systemd.repart.device = "/dev/vda";
259 systemd.repart.partitions = {
260 "10-root" = {
261 Type = "root";
262 Label = "created-root";
263 Format = "ext4";
264 };
265 };
266 };
267
268 testScript =
269 { nodes, ... }:
270 ''
271 ${useDiskImage { inherit (nodes) machine; }}
272
273 machine.start()
274 machine.wait_for_unit("multi-user.target")
275
276 systemd_repart_logs = machine.succeed("journalctl --boot --unit systemd-repart.service")
277 assert "Adding new partition 2 to partition table." in systemd_repart_logs
278 '';
279 };
280}