at 16.09-beta 17 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 57 extraEntriesBeforeNixOS extraPrepareConfig configurationLimit copyKernels 58 default fsIdentifier efiSupport gfxmodeEfi gfxmodeBios; 59 path = (makeBinPath ([ 60 pkgs.coreutils pkgs.gnused pkgs.gnugrep pkgs.findutils pkgs.diffutils pkgs.btrfs-progs 61 pkgs.utillinux ] ++ (if cfg.efiSupport && (cfg.version == 2) then [pkgs.efibootmgr ] else []) 62 )) + ":" + (makeSearchPathOutput "bin" "sbin" [ 63 pkgs.mdadm pkgs.utillinux 64 ]); 65 }); 66 67 bootDeviceCounters = fold (device: attr: attr // { "${device}" = (attr."${device}" or 0) + 1; }) {} 68 (concatMap (args: args.devices) cfg.mirroredBoots); 69 70in 71 72{ 73 74 ###### interface 75 76 options = { 77 78 boot.loader.grub = { 79 80 enable = mkOption { 81 default = !config.boot.isContainer; 82 type = types.bool; 83 description = '' 84 Whether to enable the GNU GRUB boot loader. 85 ''; 86 }; 87 88 version = mkOption { 89 default = 2; 90 example = 1; 91 type = types.int; 92 description = '' 93 The version of GRUB to use: <literal>1</literal> for GRUB 94 Legacy (versions 0.9x), or <literal>2</literal> (the 95 default) for GRUB 2. 96 ''; 97 }; 98 99 device = mkOption { 100 default = ""; 101 example = "/dev/hda"; 102 type = types.str; 103 description = '' 104 The device on which the GRUB boot loader will be installed. 105 The special value <literal>nodev</literal> means that a GRUB 106 boot menu will be generated, but GRUB itself will not 107 actually be installed. To install GRUB on multiple devices, 108 use <literal>boot.loader.grub.devices</literal>. 109 ''; 110 }; 111 112 devices = mkOption { 113 default = []; 114 example = [ "/dev/hda" ]; 115 type = types.listOf types.str; 116 description = '' 117 The devices on which the boot loader, GRUB, will be 118 installed. Can be used instead of <literal>device</literal> to 119 install GRUB onto multiple devices. 120 ''; 121 }; 122 123 mirroredBoots = mkOption { 124 default = [ ]; 125 example = [ 126 { path = "/boot1"; devices = [ "/dev/sda" ]; } 127 { path = "/boot2"; devices = [ "/dev/sdb" ]; } 128 ]; 129 description = '' 130 Mirror the boot configuration to multiple partitions and install grub 131 to the respective devices corresponding to those partitions. 132 ''; 133 134 type = types.listOf types.optionSet; 135 136 options = { 137 138 path = mkOption { 139 example = "/boot1"; 140 type = types.str; 141 description = '' 142 The path to the boot directory where GRUB will be written. Generally 143 this boot path should double as an EFI path. 144 ''; 145 }; 146 147 efiSysMountPoint = mkOption { 148 default = null; 149 example = "/boot1/efi"; 150 type = types.nullOr types.str; 151 description = '' 152 The path to the efi system mount point. Usually this is the same 153 partition as the above path and can be left as null. 154 ''; 155 }; 156 157 efiBootloaderId = mkOption { 158 default = null; 159 example = "NixOS-fsid"; 160 type = types.nullOr types.str; 161 description = '' 162 The id of the bootloader to store in efi nvram. 163 The default is to name it NixOS and append the path or efiSysMountPoint. 164 This is only used if <literal>boot.loader.efi.canTouchEfiVariables</literal> is true. 165 ''; 166 }; 167 168 devices = mkOption { 169 default = [ ]; 170 example = [ "/dev/sda" "/dev/sdb" ]; 171 type = types.listOf types.str; 172 description = '' 173 The path to the devices which will have the GRUB MBR written. 174 Note these are typically device paths and not paths to partitions. 175 ''; 176 }; 177 178 }; 179 }; 180 181 configurationName = mkOption { 182 default = ""; 183 example = "Stable 2.6.21"; 184 type = types.str; 185 description = '' 186 GRUB entry name instead of default. 187 ''; 188 }; 189 190 storePath = mkOption { 191 default = "/nix/store"; 192 type = types.str; 193 description = '' 194 Path to the Nix store when looking for kernels at boot. 195 Only makes sense when copyKernels is false. 196 ''; 197 }; 198 199 extraPrepareConfig = mkOption { 200 default = ""; 201 type = types.lines; 202 description = '' 203 Additional bash commands to be run at the script that 204 prepares the GRUB menu entries. 205 ''; 206 }; 207 208 extraConfig = mkOption { 209 default = ""; 210 example = "serial; terminal_output.serial"; 211 type = types.lines; 212 description = '' 213 Additional GRUB commands inserted in the configuration file 214 just before the menu entries. 215 ''; 216 }; 217 218 extraPerEntryConfig = mkOption { 219 default = ""; 220 example = "root (hd0)"; 221 type = types.lines; 222 description = '' 223 Additional GRUB commands inserted in the configuration file 224 at the start of each NixOS menu entry. 225 ''; 226 }; 227 228 extraEntries = mkOption { 229 default = ""; 230 type = types.lines; 231 example = '' 232 # GRUB 1 example (not GRUB 2 compatible) 233 title Windows 234 chainloader (hd0,1)+1 235 236 # GRUB 2 example 237 menuentry "Windows 7" { 238 chainloader (hd0,4)+1 239 } 240 ''; 241 description = '' 242 Any additional entries you want added to the GRUB boot menu. 243 ''; 244 }; 245 246 extraEntriesBeforeNixOS = mkOption { 247 default = false; 248 type = types.bool; 249 description = '' 250 Whether extraEntries are included before the default option. 251 ''; 252 }; 253 254 extraFiles = mkOption { 255 type = types.attrsOf types.path; 256 default = {}; 257 example = literalExample '' 258 { "memtest.bin" = "''${pkgs.memtest86plus}/memtest.bin"; } 259 ''; 260 description = '' 261 A set of files to be copied to <filename>/boot</filename>. 262 Each attribute name denotes the destination file name in 263 <filename>/boot</filename>, while the corresponding 264 attribute value specifies the source file. 265 ''; 266 }; 267 268 splashImage = mkOption { 269 type = types.nullOr types.path; 270 example = literalExample "./my-background.png"; 271 description = '' 272 Background image used for GRUB. It must be a 640x480, 273 14-colour image in XPM format, optionally compressed with 274 <command>gzip</command> or <command>bzip2</command>. Set to 275 <literal>null</literal> to run GRUB in text mode. 276 ''; 277 }; 278 279 gfxmodeEfi = mkOption { 280 default = "auto"; 281 example = "1024x768"; 282 type = types.str; 283 description = '' 284 The gfxmode to pass to GRUB when loading a graphical boot interface under EFI. 285 ''; 286 }; 287 288 gfxmodeBios = mkOption { 289 default = "1024x768"; 290 example = "auto"; 291 type = types.str; 292 description = '' 293 The gfxmode to pass to GRUB when loading a graphical boot interface under BIOS. 294 ''; 295 }; 296 297 configurationLimit = mkOption { 298 default = 100; 299 example = 120; 300 type = types.int; 301 description = '' 302 Maximum of configurations in boot menu. GRUB has problems when 303 there are too many entries. 304 ''; 305 }; 306 307 copyKernels = mkOption { 308 default = false; 309 type = types.bool; 310 description = '' 311 Whether the GRUB menu builder should copy kernels and initial 312 ramdisks to /boot. This is done automatically if /boot is 313 on a different partition than /. 314 ''; 315 }; 316 317 default = mkOption { 318 default = 0; 319 type = types.int; 320 description = '' 321 Index of the default menu item to be booted. 322 ''; 323 }; 324 325 fsIdentifier = mkOption { 326 default = "uuid"; 327 type = types.addCheck types.str 328 (type: type == "uuid" || type == "label" || type == "provided"); 329 description = '' 330 Determines how GRUB will identify devices when generating the 331 configuration file. A value of uuid / label signifies that grub 332 will always resolve the uuid or label of the device before using 333 it in the configuration. A value of provided means that GRUB will 334 use the device name as show in <command>df</command> or 335 <command>mount</command>. Note, zfs zpools / datasets are ignored 336 and will always be mounted using their labels. 337 ''; 338 }; 339 340 zfsSupport = mkOption { 341 default = false; 342 type = types.bool; 343 description = '' 344 Whether GRUB should be built against libzfs. 345 ZFS support is only available for GRUB v2. 346 This option is ignored for GRUB v1. 347 ''; 348 }; 349 350 efiSupport = mkOption { 351 default = false; 352 type = types.bool; 353 description = '' 354 Whether GRUB should be built with EFI support. 355 EFI support is only available for GRUB v2. 356 This option is ignored for GRUB v1. 357 ''; 358 }; 359 360 enableCryptodisk = mkOption { 361 default = false; 362 type = types.bool; 363 description = '' 364 Enable support for encrypted partitions. GRUB should automatically 365 unlock the correct encrypted partition and look for filesystems. 366 ''; 367 }; 368 369 trustedBoot = { 370 371 enable = mkOption { 372 default = false; 373 type = types.bool; 374 description = '' 375 Enable trusted boot. GRUB will measure all critical components during 376 the boot process to offer TCG (TPM) support. 377 ''; 378 }; 379 380 systemHasTPM = mkOption { 381 default = ""; 382 example = "YES_TPM_is_activated"; 383 type = types.string; 384 description = '' 385 Assertion that the target system has an activated TPM. It is a safety 386 check before allowing the activation of 'trustedBoot.enable'. TrustedBoot 387 WILL FAIL TO BOOT YOUR SYSTEM if no TPM is available. 388 ''; 389 }; 390 391 isHPLaptop = mkOption { 392 default = false; 393 type = types.bool; 394 description = '' 395 Use a special version of TrustedGRUB that is needed by some HP laptops 396 and works only for the HP laptops. 397 ''; 398 }; 399 400 }; 401 402 }; 403 404 }; 405 406 407 ###### implementation 408 409 config = mkMerge [ 410 411 { boot.loader.grub.splashImage = mkDefault ( 412 if cfg.version == 1 then pkgs.fetchurl { 413 url = http://www.gnome-look.org/CONTENT/content-files/36909-soft-tux.xpm.gz; 414 sha256 = "14kqdx2lfqvh40h6fjjzqgff1mwk74dmbjvmqphi6azzra7z8d59"; 415 } 416 # GRUB 1.97 doesn't support gzipped XPMs. 417 else "${pkgs.nixos-artwork}/share/artwork/gnome/Gnome_Dark.png"); 418 } 419 420 (mkIf cfg.enable { 421 422 boot.loader.grub.devices = optional (cfg.device != "") cfg.device; 423 424 boot.loader.grub.mirroredBoots = optionals (cfg.devices != [ ]) [ 425 { path = "/boot"; inherit (cfg) devices; inherit (efi) efiSysMountPoint; } 426 ]; 427 428 system.build.installBootLoader = 429 let 430 install-grub-pl = pkgs.substituteAll { 431 src = ./install-grub.pl; 432 inherit (pkgs) utillinux; 433 btrfsprogs = pkgs.btrfs-progs; 434 }; 435 in pkgs.writeScript "install-grub.sh" ('' 436 #!${pkgs.stdenv.shell} 437 set -e 438 export PERL5LIB=${makePerlPath (with pkgs.perlPackages; [ FileSlurp XMLLibXML XMLSAX ListCompare ])} 439 ${optionalString cfg.enableCryptodisk "export GRUB_ENABLE_CRYPTODISK=y"} 440 '' + flip concatMapStrings cfg.mirroredBoots (args: '' 441 ${pkgs.perl}/bin/perl ${install-grub-pl} ${grubConfig args} $@ 442 '')); 443 444 system.build.grub = grub; 445 446 # Common attribute for boot loaders so only one of them can be 447 # set at once. 448 system.boot.loader.id = "grub"; 449 450 environment.systemPackages = optional (grub != null) grub; 451 452 boot.loader.grub.extraPrepareConfig = 453 concatStrings (mapAttrsToList (n: v: '' 454 ${pkgs.coreutils}/bin/cp -pf "${v}" "/boot/${n}" 455 '') config.boot.loader.grub.extraFiles); 456 457 assertions = [ 458 { 459 assertion = !cfg.zfsSupport || cfg.version == 2; 460 message = "Only GRUB version 2 provides ZFS support"; 461 } 462 { 463 assertion = cfg.mirroredBoots != [ ]; 464 message = "You must set the option boot.loader.grub.devices or " 465 + "'boot.loader.grub.mirroredBoots' to make the system bootable."; 466 } 467 { 468 assertion = all (c: c < 2) (mapAttrsToList (_: c: c) bootDeviceCounters); 469 message = "You cannot have duplicated devices in mirroredBoots"; 470 } 471 { 472 assertion = !cfg.trustedBoot.enable || cfg.version == 2; 473 message = "Trusted GRUB is only available for GRUB 2"; 474 } 475 { 476 assertion = !cfg.efiSupport || !cfg.trustedBoot.enable; 477 message = "Trusted GRUB does not have EFI support"; 478 } 479 { 480 assertion = !cfg.zfsSupport || !cfg.trustedBoot.enable; 481 message = "Trusted GRUB does not have ZFS support"; 482 } 483 { 484 assertion = !cfg.trustedBoot.enable || cfg.trustedBoot.systemHasTPM == "YES_TPM_is_activated"; 485 message = "Trusted GRUB can break the system! Confirm that the system has an activated TPM by setting 'systemHasTPM'."; 486 } 487 ] ++ flip concatMap cfg.mirroredBoots (args: [ 488 { 489 assertion = args.devices != [ ]; 490 message = "A boot path cannot have an empty devices string in ${args.path}"; 491 } 492 { 493 assertion = hasPrefix "/" args.path; 494 message = "Boot paths must be absolute, not ${args.path}"; 495 } 496 { 497 assertion = if args.efiSysMountPoint == null then true else hasPrefix "/" args.efiSysMountPoint; 498 message = "EFI paths must be absolute, not ${args.efiSysMountPoint}"; 499 } 500 ] ++ flip map args.devices (device: { 501 assertion = device == "nodev" || hasPrefix "/" device; 502 message = "GRUB devices must be absolute paths, not ${device} in ${args.path}"; 503 })); 504 }) 505 506 ]; 507 508 509 imports = 510 [ (mkRemovedOptionModule [ "boot" "loader" "grub" "bootDevice" ] "") 511 (mkRenamedOptionModule [ "boot" "copyKernels" ] [ "boot" "loader" "grub" "copyKernels" ]) 512 (mkRenamedOptionModule [ "boot" "extraGrubEntries" ] [ "boot" "loader" "grub" "extraEntries" ]) 513 (mkRenamedOptionModule [ "boot" "extraGrubEntriesBeforeNixos" ] [ "boot" "loader" "grub" "extraEntriesBeforeNixOS" ]) 514 (mkRenamedOptionModule [ "boot" "grubDevice" ] [ "boot" "loader" "grub" "device" ]) 515 (mkRenamedOptionModule [ "boot" "bootMount" ] [ "boot" "loader" "grub" "bootDevice" ]) 516 (mkRenamedOptionModule [ "boot" "grubSplashImage" ] [ "boot" "loader" "grub" "splashImage" ]) 517 ]; 518 519}