at 18.09-beta 24 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 7 cfg = config.boot.loader.grub; 8 9 efi = config.boot.loader.efi; 10 11 realGrub = if cfg.version == 1 then pkgs.grub 12 else if cfg.zfsSupport then pkgs.grub2.override { zfsSupport = true; } 13 else if cfg.trustedBoot.enable 14 then if cfg.trustedBoot.isHPLaptop 15 then pkgs.trustedGrub-for-HP 16 else pkgs.trustedGrub 17 else pkgs.grub2; 18 19 grub = 20 # Don't include GRUB if we're only generating a GRUB menu (e.g., 21 # in EC2 instances). 22 if cfg.devices == ["nodev"] 23 then null 24 else realGrub; 25 26 grubEfi = 27 # EFI version of Grub v2 28 if cfg.efiSupport && (cfg.version == 2) 29 then realGrub.override { efiSupport = cfg.efiSupport; } 30 else null; 31 32 f = x: if x == null then "" else "" + x; 33 34 grubConfig = args: 35 let 36 efiSysMountPoint = if args.efiSysMountPoint == null then args.path else args.efiSysMountPoint; 37 efiSysMountPoint' = replaceChars [ "/" ] [ "-" ] efiSysMountPoint; 38 in 39 pkgs.writeText "grub-config.xml" (builtins.toXML 40 { splashImage = f cfg.splashImage; 41 splashMode = f cfg.splashMode; 42 backgroundColor = f cfg.backgroundColor; 43 grub = f grub; 44 grubTarget = f (grub.grubTarget or ""); 45 shell = "${pkgs.runtimeShell}"; 46 fullName = (builtins.parseDrvName realGrub.name).name; 47 fullVersion = (builtins.parseDrvName realGrub.name).version; 48 grubEfi = f grubEfi; 49 grubTargetEfi = if cfg.efiSupport && (cfg.version == 2) then f (grubEfi.grubTarget or "") else ""; 50 bootPath = args.path; 51 storePath = config.boot.loader.grub.storePath; 52 bootloaderId = if args.efiBootloaderId == null then "NixOS${efiSysMountPoint'}" else args.efiBootloaderId; 53 timeout = if config.boot.loader.timeout == null then -1 else config.boot.loader.timeout; 54 inherit efiSysMountPoint; 55 inherit (args) devices; 56 inherit (efi) canTouchEfiVariables; 57 inherit (cfg) 58 version extraConfig extraPerEntryConfig extraEntries forceInstall useOSProber 59 extraEntriesBeforeNixOS extraPrepareConfig extraInitrd configurationLimit copyKernels 60 default fsIdentifier efiSupport efiInstallAsRemovable gfxmodeEfi gfxmodeBios; 61 path = (makeBinPath ([ 62 pkgs.coreutils pkgs.gnused pkgs.gnugrep pkgs.findutils pkgs.diffutils pkgs.btrfs-progs 63 pkgs.utillinux ] 64 ++ (optional (cfg.efiSupport && (cfg.version == 2)) pkgs.efibootmgr) 65 ++ (optionals cfg.useOSProber [pkgs.busybox pkgs.os-prober]) 66 )) + ":" + (makeSearchPathOutput "bin" "sbin" [ 67 pkgs.mdadm pkgs.utillinux 68 ]); 69 font = if cfg.font == null then "" 70 else (if lib.last (lib.splitString "." cfg.font) == "pf2" 71 then cfg.font 72 else "${convertedFont}"); 73 }); 74 75 bootDeviceCounters = fold (device: attr: attr // { "${device}" = (attr."${device}" or 0) + 1; }) {} 76 (concatMap (args: args.devices) cfg.mirroredBoots); 77 78 convertedFont = (pkgs.runCommand "grub-font-converted.pf2" {} 79 (builtins.concatStringsSep " " 80 ([ "${realGrub}/bin/grub-mkfont" 81 cfg.font 82 "--output" "$out" 83 ] ++ (optional (cfg.fontSize!=null) "--size ${toString cfg.fontSize}"))) 84 ); 85 86 defaultSplash = "${pkgs.nixos-artwork.wallpapers.simple-dark-gray-bootloader}/share/artwork/gnome/nix-wallpaper-simple-dark-gray_bootloader.png"; 87in 88 89{ 90 91 ###### interface 92 93 options = { 94 95 boot.loader.grub = { 96 97 enable = mkOption { 98 default = !config.boot.isContainer; 99 type = types.bool; 100 description = '' 101 Whether to enable the GNU GRUB boot loader. 102 ''; 103 }; 104 105 version = mkOption { 106 default = 2; 107 example = 1; 108 type = types.int; 109 description = '' 110 The version of GRUB to use: <literal>1</literal> for GRUB 111 Legacy (versions 0.9x), or <literal>2</literal> (the 112 default) for GRUB 2. 113 ''; 114 }; 115 116 device = mkOption { 117 default = ""; 118 example = "/dev/disk/by-id/wwn-0x500001234567890a"; 119 type = types.str; 120 description = '' 121 The device on which the GRUB boot loader will be installed. 122 The special value <literal>nodev</literal> means that a GRUB 123 boot menu will be generated, but GRUB itself will not 124 actually be installed. To install GRUB on multiple devices, 125 use <literal>boot.loader.grub.devices</literal>. 126 ''; 127 }; 128 129 devices = mkOption { 130 default = []; 131 example = [ "/dev/disk/by-id/wwn-0x500001234567890a" ]; 132 type = types.listOf types.str; 133 description = '' 134 The devices on which the boot loader, GRUB, will be 135 installed. Can be used instead of <literal>device</literal> to 136 install GRUB onto multiple devices. 137 ''; 138 }; 139 140 mirroredBoots = mkOption { 141 default = [ ]; 142 example = [ 143 { path = "/boot1"; devices = [ "/dev/disk/by-id/wwn-0x500001234567890a" ]; } 144 { path = "/boot2"; devices = [ "/dev/disk/by-id/wwn-0x500009876543210a" ]; } 145 ]; 146 description = '' 147 Mirror the boot configuration to multiple partitions and install grub 148 to the respective devices corresponding to those partitions. 149 ''; 150 151 type = with types; listOf (submodule { 152 options = { 153 154 path = mkOption { 155 example = "/boot1"; 156 type = types.str; 157 description = '' 158 The path to the boot directory where GRUB will be written. Generally 159 this boot path should double as an EFI path. 160 ''; 161 }; 162 163 efiSysMountPoint = mkOption { 164 default = null; 165 example = "/boot1/efi"; 166 type = types.nullOr types.str; 167 description = '' 168 The path to the efi system mount point. Usually this is the same 169 partition as the above path and can be left as null. 170 ''; 171 }; 172 173 efiBootloaderId = mkOption { 174 default = null; 175 example = "NixOS-fsid"; 176 type = types.nullOr types.str; 177 description = '' 178 The id of the bootloader to store in efi nvram. 179 The default is to name it NixOS and append the path or efiSysMountPoint. 180 This is only used if <literal>boot.loader.efi.canTouchEfiVariables</literal> is true. 181 ''; 182 }; 183 184 devices = mkOption { 185 default = [ ]; 186 example = [ "/dev/disk/by-id/wwn-0x500001234567890a" "/dev/disk/by-id/wwn-0x500009876543210a" ]; 187 type = types.listOf types.str; 188 description = '' 189 The path to the devices which will have the GRUB MBR written. 190 Note these are typically device paths and not paths to partitions. 191 ''; 192 }; 193 194 }; 195 }); 196 }; 197 198 configurationName = mkOption { 199 default = ""; 200 example = "Stable 2.6.21"; 201 type = types.str; 202 description = '' 203 GRUB entry name instead of default. 204 ''; 205 }; 206 207 storePath = mkOption { 208 default = "/nix/store"; 209 type = types.str; 210 description = '' 211 Path to the Nix store when looking for kernels at boot. 212 Only makes sense when copyKernels is false. 213 ''; 214 }; 215 216 extraPrepareConfig = mkOption { 217 default = ""; 218 type = types.lines; 219 description = '' 220 Additional bash commands to be run at the script that 221 prepares the GRUB menu entries. 222 ''; 223 }; 224 225 extraConfig = mkOption { 226 default = ""; 227 example = "serial; terminal_output.serial"; 228 type = types.lines; 229 description = '' 230 Additional GRUB commands inserted in the configuration file 231 just before the menu entries. 232 ''; 233 }; 234 235 extraPerEntryConfig = mkOption { 236 default = ""; 237 example = "root (hd0)"; 238 type = types.lines; 239 description = '' 240 Additional GRUB commands inserted in the configuration file 241 at the start of each NixOS menu entry. 242 ''; 243 }; 244 245 extraEntries = mkOption { 246 default = ""; 247 type = types.lines; 248 example = '' 249 # GRUB 1 example (not GRUB 2 compatible) 250 title Windows 251 chainloader (hd0,1)+1 252 253 # GRUB 2 example 254 menuentry "Windows 7" { 255 chainloader (hd0,4)+1 256 } 257 258 # GRUB 2 with UEFI example, chainloading another distro 259 menuentry "Fedora" { 260 set root=(hd1,1) 261 chainloader /efi/fedora/grubx64.efi 262 } 263 ''; 264 description = '' 265 Any additional entries you want added to the GRUB boot menu. 266 ''; 267 }; 268 269 extraEntriesBeforeNixOS = mkOption { 270 default = false; 271 type = types.bool; 272 description = '' 273 Whether extraEntries are included before the default option. 274 ''; 275 }; 276 277 extraFiles = mkOption { 278 type = types.attrsOf types.path; 279 default = {}; 280 example = literalExample '' 281 { "memtest.bin" = "''${pkgs.memtest86plus}/memtest.bin"; } 282 ''; 283 description = '' 284 A set of files to be copied to <filename>/boot</filename>. 285 Each attribute name denotes the destination file name in 286 <filename>/boot</filename>, while the corresponding 287 attribute value specifies the source file. 288 ''; 289 }; 290 291 extraInitrd = mkOption { 292 type = types.nullOr types.path; 293 default = null; 294 example = "/boot/extra_initramfs.gz"; 295 description = '' 296 The path to a second initramfs to be supplied to the kernel. 297 This ramfs will not be copied to the store, so that it can 298 contain secrets such as LUKS keyfiles or ssh keys. 299 This implies that rolling back to a previous configuration 300 won't rollback the state of this file. 301 ''; 302 }; 303 304 useOSProber = mkOption { 305 default = false; 306 type = types.bool; 307 description = '' 308 If set to true, append entries for other OSs detected by os-prober. 309 ''; 310 }; 311 312 splashImage = mkOption { 313 type = types.nullOr types.path; 314 example = literalExample "./my-background.png"; 315 description = '' 316 Background image used for GRUB. 317 Set to <literal>null</literal> to run GRUB in text mode. 318 319 <note><para> 320 For grub 1: 321 It must be a 640x480, 322 14-colour image in XPM format, optionally compressed with 323 <command>gzip</command> or <command>bzip2</command>. 324 </para></note> 325 326 <note><para> 327 For grub 2: 328 File must be one of .png, .tga, .jpg, or .jpeg. JPEG images must 329 not be progressive. 330 The image will be scaled if necessary to fit the screen. 331 </para></note> 332 ''; 333 }; 334 335 backgroundColor = mkOption { 336 type = types.nullOr types.string; 337 example = "#7EBAE4"; 338 default = null; 339 description = '' 340 Background color to be used for GRUB to fill the areas the image isn't filling. 341 342 <note><para> 343 This options has no effect for GRUB 1. 344 </para></note> 345 ''; 346 }; 347 348 splashMode = mkOption { 349 type = types.enum [ "normal" "stretch" ]; 350 default = "stretch"; 351 description = '' 352 Whether to stretch the image or show the image in the top-left corner unstretched. 353 354 <note><para> 355 This options has no effect for GRUB 1. 356 </para></note> 357 ''; 358 }; 359 360 font = mkOption { 361 type = types.nullOr types.path; 362 default = "${realGrub}/share/grub/unicode.pf2"; 363 description = '' 364 Path to a TrueType, OpenType, or pf2 font to be used by Grub. 365 ''; 366 }; 367 368 fontSize = mkOption { 369 type = types.nullOr types.int; 370 example = literalExample 16; 371 default = null; 372 description = '' 373 Font size for the grub menu. Ignored unless <literal>font</literal> 374 is set to a ttf or otf font. 375 ''; 376 }; 377 378 gfxmodeEfi = mkOption { 379 default = "auto"; 380 example = "1024x768"; 381 type = types.str; 382 description = '' 383 The gfxmode to pass to GRUB when loading a graphical boot interface under EFI. 384 ''; 385 }; 386 387 gfxmodeBios = mkOption { 388 default = "1024x768"; 389 example = "auto"; 390 type = types.str; 391 description = '' 392 The gfxmode to pass to GRUB when loading a graphical boot interface under BIOS. 393 ''; 394 }; 395 396 configurationLimit = mkOption { 397 default = 100; 398 example = 120; 399 type = types.int; 400 description = '' 401 Maximum of configurations in boot menu. GRUB has problems when 402 there are too many entries. 403 ''; 404 }; 405 406 copyKernels = mkOption { 407 default = false; 408 type = types.bool; 409 description = '' 410 Whether the GRUB menu builder should copy kernels and initial 411 ramdisks to /boot. This is done automatically if /boot is 412 on a different partition than /. 413 ''; 414 }; 415 416 default = mkOption { 417 default = "0"; 418 type = types.either types.int types.str; 419 apply = toString; 420 description = '' 421 Index of the default menu item to be booted. 422 ''; 423 }; 424 425 fsIdentifier = mkOption { 426 default = "uuid"; 427 type = types.enum [ "uuid" "label" "provided" ]; 428 description = '' 429 Determines how GRUB will identify devices when generating the 430 configuration file. A value of uuid / label signifies that grub 431 will always resolve the uuid or label of the device before using 432 it in the configuration. A value of provided means that GRUB will 433 use the device name as show in <command>df</command> or 434 <command>mount</command>. Note, zfs zpools / datasets are ignored 435 and will always be mounted using their labels. 436 ''; 437 }; 438 439 zfsSupport = mkOption { 440 default = false; 441 type = types.bool; 442 description = '' 443 Whether GRUB should be built against libzfs. 444 ZFS support is only available for GRUB v2. 445 This option is ignored for GRUB v1. 446 ''; 447 }; 448 449 efiSupport = mkOption { 450 default = false; 451 type = types.bool; 452 description = '' 453 Whether GRUB should be built with EFI support. 454 EFI support is only available for GRUB v2. 455 This option is ignored for GRUB v1. 456 ''; 457 }; 458 459 efiInstallAsRemovable = mkOption { 460 default = false; 461 type = types.bool; 462 description = '' 463 Whether to invoke <literal>grub-install</literal> with 464 <literal>--removable</literal>.</para> 465 466 <para>Unless you turn this on, GRUB will install itself somewhere in 467 <literal>boot.loader.efi.efiSysMountPoint</literal> (exactly where 468 depends on other config variables). If you've set 469 <literal>boot.loader.efi.canTouchEfiVariables</literal> *AND* you 470 are currently booted in UEFI mode, then GRUB will use 471 <literal>efibootmgr</literal> to modify the boot order in the 472 EFI variables of your firmware to include this location. If you are 473 *not* booted in UEFI mode at the time GRUB is being installed, the 474 NVRAM will not be modified, and your system will not find GRUB at 475 boot time. However, GRUB will still return success so you may miss 476 the warning that gets printed ("<literal>efibootmgr: EFI variables 477 are not supported on this system.</literal>").</para> 478 479 <para>If you turn this feature on, GRUB will install itself in a 480 special location within <literal>efiSysMountPoint</literal> (namely 481 <literal>EFI/boot/boot$arch.efi</literal>) which the firmwares 482 are hardcoded to try first, regardless of NVRAM EFI variables.</para> 483 484 <para>To summarize, turn this on if: 485 <itemizedlist> 486 <listitem><para>You are installing NixOS and want it to boot in UEFI mode, 487 but you are currently booted in legacy mode</para></listitem> 488 <listitem><para>You want to make a drive that will boot regardless of 489 the NVRAM state of the computer (like a USB "removable" drive)</para></listitem> 490 <listitem><para>You simply dislike the idea of depending on NVRAM 491 state to make your drive bootable</para></listitem> 492 </itemizedlist> 493 ''; 494 }; 495 496 enableCryptodisk = mkOption { 497 default = false; 498 type = types.bool; 499 description = '' 500 Enable support for encrypted partitions. GRUB should automatically 501 unlock the correct encrypted partition and look for filesystems. 502 ''; 503 }; 504 505 forceInstall = mkOption { 506 default = false; 507 type = types.bool; 508 description = '' 509 Whether to try and forcibly install GRUB even if problems are 510 detected. It is not recommended to enable this unless you know what 511 you are doing. 512 ''; 513 }; 514 515 trustedBoot = { 516 517 enable = mkOption { 518 default = false; 519 type = types.bool; 520 description = '' 521 Enable trusted boot. GRUB will measure all critical components during 522 the boot process to offer TCG (TPM) support. 523 ''; 524 }; 525 526 systemHasTPM = mkOption { 527 default = ""; 528 example = "YES_TPM_is_activated"; 529 type = types.string; 530 description = '' 531 Assertion that the target system has an activated TPM. It is a safety 532 check before allowing the activation of 'trustedBoot.enable'. TrustedBoot 533 WILL FAIL TO BOOT YOUR SYSTEM if no TPM is available. 534 ''; 535 }; 536 537 isHPLaptop = mkOption { 538 default = false; 539 type = types.bool; 540 description = '' 541 Use a special version of TrustedGRUB that is needed by some HP laptops 542 and works only for the HP laptops. 543 ''; 544 }; 545 546 }; 547 548 }; 549 550 }; 551 552 553 ###### implementation 554 555 config = mkMerge [ 556 557 { boot.loader.grub.splashImage = mkDefault ( 558 if cfg.version == 1 then pkgs.fetchurl { 559 url = http://www.gnome-look.org/CONTENT/content-files/36909-soft-tux.xpm.gz; 560 sha256 = "14kqdx2lfqvh40h6fjjzqgff1mwk74dmbjvmqphi6azzra7z8d59"; 561 } 562 # GRUB 1.97 doesn't support gzipped XPMs. 563 else defaultSplash); 564 } 565 566 (mkIf (cfg.splashImage == defaultSplash) { 567 boot.loader.grub.backgroundColor = mkDefault "#2F302F"; 568 boot.loader.grub.splashMode = mkDefault "normal"; 569 }) 570 571 (mkIf cfg.enable { 572 573 boot.loader.grub.devices = optional (cfg.device != "") cfg.device; 574 575 boot.loader.grub.mirroredBoots = optionals (cfg.devices != [ ]) [ 576 { path = "/boot"; inherit (cfg) devices; inherit (efi) efiSysMountPoint; } 577 ]; 578 579 system.build.installBootLoader = 580 let 581 install-grub-pl = pkgs.substituteAll { 582 src = ./install-grub.pl; 583 inherit (pkgs) utillinux; 584 btrfsprogs = pkgs.btrfs-progs; 585 }; 586 in pkgs.writeScript "install-grub.sh" ('' 587 #!${pkgs.runtimeShell} 588 set -e 589 export PERL5LIB=${makePerlPath (with pkgs.perlPackages; [ FileSlurp XMLLibXML XMLSAX XMLSAXBase ListCompare ])} 590 ${optionalString cfg.enableCryptodisk "export GRUB_ENABLE_CRYPTODISK=y"} 591 '' + flip concatMapStrings cfg.mirroredBoots (args: '' 592 ${pkgs.perl}/bin/perl ${install-grub-pl} ${grubConfig args} $@ 593 '')); 594 595 system.build.grub = grub; 596 597 # Common attribute for boot loaders so only one of them can be 598 # set at once. 599 system.boot.loader.id = "grub"; 600 601 environment.systemPackages = optional (grub != null) grub; 602 603 boot.loader.grub.extraPrepareConfig = 604 concatStrings (mapAttrsToList (n: v: '' 605 ${pkgs.coreutils}/bin/cp -pf "${v}" "/boot/${n}" 606 '') config.boot.loader.grub.extraFiles); 607 608 assertions = [ 609 { 610 assertion = !cfg.zfsSupport || cfg.version == 2; 611 message = "Only GRUB version 2 provides ZFS support"; 612 } 613 { 614 assertion = cfg.mirroredBoots != [ ]; 615 message = "You must set the option boot.loader.grub.devices or " 616 + "'boot.loader.grub.mirroredBoots' to make the system bootable."; 617 } 618 { 619 assertion = cfg.efiSupport || all (c: c < 2) (mapAttrsToList (_: c: c) bootDeviceCounters); 620 message = "You cannot have duplicated devices in mirroredBoots"; 621 } 622 { 623 assertion = !cfg.trustedBoot.enable || cfg.version == 2; 624 message = "Trusted GRUB is only available for GRUB 2"; 625 } 626 { 627 assertion = !cfg.efiSupport || !cfg.trustedBoot.enable; 628 message = "Trusted GRUB does not have EFI support"; 629 } 630 { 631 assertion = !cfg.zfsSupport || !cfg.trustedBoot.enable; 632 message = "Trusted GRUB does not have ZFS support"; 633 } 634 { 635 assertion = !cfg.trustedBoot.enable || cfg.trustedBoot.systemHasTPM == "YES_TPM_is_activated"; 636 message = "Trusted GRUB can break the system! Confirm that the system has an activated TPM by setting 'systemHasTPM'."; 637 } 638 { 639 assertion = cfg.efiInstallAsRemovable -> cfg.efiSupport; 640 message = "If you wish to to use boot.loader.grub.efiInstallAsRemovable, then turn on boot.loader.grub.efiSupport"; 641 } 642 { 643 assertion = cfg.efiInstallAsRemovable -> !config.boot.loader.efi.canTouchEfiVariables; 644 message = "If you wish to to use boot.loader.grub.efiInstallAsRemovable, then turn off boot.loader.efi.canTouchEfiVariables"; 645 } 646 ] ++ flip concatMap cfg.mirroredBoots (args: [ 647 { 648 assertion = args.devices != [ ]; 649 message = "A boot path cannot have an empty devices string in ${args.path}"; 650 } 651 { 652 assertion = hasPrefix "/" args.path; 653 message = "Boot paths must be absolute, not ${args.path}"; 654 } 655 { 656 assertion = if args.efiSysMountPoint == null then true else hasPrefix "/" args.efiSysMountPoint; 657 message = "EFI paths must be absolute, not ${args.efiSysMountPoint}"; 658 } 659 ] ++ flip map args.devices (device: { 660 assertion = device == "nodev" || hasPrefix "/" device; 661 message = "GRUB devices must be absolute paths, not ${device} in ${args.path}"; 662 })); 663 }) 664 665 ]; 666 667 668 imports = 669 [ (mkRemovedOptionModule [ "boot" "loader" "grub" "bootDevice" ] "") 670 (mkRenamedOptionModule [ "boot" "copyKernels" ] [ "boot" "loader" "grub" "copyKernels" ]) 671 (mkRenamedOptionModule [ "boot" "extraGrubEntries" ] [ "boot" "loader" "grub" "extraEntries" ]) 672 (mkRenamedOptionModule [ "boot" "extraGrubEntriesBeforeNixos" ] [ "boot" "loader" "grub" "extraEntriesBeforeNixOS" ]) 673 (mkRenamedOptionModule [ "boot" "grubDevice" ] [ "boot" "loader" "grub" "device" ]) 674 (mkRenamedOptionModule [ "boot" "bootMount" ] [ "boot" "loader" "grub" "bootDevice" ]) 675 (mkRenamedOptionModule [ "boot" "grubSplashImage" ] [ "boot" "loader" "grub" "splashImage" ]) 676 ]; 677 678}