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