1{ config, options, lib, pkgs, ... }:
2
3let
4 inherit (lib)
5 all
6 concatMap
7 concatMapStrings
8 concatStrings
9 concatStringsSep
10 escapeShellArg
11 flip
12 foldr
13 forEach
14 hasPrefix
15 mapAttrsToList
16 literalExpression
17 makeBinPath
18 mkDefault
19 mkIf
20 mkMerge
21 mkOption
22 mkRemovedOptionModule
23 mkRenamedOptionModule
24 optional
25 optionals
26 optionalString
27 replaceStrings
28 types
29 ;
30
31 cfg = config.boot.loader.grub;
32
33 efi = config.boot.loader.efi;
34
35 grubPkgs =
36 # Package set of targeted architecture
37 if cfg.forcei686 then pkgs.pkgsi686Linux else pkgs;
38
39 realGrub = if cfg.zfsSupport then grubPkgs.grub2.override { zfsSupport = true; }
40 else grubPkgs.grub2;
41
42 grub =
43 # Don't include GRUB if we're only generating a GRUB menu (e.g.,
44 # in EC2 instances).
45 if cfg.devices == ["nodev"]
46 then null
47 else realGrub;
48
49 grubEfi =
50 if cfg.efiSupport
51 then realGrub.override { efiSupport = cfg.efiSupport; }
52 else null;
53
54 f = x: optionalString (x != null) ("" + x);
55
56 grubConfig = args:
57 let
58 efiSysMountPoint = if args.efiSysMountPoint == null then args.path else args.efiSysMountPoint;
59 efiSysMountPoint' = replaceStrings [ "/" ] [ "-" ] efiSysMountPoint;
60 in
61 pkgs.writeText "grub-config.xml" (builtins.toXML
62 { splashImage = f cfg.splashImage;
63 splashMode = f cfg.splashMode;
64 backgroundColor = f cfg.backgroundColor;
65 entryOptions = f cfg.entryOptions;
66 subEntryOptions = f cfg.subEntryOptions;
67 # PC platforms (like x86_64-linux) have a non-EFI target (`grubTarget`), but other platforms
68 # (like aarch64-linux) have an undefined `grubTarget`. Avoid providing the path to a non-EFI
69 # GRUB on those platforms.
70 grub = f (if (grub.grubTarget or "") != "" then grub else "");
71 grubTarget = f (grub.grubTarget or "");
72 shell = "${pkgs.runtimeShell}";
73 fullName = lib.getName realGrub;
74 fullVersion = lib.getVersion realGrub;
75 grubEfi = f grubEfi;
76 grubTargetEfi = optionalString cfg.efiSupport (f (grubEfi.grubTarget or ""));
77 bootPath = args.path;
78 storePath = config.boot.loader.grub.storePath;
79 bootloaderId = if args.efiBootloaderId == null then "${config.system.nixos.distroName}${efiSysMountPoint'}" else args.efiBootloaderId;
80 timeout = if config.boot.loader.timeout == null then -1 else config.boot.loader.timeout;
81 theme = f cfg.theme;
82 inherit efiSysMountPoint;
83 inherit (args) devices;
84 inherit (efi) canTouchEfiVariables;
85 inherit (cfg)
86 extraConfig extraPerEntryConfig extraEntries forceInstall useOSProber
87 extraGrubInstallArgs
88 extraEntriesBeforeNixOS extraPrepareConfig configurationLimit copyKernels
89 default fsIdentifier efiSupport efiInstallAsRemovable gfxmodeEfi gfxmodeBios gfxpayloadEfi gfxpayloadBios
90 users
91 timeoutStyle
92 ;
93 path = with pkgs; makeBinPath (
94 [ coreutils gnused gnugrep findutils diffutils btrfs-progs util-linux mdadm ]
95 ++ optional cfg.efiSupport efibootmgr
96 ++ optionals cfg.useOSProber [ busybox os-prober ]);
97 font = lib.optionalString (cfg.font != null) (
98 if lib.last (lib.splitString "." cfg.font) == "pf2"
99 then cfg.font
100 else "${convertedFont}");
101 });
102
103 bootDeviceCounters = foldr (device: attr: attr // { ${device} = (attr.${device} or 0) + 1; }) {}
104 (concatMap (args: args.devices) cfg.mirroredBoots);
105
106 convertedFont = (pkgs.runCommand "grub-font-converted.pf2" {}
107 (builtins.concatStringsSep " "
108 ([ "${realGrub}/bin/grub-mkfont"
109 cfg.font
110 "--output" "$out"
111 ] ++ (optional (cfg.fontSize!=null) "--size ${toString cfg.fontSize}")))
112 );
113
114 defaultSplash = pkgs.nixos-artwork.wallpapers.simple-dark-gray-bootloader.gnomeFilePath;
115in
116
117{
118
119 ###### interface
120
121 options = {
122
123 boot.loader.grub = {
124
125 enable = mkOption {
126 default = !config.boot.isContainer;
127 defaultText = literalExpression "!config.boot.isContainer";
128 type = types.bool;
129 description = lib.mdDoc ''
130 Whether to enable the GNU GRUB boot loader.
131 '';
132 };
133
134 version = mkOption {
135 visible = false;
136 type = types.int;
137 };
138
139 device = mkOption {
140 default = "";
141 example = "/dev/disk/by-id/wwn-0x500001234567890a";
142 type = types.str;
143 description = lib.mdDoc ''
144 The device on which the GRUB boot loader will be installed.
145 The special value `nodev` means that a GRUB
146 boot menu will be generated, but GRUB itself will not
147 actually be installed. To install GRUB on multiple devices,
148 use `boot.loader.grub.devices`.
149 '';
150 };
151
152 devices = mkOption {
153 default = [];
154 example = [ "/dev/disk/by-id/wwn-0x500001234567890a" ];
155 type = types.listOf types.str;
156 description = lib.mdDoc ''
157 The devices on which the boot loader, GRUB, will be
158 installed. Can be used instead of `device` to
159 install GRUB onto multiple devices.
160 '';
161 };
162
163 users = mkOption {
164 default = {};
165 example = {
166 root = { hashedPasswordFile = "/path/to/file"; };
167 };
168 description = lib.mdDoc ''
169 User accounts for GRUB. When specified, the GRUB command line and
170 all boot options except the default are password-protected.
171 All passwords and hashes provided will be stored in /boot/grub/grub.cfg,
172 and will be visible to any local user who can read this file. Additionally,
173 any passwords and hashes provided directly in a Nix configuration
174 (as opposed to external files) will be copied into the Nix store, and
175 will be visible to all local users.
176 '';
177 type = types.attrsOf (types.submodule {
178 options = {
179 hashedPasswordFile = mkOption {
180 example = "/path/to/file";
181 default = null;
182 type = with types; uniq (nullOr str);
183 description = lib.mdDoc ''
184 Specifies the path to a file containing the password hash
185 for the account, generated with grub-mkpasswd-pbkdf2.
186 This hash will be stored in /boot/grub/grub.cfg, and will
187 be visible to any local user who can read this file.
188 '';
189 };
190 hashedPassword = mkOption {
191 example = "grub.pbkdf2.sha512.10000.674DFFDEF76E13EA...2CC972B102CF4355";
192 default = null;
193 type = with types; uniq (nullOr str);
194 description = lib.mdDoc ''
195 Specifies the password hash for the account,
196 generated with grub-mkpasswd-pbkdf2.
197 This hash will be copied to the Nix store, and will be visible to all local users.
198 '';
199 };
200 passwordFile = mkOption {
201 example = "/path/to/file";
202 default = null;
203 type = with types; uniq (nullOr str);
204 description = lib.mdDoc ''
205 Specifies the path to a file containing the
206 clear text password for the account.
207 This password will be stored in /boot/grub/grub.cfg, and will
208 be visible to any local user who can read this file.
209 '';
210 };
211 password = mkOption {
212 example = "Pa$$w0rd!";
213 default = null;
214 type = with types; uniq (nullOr str);
215 description = lib.mdDoc ''
216 Specifies the clear text password for the account.
217 This password will be copied to the Nix store, and will be visible to all local users.
218 '';
219 };
220 };
221 });
222 };
223
224 mirroredBoots = mkOption {
225 default = [ ];
226 example = [
227 { path = "/boot1"; devices = [ "/dev/disk/by-id/wwn-0x500001234567890a" ]; }
228 { path = "/boot2"; devices = [ "/dev/disk/by-id/wwn-0x500009876543210a" ]; }
229 ];
230 description = lib.mdDoc ''
231 Mirror the boot configuration to multiple partitions and install grub
232 to the respective devices corresponding to those partitions.
233 '';
234
235 type = with types; listOf (submodule {
236 options = {
237
238 path = mkOption {
239 example = "/boot1";
240 type = types.str;
241 description = lib.mdDoc ''
242 The path to the boot directory where GRUB will be written. Generally
243 this boot path should double as an EFI path.
244 '';
245 };
246
247 efiSysMountPoint = mkOption {
248 default = null;
249 example = "/boot1/efi";
250 type = types.nullOr types.str;
251 description = lib.mdDoc ''
252 The path to the efi system mount point. Usually this is the same
253 partition as the above path and can be left as null.
254 '';
255 };
256
257 efiBootloaderId = mkOption {
258 default = null;
259 example = "NixOS-fsid";
260 type = types.nullOr types.str;
261 description = lib.mdDoc ''
262 The id of the bootloader to store in efi nvram.
263 The default is to name it NixOS and append the path or efiSysMountPoint.
264 This is only used if `boot.loader.efi.canTouchEfiVariables` is true.
265 '';
266 };
267
268 devices = mkOption {
269 default = [ ];
270 example = [ "/dev/disk/by-id/wwn-0x500001234567890a" "/dev/disk/by-id/wwn-0x500009876543210a" ];
271 type = types.listOf types.str;
272 description = lib.mdDoc ''
273 The path to the devices which will have the GRUB MBR written.
274 Note these are typically device paths and not paths to partitions.
275 '';
276 };
277
278 };
279 });
280 };
281
282 configurationName = mkOption {
283 default = "";
284 example = "Stable 2.6.21";
285 type = types.str;
286 description = lib.mdDoc ''
287 GRUB entry name instead of default.
288 '';
289 };
290
291 storePath = mkOption {
292 default = "/nix/store";
293 type = types.str;
294 description = lib.mdDoc ''
295 Path to the Nix store when looking for kernels at boot.
296 Only makes sense when copyKernels is false.
297 '';
298 };
299
300 extraPrepareConfig = mkOption {
301 default = "";
302 type = types.lines;
303 description = lib.mdDoc ''
304 Additional bash commands to be run at the script that
305 prepares the GRUB menu entries.
306 '';
307 };
308
309 extraConfig = mkOption {
310 default = "";
311 example = ''
312 serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1
313 terminal_input --append serial
314 terminal_output --append serial
315 '';
316 type = types.lines;
317 description = lib.mdDoc ''
318 Additional GRUB commands inserted in the configuration file
319 just before the menu entries.
320 '';
321 };
322
323 extraGrubInstallArgs = mkOption {
324 default = [ ];
325 example = [ "--modules=nativedisk ahci pata part_gpt part_msdos diskfilter mdraid1x lvm ext2" ];
326 type = types.listOf types.str;
327 description = lib.mdDoc ''
328 Additional arguments passed to `grub-install`.
329
330 A use case for this is to build specific GRUB2 modules
331 directly into the GRUB2 kernel image, so that they are available
332 and activated even in the `grub rescue` shell.
333
334 They are also necessary when the BIOS/UEFI is bugged and cannot
335 correctly read large disks (e.g. above 2 TB), so GRUB2's own
336 `nativedisk` and related modules can be used
337 to use its own disk drivers. The example shows one such case.
338 This is also useful for booting from USB.
339 See the
340 [
341 GRUB source code
342 ](https://git.savannah.gnu.org/cgit/grub.git/tree/grub-core/commands/nativedisk.c?h=grub-2.04#n326)
343 for which disk modules are available.
344
345 The list elements are passed directly as `argv`
346 arguments to the `grub-install` program, in order.
347 '';
348 };
349
350 extraInstallCommands = mkOption {
351 default = "";
352 example = ''
353 # the example below generates detached signatures that GRUB can verify
354 # https://www.gnu.org/software/grub/manual/grub/grub.html#Using-digital-signatures
355 ''${pkgs.findutils}/bin/find /boot -not -path "/boot/efi/*" -type f -name '*.sig' -delete
356 old_gpg_home=$GNUPGHOME
357 export GNUPGHOME="$(mktemp -d)"
358 ''${pkgs.gnupg}/bin/gpg --import ''${priv_key} > /dev/null 2>&1
359 ''${pkgs.findutils}/bin/find /boot -not -path "/boot/efi/*" -type f -exec ''${pkgs.gnupg}/bin/gpg --detach-sign "{}" \; > /dev/null 2>&1
360 rm -rf $GNUPGHOME
361 export GNUPGHOME=$old_gpg_home
362 '';
363 type = types.lines;
364 description = lib.mdDoc ''
365 Additional shell commands inserted in the bootloader installer
366 script after generating menu entries.
367 '';
368 };
369
370 extraPerEntryConfig = mkOption {
371 default = "";
372 example = "root (hd0)";
373 type = types.lines;
374 description = lib.mdDoc ''
375 Additional GRUB commands inserted in the configuration file
376 at the start of each NixOS menu entry.
377 '';
378 };
379
380 extraEntries = mkOption {
381 default = "";
382 type = types.lines;
383 example = ''
384 # GRUB 2 example
385 menuentry "Windows 7" {
386 chainloader (hd0,4)+1
387 }
388
389 # GRUB 2 with UEFI example, chainloading another distro
390 menuentry "Fedora" {
391 set root=(hd1,1)
392 chainloader /efi/fedora/grubx64.efi
393 }
394 '';
395 description = lib.mdDoc ''
396 Any additional entries you want added to the GRUB boot menu.
397 '';
398 };
399
400 extraEntriesBeforeNixOS = mkOption {
401 default = false;
402 type = types.bool;
403 description = lib.mdDoc ''
404 Whether extraEntries are included before the default option.
405 '';
406 };
407
408 extraFiles = mkOption {
409 type = types.attrsOf types.path;
410 default = {};
411 example = literalExpression ''
412 { "memtest.bin" = "''${pkgs.memtest86plus}/memtest.bin"; }
413 '';
414 description = lib.mdDoc ''
415 A set of files to be copied to {file}`/boot`.
416 Each attribute name denotes the destination file name in
417 {file}`/boot`, while the corresponding
418 attribute value specifies the source file.
419 '';
420 };
421
422 useOSProber = mkOption {
423 default = false;
424 type = types.bool;
425 description = lib.mdDoc ''
426 If set to true, append entries for other OSs detected by os-prober.
427 '';
428 };
429
430 splashImage = mkOption {
431 type = types.nullOr types.path;
432 example = literalExpression "./my-background.png";
433 description = lib.mdDoc ''
434 Background image used for GRUB.
435 Set to `null` to run GRUB in text mode.
436
437 ::: {.note}
438 File must be one of .png, .tga, .jpg, or .jpeg. JPEG images must
439 not be progressive.
440 The image will be scaled if necessary to fit the screen.
441 :::
442 '';
443 };
444
445 backgroundColor = mkOption {
446 type = types.nullOr types.str;
447 example = "#7EBAE4";
448 default = null;
449 description = lib.mdDoc ''
450 Background color to be used for GRUB to fill the areas the image isn't filling.
451 '';
452 };
453
454 timeoutStyle = mkOption {
455 default = "menu";
456 type = types.enum [ "menu" "countdown" "hidden" ];
457 description = lib.mdDoc ''
458 - `menu` shows the menu.
459 - `countdown` uses a text-mode countdown.
460 - `hidden` hides GRUB entirely.
461
462 When using a theme, the default value (`menu`) is appropriate for the graphical countdown.
463
464 When attempting to do flicker-free boot, `hidden` should be used.
465
466 See the [GRUB documentation section about `timeout_style`](https://www.gnu.org/software/grub/manual/grub/html_node/timeout.html).
467
468 ::: {.note}
469 If this option is set to ‘countdown’ or ‘hidden’ [...] and ESC or F4 are pressed, or SHIFT is held down during that time, it will display the menu and wait for input.
470 :::
471
472 From: [Simple configuration handling page, under GRUB_TIMEOUT_STYLE](https://www.gnu.org/software/grub/manual/grub/html_node/Simple-configuration.html).
473 '';
474 };
475
476 entryOptions = mkOption {
477 default = "--class nixos --unrestricted";
478 type = types.nullOr types.str;
479 description = lib.mdDoc ''
480 Options applied to the primary NixOS menu entry.
481 '';
482 };
483
484 subEntryOptions = mkOption {
485 default = "--class nixos";
486 type = types.nullOr types.str;
487 description = lib.mdDoc ''
488 Options applied to the secondary NixOS submenu entry.
489 '';
490 };
491
492 theme = mkOption {
493 type = types.nullOr types.path;
494 example = literalExpression "pkgs.nixos-grub2-theme";
495 default = null;
496 description = lib.mdDoc ''
497 Grub theme to be used.
498 '';
499 };
500
501 splashMode = mkOption {
502 type = types.enum [ "normal" "stretch" ];
503 default = "stretch";
504 description = lib.mdDoc ''
505 Whether to stretch the image or show the image in the top-left corner unstretched.
506 '';
507 };
508
509 font = mkOption {
510 type = types.nullOr types.path;
511 default = "${realGrub}/share/grub/unicode.pf2";
512 defaultText = literalExpression ''"''${pkgs.grub2}/share/grub/unicode.pf2"'';
513 description = lib.mdDoc ''
514 Path to a TrueType, OpenType, or pf2 font to be used by Grub.
515 '';
516 };
517
518 fontSize = mkOption {
519 type = types.nullOr types.int;
520 example = 16;
521 default = null;
522 description = lib.mdDoc ''
523 Font size for the grub menu. Ignored unless `font`
524 is set to a ttf or otf font.
525 '';
526 };
527
528 gfxmodeEfi = mkOption {
529 default = "auto";
530 example = "1024x768";
531 type = types.str;
532 description = lib.mdDoc ''
533 The gfxmode to pass to GRUB when loading a graphical boot interface under EFI.
534 '';
535 };
536
537 gfxmodeBios = mkOption {
538 default = "1024x768";
539 example = "auto";
540 type = types.str;
541 description = lib.mdDoc ''
542 The gfxmode to pass to GRUB when loading a graphical boot interface under BIOS.
543 '';
544 };
545
546 gfxpayloadEfi = mkOption {
547 default = "keep";
548 example = "text";
549 type = types.str;
550 description = lib.mdDoc ''
551 The gfxpayload to pass to GRUB when loading a graphical boot interface under EFI.
552 '';
553 };
554
555 gfxpayloadBios = mkOption {
556 default = "text";
557 example = "keep";
558 type = types.str;
559 description = lib.mdDoc ''
560 The gfxpayload to pass to GRUB when loading a graphical boot interface under BIOS.
561 '';
562 };
563
564 configurationLimit = mkOption {
565 default = 100;
566 example = 120;
567 type = types.int;
568 description = lib.mdDoc ''
569 Maximum of configurations in boot menu. GRUB has problems when
570 there are too many entries.
571 '';
572 };
573
574 copyKernels = mkOption {
575 default = false;
576 type = types.bool;
577 description = lib.mdDoc ''
578 Whether the GRUB menu builder should copy kernels and initial
579 ramdisks to /boot. This is done automatically if /boot is
580 on a different partition than /.
581 '';
582 };
583
584 default = mkOption {
585 default = "0";
586 type = types.either types.int types.str;
587 apply = toString;
588 description = lib.mdDoc ''
589 Index of the default menu item to be booted.
590 Can also be set to "saved", which will make GRUB select
591 the menu item that was used at the last boot.
592 '';
593 };
594
595 fsIdentifier = mkOption {
596 default = "uuid";
597 type = types.enum [ "uuid" "label" "provided" ];
598 description = lib.mdDoc ''
599 Determines how GRUB will identify devices when generating the
600 configuration file. A value of uuid / label signifies that grub
601 will always resolve the uuid or label of the device before using
602 it in the configuration. A value of provided means that GRUB will
603 use the device name as show in {command}`df` or
604 {command}`mount`. Note, zfs zpools / datasets are ignored
605 and will always be mounted using their labels.
606 '';
607 };
608
609 zfsSupport = mkOption {
610 default = false;
611 type = types.bool;
612 description = lib.mdDoc ''
613 Whether GRUB should be built against libzfs.
614 '';
615 };
616
617 efiSupport = mkOption {
618 default = false;
619 type = types.bool;
620 description = lib.mdDoc ''
621 Whether GRUB should be built with EFI support.
622 '';
623 };
624
625 efiInstallAsRemovable = mkOption {
626 default = false;
627 type = types.bool;
628 description = lib.mdDoc ''
629 Whether to invoke `grub-install` with
630 `--removable`.
631
632 Unless you turn this on, GRUB will install itself somewhere in
633 `boot.loader.efi.efiSysMountPoint` (exactly where
634 depends on other config variables). If you've set
635 `boot.loader.efi.canTouchEfiVariables` *AND* you
636 are currently booted in UEFI mode, then GRUB will use
637 `efibootmgr` to modify the boot order in the
638 EFI variables of your firmware to include this location. If you are
639 *not* booted in UEFI mode at the time GRUB is being installed, the
640 NVRAM will not be modified, and your system will not find GRUB at
641 boot time. However, GRUB will still return success so you may miss
642 the warning that gets printed ("`efibootmgr: EFI variables
643 are not supported on this system.`").
644
645 If you turn this feature on, GRUB will install itself in a
646 special location within `efiSysMountPoint` (namely
647 `EFI/boot/boot$arch.efi`) which the firmwares
648 are hardcoded to try first, regardless of NVRAM EFI variables.
649
650 To summarize, turn this on if:
651 - You are installing NixOS and want it to boot in UEFI mode,
652 but you are currently booted in legacy mode
653 - You want to make a drive that will boot regardless of
654 the NVRAM state of the computer (like a USB "removable" drive)
655 - You simply dislike the idea of depending on NVRAM
656 state to make your drive bootable
657 '';
658 };
659
660 enableCryptodisk = mkOption {
661 default = false;
662 type = types.bool;
663 description = lib.mdDoc ''
664 Enable support for encrypted partitions. GRUB should automatically
665 unlock the correct encrypted partition and look for filesystems.
666 '';
667 };
668
669 forceInstall = mkOption {
670 default = false;
671 type = types.bool;
672 description = lib.mdDoc ''
673 Whether to try and forcibly install GRUB even if problems are
674 detected. It is not recommended to enable this unless you know what
675 you are doing.
676 '';
677 };
678
679 forcei686 = mkOption {
680 default = false;
681 type = types.bool;
682 description = lib.mdDoc ''
683 Whether to force the use of a ia32 boot loader on x64 systems. Required
684 to install and run NixOS on 64bit x86 systems with 32bit (U)EFI.
685 '';
686 };
687
688 };
689
690 };
691
692
693 ###### implementation
694
695 config = mkMerge [
696
697 { boot.loader.grub.splashImage = mkDefault defaultSplash; }
698
699 (mkIf (cfg.splashImage == defaultSplash) {
700 boot.loader.grub.backgroundColor = mkDefault "#2F302F";
701 boot.loader.grub.splashMode = mkDefault "normal";
702 })
703
704 (mkIf cfg.enable {
705
706 boot.loader.grub.devices = optional (cfg.device != "") cfg.device;
707
708 boot.loader.grub.mirroredBoots = optionals (cfg.devices != [ ]) [
709 { path = "/boot"; inherit (cfg) devices; inherit (efi) efiSysMountPoint; }
710 ];
711
712 boot.loader.supportsInitrdSecrets = true;
713
714 system.systemBuilderArgs.configurationName = cfg.configurationName;
715 system.systemBuilderCommands = ''
716 echo -n "$configurationName" > $out/configuration-name
717 '';
718
719 system.build.installBootLoader =
720 let
721 install-grub-pl = pkgs.substituteAll {
722 src = ./install-grub.pl;
723 utillinux = pkgs.util-linux;
724 btrfsprogs = pkgs.btrfs-progs;
725 inherit (config.system.nixos) distroName;
726 };
727 perl = pkgs.perl.withPackages (p: with p; [
728 FileSlurp FileCopyRecursive
729 XMLLibXML XMLSAX XMLSAXBase
730 ListCompare JSON
731 ]);
732 in pkgs.writeScript "install-grub.sh" (''
733 #!${pkgs.runtimeShell}
734 set -e
735 ${optionalString cfg.enableCryptodisk "export GRUB_ENABLE_CRYPTODISK=y"}
736 '' + flip concatMapStrings cfg.mirroredBoots (args: ''
737 ${perl}/bin/perl ${install-grub-pl} ${grubConfig args} $@
738 '') + cfg.extraInstallCommands);
739
740 system.build.grub = grub;
741
742 # Common attribute for boot loaders so only one of them can be
743 # set at once.
744 system.boot.loader.id = "grub";
745
746 environment.systemPackages = optional (grub != null) grub;
747
748 boot.loader.grub.extraPrepareConfig =
749 concatStrings (mapAttrsToList (n: v: ''
750 ${pkgs.coreutils}/bin/install -Dp "${v}" "${efi.efiSysMountPoint}/"${escapeShellArg n}
751 '') config.boot.loader.grub.extraFiles);
752
753 assertions = [
754 {
755 assertion = cfg.mirroredBoots != [ ];
756 message = "You must set the option ‘boot.loader.grub.devices’ or "
757 + "'boot.loader.grub.mirroredBoots' to make the system bootable.";
758 }
759 {
760 assertion = cfg.efiSupport || all (c: c < 2) (mapAttrsToList (n: c: if n == "nodev" then 0 else c) bootDeviceCounters);
761 message = "You cannot have duplicated devices in mirroredBoots";
762 }
763 {
764 assertion = cfg.efiInstallAsRemovable -> cfg.efiSupport;
765 message = "If you wish to to use boot.loader.grub.efiInstallAsRemovable, then turn on boot.loader.grub.efiSupport";
766 }
767 {
768 assertion = cfg.efiInstallAsRemovable -> !config.boot.loader.efi.canTouchEfiVariables;
769 message = "If you wish to to use boot.loader.grub.efiInstallAsRemovable, then turn off boot.loader.efi.canTouchEfiVariables";
770 }
771 {
772 assertion = !(options.boot.loader.grub.version.isDefined && cfg.version == 1);
773 message = "Support for version 0.9x of GRUB was removed after being unsupported upstream for around a decade";
774 }
775 ] ++ flip concatMap cfg.mirroredBoots (args: [
776 {
777 assertion = args.devices != [ ];
778 message = "A boot path cannot have an empty devices string in ${args.path}";
779 }
780 {
781 assertion = hasPrefix "/" args.path;
782 message = "Boot paths must be absolute, not ${args.path}";
783 }
784 {
785 assertion = if args.efiSysMountPoint == null then true else hasPrefix "/" args.efiSysMountPoint;
786 message = "EFI paths must be absolute, not ${args.efiSysMountPoint}";
787 }
788 ] ++ forEach args.devices (device: {
789 assertion = device == "nodev" || hasPrefix "/" device;
790 message = "GRUB devices must be absolute paths, not ${device} in ${args.path}";
791 }));
792 })
793
794 (mkIf options.boot.loader.grub.version.isDefined {
795 warnings = [ ''
796 The boot.loader.grub.version option does not have any effect anymore, please remove it from your configuration.
797 '' ];
798 })
799 ];
800
801
802 imports =
803 [ (mkRemovedOptionModule [ "boot" "loader" "grub" "bootDevice" ] "")
804 (mkRenamedOptionModule [ "boot" "copyKernels" ] [ "boot" "loader" "grub" "copyKernels" ])
805 (mkRenamedOptionModule [ "boot" "extraGrubEntries" ] [ "boot" "loader" "grub" "extraEntries" ])
806 (mkRenamedOptionModule [ "boot" "extraGrubEntriesBeforeNixos" ] [ "boot" "loader" "grub" "extraEntriesBeforeNixOS" ])
807 (mkRenamedOptionModule [ "boot" "grubDevice" ] [ "boot" "loader" "grub" "device" ])
808 (mkRenamedOptionModule [ "boot" "bootMount" ] [ "boot" "loader" "grub" "bootDevice" ])
809 (mkRenamedOptionModule [ "boot" "grubSplashImage" ] [ "boot" "loader" "grub" "splashImage" ])
810 (mkRemovedOptionModule [ "boot" "loader" "grub" "trustedBoot" ] ''
811 Support for Trusted GRUB has been removed, because the project
812 has been retired upstream.
813 '')
814 (mkRemovedOptionModule [ "boot" "loader" "grub" "extraInitrd" ] ''
815 This option has been replaced with the bootloader agnostic
816 boot.initrd.secrets option. To migrate to the initrd secrets system,
817 extract the extraInitrd archive into your main filesystem:
818
819 # zcat /boot/extra_initramfs.gz | cpio -idvmD /etc/secrets/initrd
820 /path/to/secret1
821 /path/to/secret2
822
823 then replace boot.loader.grub.extraInitrd with boot.initrd.secrets:
824
825 boot.initrd.secrets = {
826 "/path/to/secret1" = "/etc/secrets/initrd/path/to/secret1";
827 "/path/to/secret2" = "/etc/secrets/initrd/path/to/secret2";
828 };
829
830 See the boot.initrd.secrets option documentation for more information.
831 '')
832 ];
833
834}