Release NixOS 23.11
[NixPkgs.git] / nixos / tests / systemd-boot.nix
blob256a18532b0a212acab9132bdc4c89e6da8106d2
1 { system ? builtins.currentSystem,
2   config ? {},
3   pkgs ? import ../.. { inherit system config; }
4 }:
6 with import ../lib/testing-python.nix { inherit system pkgs; };
7 with pkgs.lib;
9 let
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   };
19   basic = makeTest {
20     name = "systemd-boot";
21     meta.maintainers = with pkgs.lib.maintainers; [ danielfullmer julienmalka ];
23     nodes.machine = common;
25     testScript = ''
26       machine.start()
27       machine.wait_for_unit("multi-user.target")
29       machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf")
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       )
37       # "bootctl install" should have created an EFI entry
38       machine.succeed('efibootmgr | grep "Linux Boot Manager"')
39     '';
40   };
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 ];
47     nodes.machine = { pkgs, lib, ... }: {
48       imports = [ common ];
49       specialisation.something.configuration = {};
50     };
52     testScript = ''
53       machine.start()
54       machine.wait_for_unit("multi-user.target")
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   };
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 ];
70     nodes.machine = { pkgs, lib, ... }: {
71       imports = [ common ];
72       boot.loader.efi.canTouchEfiVariables = mkForce false;
73     };
75     testScript = ''
76       machine.start()
77       machine.wait_for_unit("multi-user.target")
79       machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf")
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       )
87       # "bootctl install" should _not_ have created an EFI entry
88       machine.fail('efibootmgr | grep "Linux Boot Manager"')
89     '';
90   };
92   update = makeTest {
93     name = "systemd-boot-update";
94     meta.maintainers = with pkgs.lib.maintainers; [ danielfullmer julienmalka ];
96     nodes.machine = common;
98     testScript = ''
99       machine.succeed("mount -o remount,rw /boot")
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       )
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   };
114   memtest86 = makeTest {
115     name = "systemd-boot-memtest86";
116     meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
118     nodes.machine = { pkgs, lib, ... }: {
119       imports = [ common ];
120       boot.loader.systemd-boot.memtest86.enable = true;
121     };
123     testScript = ''
124       machine.succeed("test -e /boot/loader/entries/memtest86.conf")
125       machine.succeed("test -e /boot/efi/memtest86/memtest.efi")
126     '';
127   };
129   netbootxyz = makeTest {
130     name = "systemd-boot-netbootxyz";
131     meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
133     nodes.machine = { pkgs, lib, ... }: {
134       imports = [ common ];
135       boot.loader.systemd-boot.netbootxyz.enable = true;
136     };
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   };
144   entryFilename = makeTest {
145     name = "systemd-boot-entry-filename";
146     meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
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     };
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   };
161   extraEntries = makeTest {
162     name = "systemd-boot-extra-entries";
163     meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
165     nodes.machine = { pkgs, lib, ... }: {
166       imports = [ common ];
167       boot.loader.systemd-boot.extraEntries = {
168         "banana.conf" = ''
169           title banana
170         '';
171       };
172     };
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   };
180   extraFiles = makeTest {
181     name = "systemd-boot-extra-files";
182     meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
184     nodes.machine = { pkgs, lib, ... }: {
185       imports = [ common ];
186       boot.loader.systemd-boot.extraFiles = {
187         "efi/fruits/tomato.efi" = pkgs.netbootxyz-efi;
188       };
189     };
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   };
197   switch-test = makeTest {
198     name = "systemd-boot-switch-test";
199     meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
201     nodes = {
202       inherit common;
204       machine = { pkgs, nodes, ... }: {
205         imports = [ common ];
206         boot.loader.systemd-boot.extraFiles = {
207           "efi/fruits/tomato.efi" = pkgs.netbootxyz-efi;
208         };
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       };
217       with_netbootxyz = { pkgs, ... }: {
218         imports = [ common ];
219         boot.loader.systemd-boot.netbootxyz.enable = true;
220       };
221     };
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")
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")
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")
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   };
255   garbage-collect-entry = makeTest {
256     name = "systemd-boot-switch-test";
257     meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
259     nodes = {
260       inherit common;
261       machine = { pkgs, nodes, ... }: {
262         imports = [ common ];
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     };
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   };
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     };
305     testScript = ''
306       machine.wait_for_unit("multi-user.target")
307     '';
308   };
310   no-bootspec = makeTest
311     {
312       name = "systemd-boot-no-bootspec";
313       meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
315       nodes.machine = {
316         imports = [ common ];
317         boot.bootspec.enable = false;
318       };
320       testScript = ''
321         machine.start()
322         machine.wait_for_unit("multi-user.target")
323       '';
324     };