1{ system ? builtins.currentSystem,
2 config ? {},
3 pkgs ? import ../.. { inherit system config; }
4}:
5
6with import ../lib/testing-python.nix { inherit system pkgs; };
7with pkgs.lib;
8
9let
10 common = {
11 virtualisation.useBootLoader = true;
12 virtualisation.useEFIBoot = true;
13 boot.loader.systemd-boot.enable = true;
14 boot.loader.efi.canTouchEfiVariables = true;
15 environment.systemPackages = [ pkgs.efibootmgr ];
16 };
17in
18{
19 basic = makeTest {
20 name = "systemd-boot";
21 meta.maintainers = with pkgs.lib.maintainers; [ danielfullmer julienmalka ];
22
23 nodes.machine = common;
24
25 testScript = ''
26 machine.start()
27 machine.wait_for_unit("multi-user.target")
28
29 machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf")
30
31 # Ensure we actually booted using systemd-boot
32 # Magic number is the vendor UUID used by systemd-boot.
33 machine.succeed(
34 "test -e /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"
35 )
36
37 # "bootctl install" should have created an EFI entry
38 machine.succeed('efibootmgr | grep "Linux Boot Manager"')
39 '';
40 };
41
42 # Check that specialisations create corresponding boot entries.
43 specialisation = makeTest {
44 name = "systemd-boot-specialisation";
45 meta.maintainers = with pkgs.lib.maintainers; [ lukegb julienmalka ];
46
47 nodes.machine = { pkgs, lib, ... }: {
48 imports = [ common ];
49 specialisation.something.configuration = {};
50 };
51
52 testScript = ''
53 machine.start()
54 machine.wait_for_unit("multi-user.target")
55
56 machine.succeed(
57 "test -e /boot/loader/entries/nixos-generation-1-specialisation-something.conf"
58 )
59 machine.succeed(
60 "grep -q 'title NixOS (something)' /boot/loader/entries/nixos-generation-1-specialisation-something.conf"
61 )
62 '';
63 };
64
65 # Boot without having created an EFI entry--instead using default "/EFI/BOOT/BOOTX64.EFI"
66 fallback = makeTest {
67 name = "systemd-boot-fallback";
68 meta.maintainers = with pkgs.lib.maintainers; [ danielfullmer julienmalka ];
69
70 nodes.machine = { pkgs, lib, ... }: {
71 imports = [ common ];
72 boot.loader.efi.canTouchEfiVariables = mkForce false;
73 };
74
75 testScript = ''
76 machine.start()
77 machine.wait_for_unit("multi-user.target")
78
79 machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf")
80
81 # Ensure we actually booted using systemd-boot
82 # Magic number is the vendor UUID used by systemd-boot.
83 machine.succeed(
84 "test -e /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"
85 )
86
87 # "bootctl install" should _not_ have created an EFI entry
88 machine.fail('efibootmgr | grep "Linux Boot Manager"')
89 '';
90 };
91
92 update = makeTest {
93 name = "systemd-boot-update";
94 meta.maintainers = with pkgs.lib.maintainers; [ danielfullmer julienmalka ];
95
96 nodes.machine = common;
97
98 testScript = ''
99 machine.succeed("mount -o remount,rw /boot")
100
101 # Replace version inside sd-boot with something older. See magic[] string in systemd src/boot/efi/boot.c
102 machine.succeed(
103 """
104 find /boot -iname '*boot*.efi' -print0 | \
105 xargs -0 -I '{}' sed -i 's/#### LoaderInfo: systemd-boot .* ####/#### LoaderInfo: systemd-boot 000.0-1-notnixos ####/' '{}'
106 """
107 )
108
109 output = machine.succeed("/run/current-system/bin/switch-to-configuration boot")
110 assert "updating systemd-boot from 000.0-1-notnixos to " in output, "Couldn't find systemd-boot update message"
111 '';
112 };
113
114 memtest86 = makeTest {
115 name = "systemd-boot-memtest86";
116 meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
117
118 nodes.machine = { pkgs, lib, ... }: {
119 imports = [ common ];
120 boot.loader.systemd-boot.memtest86.enable = true;
121 };
122
123 testScript = ''
124 machine.succeed("test -e /boot/loader/entries/memtest86.conf")
125 machine.succeed("test -e /boot/efi/memtest86/memtest.efi")
126 '';
127 };
128
129 netbootxyz = makeTest {
130 name = "systemd-boot-netbootxyz";
131 meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
132
133 nodes.machine = { pkgs, lib, ... }: {
134 imports = [ common ];
135 boot.loader.systemd-boot.netbootxyz.enable = true;
136 };
137
138 testScript = ''
139 machine.succeed("test -e /boot/loader/entries/o_netbootxyz.conf")
140 machine.succeed("test -e /boot/efi/netbootxyz/netboot.xyz.efi")
141 '';
142 };
143
144 entryFilename = makeTest {
145 name = "systemd-boot-entry-filename";
146 meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
147
148 nodes.machine = { pkgs, lib, ... }: {
149 imports = [ common ];
150 boot.loader.systemd-boot.memtest86.enable = true;
151 boot.loader.systemd-boot.memtest86.entryFilename = "apple.conf";
152 };
153
154 testScript = ''
155 machine.fail("test -e /boot/loader/entries/memtest86.conf")
156 machine.succeed("test -e /boot/loader/entries/apple.conf")
157 machine.succeed("test -e /boot/efi/memtest86/memtest.efi")
158 '';
159 };
160
161 extraEntries = makeTest {
162 name = "systemd-boot-extra-entries";
163 meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
164
165 nodes.machine = { pkgs, lib, ... }: {
166 imports = [ common ];
167 boot.loader.systemd-boot.extraEntries = {
168 "banana.conf" = ''
169 title banana
170 '';
171 };
172 };
173
174 testScript = ''
175 machine.succeed("test -e /boot/loader/entries/banana.conf")
176 machine.succeed("test -e /boot/efi/nixos/.extra-files/loader/entries/banana.conf")
177 '';
178 };
179
180 extraFiles = makeTest {
181 name = "systemd-boot-extra-files";
182 meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
183
184 nodes.machine = { pkgs, lib, ... }: {
185 imports = [ common ];
186 boot.loader.systemd-boot.extraFiles = {
187 "efi/fruits/tomato.efi" = pkgs.netbootxyz-efi;
188 };
189 };
190
191 testScript = ''
192 machine.succeed("test -e /boot/efi/fruits/tomato.efi")
193 machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi")
194 '';
195 };
196
197 switch-test = makeTest {
198 name = "systemd-boot-switch-test";
199 meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
200
201 nodes = {
202 inherit common;
203
204 machine = { pkgs, nodes, ... }: {
205 imports = [ common ];
206 boot.loader.systemd-boot.extraFiles = {
207 "efi/fruits/tomato.efi" = pkgs.netbootxyz-efi;
208 };
209
210 # These are configs for different nodes, but we'll use them here in `machine`
211 system.extraDependencies = [
212 nodes.common.system.build.toplevel
213 nodes.with_netbootxyz.system.build.toplevel
214 ];
215 };
216
217 with_netbootxyz = { pkgs, ... }: {
218 imports = [ common ];
219 boot.loader.systemd-boot.netbootxyz.enable = true;
220 };
221 };
222
223 testScript = { nodes, ... }: let
224 originalSystem = nodes.machine.system.build.toplevel;
225 baseSystem = nodes.common.system.build.toplevel;
226 finalSystem = nodes.with_netbootxyz.system.build.toplevel;
227 in ''
228 machine.succeed("test -e /boot/efi/fruits/tomato.efi")
229 machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi")
230
231 with subtest("remove files when no longer needed"):
232 machine.succeed("${baseSystem}/bin/switch-to-configuration boot")
233 machine.fail("test -e /boot/efi/fruits/tomato.efi")
234 machine.fail("test -d /boot/efi/fruits")
235 machine.succeed("test -d /boot/efi/nixos/.extra-files")
236 machine.fail("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi")
237 machine.fail("test -d /boot/efi/nixos/.extra-files/efi/fruits")
238
239 with subtest("files are added back when needed again"):
240 machine.succeed("${originalSystem}/bin/switch-to-configuration boot")
241 machine.succeed("test -e /boot/efi/fruits/tomato.efi")
242 machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi")
243
244 with subtest("simultaneously removing and adding files works"):
245 machine.succeed("${finalSystem}/bin/switch-to-configuration boot")
246 machine.fail("test -e /boot/efi/fruits/tomato.efi")
247 machine.fail("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi")
248 machine.succeed("test -e /boot/loader/entries/o_netbootxyz.conf")
249 machine.succeed("test -e /boot/efi/netbootxyz/netboot.xyz.efi")
250 machine.succeed("test -e /boot/efi/nixos/.extra-files/loader/entries/o_netbootxyz.conf")
251 machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/netbootxyz/netboot.xyz.efi")
252 '';
253 };
254
255 garbage-collect-entry = makeTest {
256 name = "systemd-boot-switch-test";
257 meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
258
259 nodes = {
260 inherit common;
261 machine = { pkgs, nodes, ... }: {
262 imports = [ common ];
263
264 # These are configs for different nodes, but we'll use them here in `machine`
265 system.extraDependencies = [
266 nodes.common.system.build.toplevel
267 ];
268 };
269 };
270
271 testScript = { nodes, ... }:
272 let
273 baseSystem = nodes.common.system.build.toplevel;
274 in
275 ''
276 machine.succeed("nix-env -p /nix/var/nix/profiles/system --set ${baseSystem}")
277 machine.succeed("nix-env -p /nix/var/nix/profiles/system --delete-generations 1")
278 machine.succeed("${baseSystem}/bin/switch-to-configuration boot")
279 machine.fail("test -e /boot/loader/entries/nixos-generation-1.conf")
280 machine.succeed("test -e /boot/loader/entries/nixos-generation-2.conf")
281 '';
282 };
283
284 # Some UEFI firmwares fail on large reads. Now that systemd-boot loads initrd
285 # itself, systems with such firmware won't boot without this fix
286 uefiLargeFileWorkaround = makeTest {
287 name = "uefi-large-file-workaround";
288 meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
289 nodes.machine = { pkgs, ... }: {
290 imports = [common];
291 virtualisation.efi.OVMF = pkgs.OVMF.overrideAttrs (old: {
292 # This patch deliberately breaks the FAT driver in EDK2 to
293 # exhibit (part of) the firmware bug that we are testing
294 # for. Files greater than 10MiB will fail to be read in a
295 # single Read() call, so systemd-boot will fail to load the
296 # initrd without a workaround. The number 10MiB was chosen
297 # because if it were smaller than the kernel size, even the
298 # LoadImage call would fail, which is not the failure mode
299 # we're testing for. It needs to be between the kernel size
300 # and the initrd size.
301 patches = old.patches or [] ++ [ ./systemd-boot-ovmf-broken-fat-driver.patch ];
302 });
303 };
304
305 testScript = ''
306 machine.wait_for_unit("multi-user.target")
307 '';
308 };
309
310 no-bootspec = makeTest
311 {
312 name = "systemd-boot-no-bootspec";
313 meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
314
315 nodes.machine = {
316 imports = [ common ];
317 boot.bootspec.enable = false;
318 };
319
320 testScript = ''
321 machine.start()
322 machine.wait_for_unit("multi-user.target")
323 '';
324 };
325}