1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 cfg = config.boot.loader.limine;
9 efi = config.boot.loader.efi;
10 limineInstallConfig = pkgs.writeText "limine-install.json" (
11 builtins.toJSON {
12 nixPath = config.nix.package;
13 efiBootMgrPath = pkgs.efibootmgr;
14 liminePath = cfg.package;
15 efiMountPoint = efi.efiSysMountPoint;
16 fileSystems = config.fileSystems;
17 luksDevices = builtins.attrNames config.boot.initrd.luks.devices;
18 canTouchEfiVariables = efi.canTouchEfiVariables;
19 efiSupport = cfg.efiSupport;
20 efiRemovable = cfg.efiInstallAsRemovable;
21 secureBoot = cfg.secureBoot;
22 biosSupport = cfg.biosSupport;
23 biosDevice = cfg.biosDevice;
24 partitionIndex = cfg.partitionIndex;
25 forceMbr = cfg.forceMbr;
26 enrollConfig = cfg.enrollConfig;
27 style = cfg.style;
28 maxGenerations = if cfg.maxGenerations == null then 0 else cfg.maxGenerations;
29 hostArchitecture = pkgs.stdenv.hostPlatform.parsed.cpu;
30 timeout = if config.boot.loader.timeout != null then config.boot.loader.timeout else 10;
31 enableEditor = cfg.enableEditor;
32 extraConfig = cfg.extraConfig;
33 extraEntries = cfg.extraEntries;
34 additionalFiles = cfg.additionalFiles;
35 validateChecksums = cfg.validateChecksums;
36 panicOnChecksumMismatch = cfg.panicOnChecksumMismatch;
37 }
38 );
39 defaultWallpaper = pkgs.nixos-artwork.wallpapers.simple-dark-gray-bootloader.gnomeFilePath;
40in
41{
42 meta = {
43 inherit (pkgs.limine.meta) maintainers;
44 };
45
46 options.boot.loader.limine = {
47 enable = lib.mkEnableOption "the Limine Bootloader";
48 package = lib.mkPackageOption pkgs "limine" { };
49
50 enableEditor = lib.mkEnableOption null // {
51 description = ''
52 Whether to allow editing the boot entries before booting them.
53 It is recommended to set this to false, as it allows gaining root
54 access by passing `init=/bin/sh` as a kernel parameter.
55 '';
56 };
57
58 maxGenerations = lib.mkOption {
59 default = null;
60 example = 50;
61 type = lib.types.nullOr lib.types.int;
62 description = ''
63 Maximum number of latest generations in the boot menu.
64 Useful to prevent boot partition of running out of disk space.
65 `null` means no limit i.e. all generations that were not
66 garbage collected yet.
67 '';
68 };
69
70 extraConfig = lib.mkOption {
71 default = "";
72 type = lib.types.lines;
73 example = lib.literalExpression ''
74 serial: yes
75 '';
76 description = ''
77 A string which is prepended to limine.conf. The config format can be found [here](https://github.com/limine-bootloader/limine/blob/trunk/CONFIG.md).
78 '';
79 };
80
81 extraEntries = lib.mkOption {
82 default = "";
83 type = lib.types.lines;
84 example = lib.literalExpression ''
85 /memtest86
86 protocol: chainload
87 path: boot():///efi/memtest86/memtest86.efi
88 '';
89 description = ''
90 A string which is appended to the end of limine.conf. The config format can be found [here](https://github.com/limine-bootloader/limine/blob/trunk/CONFIG.md).
91 '';
92 };
93
94 additionalFiles = lib.mkOption {
95 default = { };
96 type = lib.types.attrsOf lib.types.path;
97 example = lib.literalExpression ''
98 { "efi/memtest86/memtest86.efi" = "''${pkgs.memtest86-efi}/BOOTX64.efi"; }
99 '';
100 description = ''
101 A set of files to be copied to {file}`/boot`. Each attribute name denotes the
102 destination file name in {file}`/boot`, while the corresponding attribute value
103 specifies the source file.
104 '';
105 };
106
107 validateChecksums = lib.mkEnableOption null // {
108 default = true;
109 description = ''
110 Whether to validate file checksums before booting.
111 '';
112 };
113
114 panicOnChecksumMismatch = lib.mkEnableOption null // {
115 description = ''
116 Whether or not checksum validation failure should be a fatal
117 error at boot time.
118 '';
119 };
120
121 efiSupport = lib.mkEnableOption null // {
122 default = pkgs.stdenv.hostPlatform.isEfi;
123 defaultText = lib.literalExpression "pkgs.stdenv.hostPlatform.isEfi";
124 description = ''
125 Whether or not to install the limine EFI files.
126 '';
127 };
128
129 efiInstallAsRemovable = lib.mkEnableOption null // {
130 default = !efi.canTouchEfiVariables;
131 defaultText = lib.literalExpression "!config.boot.loader.efi.canTouchEfiVariables";
132 description = ''
133 Whether or not to install the limine EFI files as removable.
134
135 See {option}`boot.loader.grub.efiInstallAsRemovable`
136 '';
137 };
138
139 biosSupport = lib.mkEnableOption null // {
140 default = !cfg.efiSupport && pkgs.stdenv.hostPlatform.isx86;
141 defaultText = lib.literalExpression "!config.boot.loader.limine.efiSupport && pkgs.stdenv.hostPlatform.isx86";
142 description = ''
143 Whether or not to install limine for BIOS.
144 '';
145 };
146
147 biosDevice = lib.mkOption {
148 default = "nodev";
149 type = lib.types.str;
150 description = ''
151 Device to install the BIOS version of limine on.
152 '';
153 };
154
155 partitionIndex = lib.mkOption {
156 default = null;
157 type = lib.types.nullOr lib.types.int;
158 description = ''
159 The 1-based index of the dedicated partition for limine's second stage.
160 '';
161 };
162
163 enrollConfig = lib.mkEnableOption null // {
164 default = cfg.panicOnChecksumMismatch;
165 defaultText = lib.literalExpression "boot.loader.limine.panicOnChecksumMismatch";
166 description = ''
167 Whether or not to enroll the config.
168 Only works on EFI!
169 '';
170 };
171
172 forceMbr = lib.mkEnableOption null // {
173 description = ''
174 Force MBR detection to work even if the safety checks fail, use absolutely only if necessary!
175 '';
176 };
177
178 secureBoot = {
179 enable = lib.mkEnableOption null // {
180 description = ''
181 Whether to use sign the limine binary with sbctl.
182
183 ::: {.note}
184 This requires you to already have generated the keys and enrolled them with {command}`sbctl`.
185
186 To create keys use {command}`sbctl create-keys`.
187
188 To enroll them first reset secure boot to "Setup Mode". This is device specific.
189 Then enroll them using {command}`sbctl enroll-keys -m -f`.
190
191 You can now rebuild your system with this option enabled.
192
193 Afterwards turn setup mode off and enable secure boot.
194 :::
195 '';
196 };
197
198 createAndEnrollKeys = lib.mkEnableOption null // {
199 internal = true;
200 description = ''
201 Creates secure boot signing keys and enrolls them during bootloader installation.
202
203 ::: {.note}
204 This is used for automated nixos tests.
205 NOT INTENDED to be used on a real system.
206 :::
207 '';
208 };
209
210 sbctl = lib.mkPackageOption pkgs "sbctl" { };
211 };
212
213 style = {
214 wallpapers = lib.mkOption {
215 default = [ ];
216 example = lib.literalExpression "[ pkgs.nixos-artwork.wallpapers.simple-dark-gray-bootloader.gnomeFilePath ]";
217 type = lib.types.listOf lib.types.path;
218 description = ''
219 A list of wallpapers.
220 If more than one is specified, a random one will be selected at boot.
221 '';
222 };
223
224 wallpaperStyle = lib.mkOption {
225 default = "streched";
226 type = lib.types.enum [
227 "centered"
228 "streched"
229 "tiled"
230 ];
231 description = ''
232 How the wallpaper should be fit to the screen.
233 '';
234 };
235
236 backdrop = lib.mkOption {
237 default = null;
238 example = "7EBAE4";
239 type = lib.types.nullOr lib.types.str;
240 description = ''
241 Color to fill the rest of the screen with when wallpaper_style is centered in RRGGBB format.
242 '';
243 };
244
245 interface = {
246 resolution = lib.mkOption {
247 default = null;
248 type = lib.types.nullOr lib.types.str;
249 description = ''
250 The resolution of the interface.
251 '';
252 };
253
254 branding = lib.mkOption {
255 default = null;
256 type = lib.types.nullOr lib.types.str;
257 description = ''
258 The title at the top of the screen.
259 '';
260 };
261
262 brandingColor = lib.mkOption {
263 default = null;
264 type = lib.types.nullOr lib.types.int;
265 description = ''
266 Color index of the title at the top of the screen in the range of 0-7 (Limine defaults to 6 (cyan)).
267 '';
268 };
269
270 helpHidden = lib.mkEnableOption null // {
271 description = ''
272 Whether or not to hide the keybinds at the top of the screen.
273 '';
274 };
275 };
276 graphicalTerminal = {
277 font = {
278 scale = lib.mkOption {
279 default = null;
280 example = lib.literalExpression "2x2";
281 type = lib.types.nullOr lib.types.str;
282 description = ''
283 The scale of the font in the format <width>x<height>.
284 '';
285 };
286
287 spacing = lib.mkOption {
288 default = null;
289 type = lib.types.nullOr lib.types.int;
290 description = ''
291 The horizontal spacing between characters in pixels.
292 '';
293 };
294 };
295
296 palette = lib.mkOption {
297 default = null;
298 type = lib.types.nullOr lib.types.str;
299 description = ''
300 A ; seperated array of 8 colors in the format RRGGBB:
301 black, red, green, brown, blue, magenta, cyan, and gray.
302 '';
303 };
304
305 brightPalette = lib.mkOption {
306 default = null;
307 type = lib.types.nullOr lib.types.str;
308 description = ''
309 A ; seperated array of 8 colors in the format RRGGBB:
310 dark gray, bright red, bright green, yellow, bright blue, bright magenta, bright cyan, and white.
311 '';
312 };
313
314 foreground = lib.mkOption {
315 default = null;
316 type = lib.types.nullOr lib.types.str;
317 description = ''
318 Text foreground color (RRGGBB).
319 '';
320 };
321
322 background = lib.mkOption {
323 default = null;
324 type = lib.types.nullOr lib.types.str;
325 description = ''
326 Text background color (TTRRGGBB). TT is transparency.
327 '';
328 };
329
330 brightForeground = lib.mkOption {
331 default = null;
332 type = lib.types.nullOr lib.types.str;
333 description = ''
334 Text foreground bright color (RRGGBB).
335 '';
336 };
337
338 brightBackground = lib.mkOption {
339 default = null;
340 type = lib.types.nullOr lib.types.str;
341 description = ''
342 Text background bright color (RRGGBB).
343 '';
344 };
345
346 margin = lib.mkOption {
347 default = null;
348 type = lib.types.nullOr lib.types.int;
349 description = ''
350 The amount of margin around the terminal.
351 '';
352 };
353
354 marginGradient = lib.mkOption {
355 default = null;
356 type = lib.types.nullOr lib.types.int;
357 description = ''
358 The thickness in pixels for the margin around the terminal.
359 '';
360 };
361 };
362 };
363 };
364
365 config = lib.mkMerge [
366 {
367 boot.loader.limine.style.wallpapers = lib.mkDefault [ defaultWallpaper ];
368 }
369 (lib.mkIf (cfg.style.wallpapers == [ defaultWallpaper ]) {
370 boot.loader.limine.style.backdrop = lib.mkDefault "2F302F";
371 boot.loader.limine.style.wallpaperStyle = lib.mkDefault "streched";
372 })
373 (lib.mkIf cfg.enable {
374 assertions = [
375 {
376 assertion =
377 pkgs.stdenv.hostPlatform.isx86_64
378 || pkgs.stdenv.hostPlatform.isi686
379 || pkgs.stdenv.hostPlatform.isAarch64;
380 message = "Limine can only be installed on aarch64 & x86 platforms";
381 }
382 {
383 assertion = cfg.efiSupport || cfg.biosSupport;
384 message = "Both UEFI support and BIOS support for Limine are disabled, this will result in an unbootable system";
385 }
386 ];
387
388 boot.loader.grub.enable = lib.mkDefault false;
389
390 boot.loader.supportsInitrdSecrets = true;
391
392 system = {
393 boot.loader.id = "limine";
394 build.installBootLoader = pkgs.replaceVarsWith {
395 src = ./limine-install.py;
396 isExecutable = true;
397 replacements = {
398 python3 = pkgs.python3.withPackages (python-packages: [ python-packages.psutil ]);
399 configPath = limineInstallConfig;
400 };
401 };
402 };
403 })
404 (lib.mkIf (cfg.enable && cfg.secureBoot.enable) {
405 assertions = [
406 {
407 assertion = cfg.enrollConfig;
408 message = "Disabling enrollConfig allows bypassing secure boot.";
409 }
410 {
411 assertion = cfg.validateChecksums;
412 message = "Disabling validateChecksums allows bypassing secure boot.";
413 }
414 {
415 assertion = cfg.panicOnChecksumMismatch;
416 message = "Disabling panicOnChecksumMismatch allows bypassing secure boot.";
417 }
418 {
419 assertion = cfg.efiSupport;
420 message = "Secure boot is only supported on EFI systems.";
421 }
422 ];
423
424 boot.loader.limine.enrollConfig = true;
425 boot.loader.limine.validateChecksums = true;
426 boot.loader.limine.panicOnChecksumMismatch = true;
427 })
428
429 # Fwupd binary needs to be signed in secure boot mode
430 (lib.mkIf (cfg.enable && cfg.secureBoot.enable && config.services.fwupd.enable) {
431 systemd.services.fwupd = {
432 environment.FWUPD_EFIAPPDIR = "/run/fwupd-efi";
433 };
434
435 systemd.services.fwupd-efi = {
436 description = "Sign fwupd EFI app for secure boot";
437 wantedBy = [ "fwupd.service" ];
438 partOf = [ "fwupd.service" ];
439 before = [ "fwupd.service" ];
440
441 unitConfig.ConditionPathIsDirectory = "/var/lib/sbctl";
442 serviceConfig = {
443 Type = "oneshot";
444 RemainAfterExit = true;
445 RuntimeDirectory = "fwupd-efi";
446 };
447
448 script = ''
449 cp ${config.services.fwupd.package.fwupd-efi}/libexec/fwupd/efi/fwupd*.efi /run/fwupd-efi/
450 chmod +w /run/fwupd-efi/fwupd*.efi
451 ${lib.getExe cfg.secureBoot.sbctl} sign /run/fwupd-efi/fwupd*.efi
452 '';
453 };
454
455 services.fwupd.uefiCapsuleSettings = {
456 DisableShimForSecureBoot = true;
457 };
458 })
459 ];
460}