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