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