at 24.11-pre 31 kB view raw
1# This module creates a bootable ISO image containing the given NixOS 2# configuration. The derivation for the ISO image will be placed in 3# config.system.build.isoImage. 4 5{ config, lib, pkgs, ... }: 6 7with lib; 8 9let 10 /** 11 * Given a list of `options`, concats the result of mapping each options 12 * to a menuentry for use in grub. 13 * 14 * * defaults: {name, image, params, initrd} 15 * * options: [ option... ] 16 * * option: {name, params, class} 17 */ 18 menuBuilderGrub2 = 19 defaults: options: lib.concatStrings 20 ( 21 map 22 (option: '' 23 menuentry '${defaults.name} ${ 24 # Name appended to menuentry defaults to params if no specific name given. 25 option.name or (optionalString (option ? params) "(${option.params})") 26 }' ${optionalString (option ? class) " --class ${option.class}"} { 27 # Fallback to UEFI console for boot, efifb sometimes has difficulties. 28 terminal_output console 29 30 linux ${defaults.image} \''${isoboot} ${defaults.params} ${ 31 option.params or "" 32 } 33 initrd ${defaults.initrd} 34 } 35 '') 36 options 37 ) 38 ; 39 40 /** 41 * Builds the default options. 42 */ 43 buildMenuGrub2 = buildMenuAdditionalParamsGrub2 ""; 44 45 targetArch = 46 if config.boot.loader.grub.forcei686 then 47 "ia32" 48 else 49 pkgs.stdenv.hostPlatform.efiArch; 50 51 /** 52 * Given params to add to `params`, build a set of default options. 53 * Use this one when creating a variant (e.g. hidpi) 54 */ 55 buildMenuAdditionalParamsGrub2 = additional: 56 let 57 finalCfg = { 58 name = "${config.isoImage.prependToMenuLabel}${config.system.nixos.distroName} ${config.system.nixos.label}${config.isoImage.appendToMenuLabel}"; 59 params = "init=${config.system.build.toplevel}/init ${additional} ${toString config.boot.kernelParams}"; 60 image = "/boot/${config.system.boot.loader.kernelFile}"; 61 initrd = "/boot/initrd"; 62 }; 63 64 in 65 menuBuilderGrub2 66 finalCfg 67 [ 68 { class = "installer"; } 69 { class = "nomodeset"; params = "nomodeset"; } 70 { class = "copytoram"; params = "copytoram"; } 71 { class = "debug"; params = "debug"; } 72 ] 73 ; 74 75 # Timeout in syslinux is in units of 1/10 of a second. 76 # null means max timeout (35996, just under 1h in 1/10 seconds) 77 # 0 means disable timeout 78 syslinuxTimeout = if config.boot.loader.timeout == null then 79 35996 80 else 81 config.boot.loader.timeout * 10; 82 83 # Timeout in grub is in seconds. 84 # null means max timeout (infinity) 85 # 0 means disable timeout 86 grubEfiTimeout = if config.boot.loader.timeout == null then 87 -1 88 else 89 config.boot.loader.timeout; 90 91 # The configuration file for syslinux. 92 93 # Notes on syslinux configuration and UNetbootin compatibility: 94 # * Do not use '/syslinux/syslinux.cfg' as the path for this 95 # configuration. UNetbootin will not parse the file and use it as-is. 96 # This results in a broken configuration if the partition label does 97 # not match the specified config.isoImage.volumeID. For this reason 98 # we're using '/isolinux/isolinux.cfg'. 99 # * Use APPEND instead of adding command-line arguments directly after 100 # the LINUX entries. 101 # * COM32 entries (chainload, reboot, poweroff) are not recognized. They 102 # result in incorrect boot entries. 103 104 baseIsolinuxCfg = '' 105 SERIAL 0 115200 106 TIMEOUT ${builtins.toString syslinuxTimeout} 107 UI vesamenu.c32 108 MENU BACKGROUND /isolinux/background.png 109 110 ${config.isoImage.syslinuxTheme} 111 112 DEFAULT boot 113 114 LABEL boot 115 MENU LABEL ${config.isoImage.prependToMenuLabel}${config.system.nixos.distroName} ${config.system.nixos.label}${config.isoImage.appendToMenuLabel} 116 LINUX /boot/${config.system.boot.loader.kernelFile} 117 APPEND init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams} 118 INITRD /boot/${config.system.boot.loader.initrdFile} 119 120 # A variant to boot with 'nomodeset' 121 LABEL boot-nomodeset 122 MENU LABEL ${config.isoImage.prependToMenuLabel}${config.system.nixos.distroName} ${config.system.nixos.label}${config.isoImage.appendToMenuLabel} (nomodeset) 123 LINUX /boot/${config.system.boot.loader.kernelFile} 124 APPEND init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams} nomodeset 125 INITRD /boot/${config.system.boot.loader.initrdFile} 126 127 # A variant to boot with 'copytoram' 128 LABEL boot-copytoram 129 MENU LABEL ${config.isoImage.prependToMenuLabel}${config.system.nixos.distroName} ${config.system.nixos.label}${config.isoImage.appendToMenuLabel} (copytoram) 130 LINUX /boot/${config.system.boot.loader.kernelFile} 131 APPEND init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams} copytoram 132 INITRD /boot/${config.system.boot.loader.initrdFile} 133 134 # A variant to boot with verbose logging to the console 135 LABEL boot-debug 136 MENU LABEL ${config.isoImage.prependToMenuLabel}${config.system.nixos.distroName} ${config.system.nixos.label}${config.isoImage.appendToMenuLabel} (debug) 137 LINUX /boot/${config.system.boot.loader.kernelFile} 138 APPEND init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams} loglevel=7 139 INITRD /boot/${config.system.boot.loader.initrdFile} 140 141 # A variant to boot with a serial console enabled 142 LABEL boot-serial 143 MENU LABEL ${config.isoImage.prependToMenuLabel}${config.system.nixos.distroName} ${config.system.nixos.label}${config.isoImage.appendToMenuLabel} (serial console=ttyS0,115200n8) 144 LINUX /boot/${config.system.boot.loader.kernelFile} 145 APPEND init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams} console=ttyS0,115200n8 146 INITRD /boot/${config.system.boot.loader.initrdFile} 147 ''; 148 149 isolinuxMemtest86Entry = '' 150 LABEL memtest 151 MENU LABEL Memtest86+ 152 LINUX /boot/memtest.bin 153 APPEND ${toString config.boot.loader.grub.memtest86.params} 154 ''; 155 156 isolinuxCfg = concatStringsSep "\n" 157 ([ baseIsolinuxCfg ] ++ optional config.boot.loader.grub.memtest86.enable isolinuxMemtest86Entry); 158 159 refindBinary = if targetArch == "x64" || targetArch == "aa64" then "refind_${targetArch}.efi" else null; 160 161 # Setup instructions for rEFInd. 162 refind = 163 if refindBinary != null then 164 '' 165 # Adds rEFInd to the ISO. 166 cp -v ${pkgs.refind}/share/refind/${refindBinary} $out/EFI/boot/ 167 '' 168 else 169 "# No refind for ${targetArch}" 170 ; 171 172 grubPkgs = if config.boot.loader.grub.forcei686 then pkgs.pkgsi686Linux else pkgs; 173 174 grubMenuCfg = '' 175 # 176 # Menu configuration 177 # 178 179 # Search using a "marker file" 180 search --set=root --file /EFI/nixos-installer-image 181 182 insmod gfxterm 183 insmod png 184 set gfxpayload=keep 185 set gfxmode=${concatStringsSep "," [ 186 # GRUB will use the first valid mode listed here. 187 # `auto` will sometimes choose the smallest valid mode it detects. 188 # So instead we'll list a lot of possibly valid modes :/ 189 #"3840x2160" 190 #"2560x1440" 191 "1920x1200" 192 "1920x1080" 193 "1366x768" 194 "1280x800" 195 "1280x720" 196 "1200x1920" 197 "1024x768" 198 "800x1280" 199 "800x600" 200 "auto" 201 ]} 202 203 if [ "\$textmode" == "false" ]; then 204 terminal_output gfxterm 205 terminal_input console 206 else 207 terminal_output console 208 terminal_input console 209 # Sets colors for console term. 210 set menu_color_normal=cyan/blue 211 set menu_color_highlight=white/blue 212 fi 213 214 ${ # When there is a theme configured, use it, otherwise use the background image. 215 if config.isoImage.grubTheme != null then '' 216 # Sets theme. 217 set theme=(\$root)/EFI/boot/grub-theme/theme.txt 218 # Load theme fonts 219 $(find ${config.isoImage.grubTheme} -iname '*.pf2' -printf "loadfont (\$root)/EFI/boot/grub-theme/%P\n") 220 '' else '' 221 if background_image (\$root)/EFI/boot/efi-background.png; then 222 # Black background means transparent background when there 223 # is a background image set... This seems undocumented :( 224 set color_normal=black/black 225 set color_highlight=white/blue 226 else 227 # Falls back again to proper colors. 228 set menu_color_normal=cyan/blue 229 set menu_color_highlight=white/blue 230 fi 231 ''} 232 ''; 233 234 # The EFI boot image. 235 # Notes about grub: 236 # * Yes, the grubMenuCfg has to be repeated in all submenus. Otherwise you 237 # will get white-on-black console-like text on sub-menus. *sigh* 238 efiDir = pkgs.runCommand "efi-directory" { 239 nativeBuildInputs = [ pkgs.buildPackages.grub2_efi ]; 240 strictDeps = true; 241 } '' 242 mkdir -p $out/EFI/boot/ 243 244 # Add a marker so GRUB can find the filesystem. 245 touch $out/EFI/nixos-installer-image 246 247 # ALWAYS required modules. 248 MODULES=( 249 # Basic modules for filesystems and partition schemes 250 "fat" 251 "iso9660" 252 "part_gpt" 253 "part_msdos" 254 255 # Basic stuff 256 "normal" 257 "boot" 258 "linux" 259 "configfile" 260 "loopback" 261 "chain" 262 "halt" 263 264 # Allows rebooting into firmware setup interface 265 "efifwsetup" 266 267 # EFI Graphics Output Protocol 268 "efi_gop" 269 270 # User commands 271 "ls" 272 273 # System commands 274 "search" 275 "search_label" 276 "search_fs_uuid" 277 "search_fs_file" 278 "echo" 279 280 # We're not using it anymore, but we'll leave it in so it can be used 281 # by user, with the console using "C" 282 "serial" 283 284 # Graphical mode stuff 285 "gfxmenu" 286 "gfxterm" 287 "gfxterm_background" 288 "gfxterm_menu" 289 "test" 290 "loadenv" 291 "all_video" 292 "videoinfo" 293 294 # File types for graphical mode 295 "png" 296 ) 297 298 echo "Building GRUB with modules:" 299 for mod in ''${MODULES[@]}; do 300 echo " - $mod" 301 done 302 303 # Modules that may or may not be available per-platform. 304 echo "Adding additional modules:" 305 for mod in efi_uga; do 306 if [ -f ${grubPkgs.grub2_efi}/lib/grub/${grubPkgs.grub2_efi.grubTarget}/$mod.mod ]; then 307 echo " - $mod" 308 MODULES+=("$mod") 309 fi 310 done 311 312 # Make our own efi program, we can't rely on "grub-install" since it seems to 313 # probe for devices, even with --skip-fs-probe. 314 grub-mkimage \ 315 --directory=${grubPkgs.grub2_efi}/lib/grub/${grubPkgs.grub2_efi.grubTarget} \ 316 -o $out/EFI/boot/boot${targetArch}.efi \ 317 -p /EFI/boot \ 318 -O ${grubPkgs.grub2_efi.grubTarget} \ 319 ''${MODULES[@]} 320 cp ${grubPkgs.grub2_efi}/share/grub/unicode.pf2 $out/EFI/boot/ 321 322 cat <<EOF > $out/EFI/boot/grub.cfg 323 324 set textmode=${boolToString (config.isoImage.forceTextMode)} 325 set timeout=${toString grubEfiTimeout} 326 327 clear 328 # This message will only be viewable on the default (UEFI) console. 329 echo "" 330 echo "Loading graphical boot menu..." 331 echo "" 332 echo "Press 't' to use the text boot menu on this console..." 333 echo "" 334 335 ${grubMenuCfg} 336 337 hiddenentry 'Text mode' --hotkey 't' { 338 loadfont (\$root)/EFI/boot/unicode.pf2 339 set textmode=true 340 terminal_output console 341 } 342 hiddenentry 'GUI mode' --hotkey 'g' { 343 $(find ${config.isoImage.grubTheme} -iname '*.pf2' -printf "loadfont (\$root)/EFI/boot/grub-theme/%P\n") 344 set textmode=false 345 terminal_output gfxterm 346 } 347 348 349 # If the parameter iso_path is set, append the findiso parameter to the kernel 350 # line. We need this to allow the nixos iso to be booted from grub directly. 351 if [ \''${iso_path} ] ; then 352 set isoboot="findiso=\''${iso_path}" 353 fi 354 355 # 356 # Menu entries 357 # 358 359 ${buildMenuGrub2} 360 submenu "HiDPI, Quirks and Accessibility" --class hidpi --class submenu { 361 ${grubMenuCfg} 362 submenu "Suggests resolution @720p" --class hidpi-720p { 363 ${grubMenuCfg} 364 ${buildMenuAdditionalParamsGrub2 "video=1280x720@60"} 365 } 366 submenu "Suggests resolution @1080p" --class hidpi-1080p { 367 ${grubMenuCfg} 368 ${buildMenuAdditionalParamsGrub2 "video=1920x1080@60"} 369 } 370 371 # If we boot into a graphical environment where X is autoran 372 # and always crashes, it makes the media unusable. Allow the user 373 # to disable this. 374 submenu "Disable display-manager" --class quirk-disable-displaymanager { 375 ${grubMenuCfg} 376 ${buildMenuAdditionalParamsGrub2 "systemd.mask=display-manager.service"} 377 } 378 379 # Some laptop and convertibles have the panel installed in an 380 # inconvenient way, rotated away from the keyboard. 381 # Those entries makes it easier to use the installer. 382 submenu "" {return} 383 submenu "Rotate framebuffer Clockwise" --class rotate-90cw { 384 ${grubMenuCfg} 385 ${buildMenuAdditionalParamsGrub2 "fbcon=rotate:1"} 386 } 387 submenu "Rotate framebuffer Upside-Down" --class rotate-180 { 388 ${grubMenuCfg} 389 ${buildMenuAdditionalParamsGrub2 "fbcon=rotate:2"} 390 } 391 submenu "Rotate framebuffer Counter-Clockwise" --class rotate-90ccw { 392 ${grubMenuCfg} 393 ${buildMenuAdditionalParamsGrub2 "fbcon=rotate:3"} 394 } 395 396 # As a proof of concept, mainly. (Not sure it has accessibility merits.) 397 submenu "" {return} 398 submenu "Use black on white" --class accessibility-blakconwhite { 399 ${grubMenuCfg} 400 ${buildMenuAdditionalParamsGrub2 "vt.default_red=0xFF,0xBC,0x4F,0xB4,0x56,0xBC,0x4F,0x00,0xA1,0xCF,0x84,0xCA,0x8D,0xB4,0x84,0x68 vt.default_grn=0xFF,0x55,0xBA,0xBA,0x4D,0x4D,0xB3,0x00,0xA0,0x8F,0xB3,0xCA,0x88,0x93,0xA4,0x68 vt.default_blu=0xFF,0x58,0x5F,0x58,0xC5,0xBD,0xC5,0x00,0xA8,0xBB,0xAB,0x97,0xBD,0xC7,0xC5,0x68"} 401 } 402 403 # Serial access is a must! 404 submenu "" {return} 405 submenu "Serial console=ttyS0,115200n8" --class serial { 406 ${grubMenuCfg} 407 ${buildMenuAdditionalParamsGrub2 "console=ttyS0,115200n8"} 408 } 409 } 410 411 ${lib.optionalString (refindBinary != null) '' 412 # GRUB apparently cannot do "chainloader" operations on "CD". 413 if [ "\$root" != "cd0" ]; then 414 menuentry 'rEFInd' --class refind { 415 # Force root to be the FAT partition 416 # Otherwise it breaks rEFInd's boot 417 search --set=root --no-floppy --fs-uuid 1234-5678 418 chainloader (\$root)/EFI/boot/${refindBinary} 419 } 420 fi 421 ''} 422 menuentry 'Firmware Setup' --class settings { 423 fwsetup 424 clear 425 echo "" 426 echo "If you see this message, your EFI system doesn't support this feature." 427 echo "" 428 } 429 menuentry 'Shutdown' --class shutdown { 430 halt 431 } 432 EOF 433 434 grub-script-check $out/EFI/boot/grub.cfg 435 436 ${refind} 437 ''; 438 439 efiImg = pkgs.runCommand "efi-image_eltorito" { 440 nativeBuildInputs = [ pkgs.buildPackages.mtools pkgs.buildPackages.libfaketime pkgs.buildPackages.dosfstools ]; 441 strictDeps = true; 442 } 443 # Be careful about determinism: du --apparent-size, 444 # dates (cp -p, touch, mcopy -m, faketime for label), IDs (mkfs.vfat -i) 445 '' 446 mkdir ./contents && cd ./contents 447 mkdir -p ./EFI/boot 448 cp -rp "${efiDir}"/EFI/boot/{grub.cfg,*.efi} ./EFI/boot 449 450 # Rewrite dates for everything in the FS 451 find . -exec touch --date=2000-01-01 {} + 452 453 # Round up to the nearest multiple of 1MB, for more deterministic du output 454 usage_size=$(( $(du -s --block-size=1M --apparent-size . | tr -cd '[:digit:]') * 1024 * 1024 )) 455 # Make the image 110% as big as the files need to make up for FAT overhead 456 image_size=$(( ($usage_size * 110) / 100 )) 457 # Make the image fit blocks of 1M 458 block_size=$((1024*1024)) 459 image_size=$(( ($image_size / $block_size + 1) * $block_size )) 460 echo "Usage size: $usage_size" 461 echo "Image size: $image_size" 462 truncate --size=$image_size "$out" 463 mkfs.vfat --invariant -i 12345678 -n EFIBOOT "$out" 464 465 # Force a fixed order in mcopy for better determinism, and avoid file globbing 466 for d in $(find EFI -type d | sort); do 467 faketime "2000-01-01 00:00:00" mmd -i "$out" "::/$d" 468 done 469 470 for f in $(find EFI -type f | sort); do 471 mcopy -pvm -i "$out" "$f" "::/$f" 472 done 473 474 # Verify the FAT partition. 475 fsck.vfat -vn "$out" 476 ''; # */ 477 478in 479 480{ 481 options = { 482 483 isoImage.isoName = mkOption { 484 default = "${config.isoImage.isoBaseName}.iso"; 485 type = lib.types.str; 486 description = '' 487 Name of the generated ISO image file. 488 ''; 489 }; 490 491 isoImage.isoBaseName = mkOption { 492 default = config.system.nixos.distroId; 493 type = lib.types.str; 494 description = '' 495 Prefix of the name of the generated ISO image file. 496 ''; 497 }; 498 499 isoImage.compressImage = mkOption { 500 default = false; 501 type = lib.types.bool; 502 description = '' 503 Whether the ISO image should be compressed using 504 {command}`zstd`. 505 ''; 506 }; 507 508 isoImage.squashfsCompression = mkOption { 509 default = with pkgs.stdenv.hostPlatform; "xz -Xdict-size 100% " 510 + lib.optionalString isx86 "-Xbcj x86" 511 # Untested but should also reduce size for these platforms 512 + lib.optionalString isAarch "-Xbcj arm" 513 + lib.optionalString (isPower && is32bit && isBigEndian) "-Xbcj powerpc" 514 + lib.optionalString (isSparc) "-Xbcj sparc"; 515 type = lib.types.nullOr lib.types.str; 516 description = '' 517 Compression settings to use for the squashfs nix store. 518 `null` disables compression. 519 ''; 520 example = "zstd -Xcompression-level 6"; 521 }; 522 523 isoImage.edition = mkOption { 524 default = ""; 525 type = lib.types.str; 526 description = '' 527 Specifies which edition string to use in the volume ID of the generated 528 ISO image. 529 ''; 530 }; 531 532 isoImage.volumeID = mkOption { 533 # nixos-$EDITION-$RELEASE-$ARCH 534 default = "nixos${optionalString (config.isoImage.edition != "") "-${config.isoImage.edition}"}-${config.system.nixos.release}-${pkgs.stdenv.hostPlatform.uname.processor}"; 535 type = lib.types.str; 536 description = '' 537 Specifies the label or volume ID of the generated ISO image. 538 Note that the label is used by stage 1 of the boot process to 539 mount the CD, so it should be reasonably distinctive. 540 ''; 541 }; 542 543 isoImage.contents = mkOption { 544 example = literalExpression '' 545 [ { source = pkgs.memtest86 + "/memtest.bin"; 546 target = "boot/memtest.bin"; 547 } 548 ] 549 ''; 550 description = '' 551 This option lists files to be copied to fixed locations in the 552 generated ISO image. 553 ''; 554 }; 555 556 isoImage.storeContents = mkOption { 557 example = literalExpression "[ pkgs.stdenv ]"; 558 description = '' 559 This option lists additional derivations to be included in the 560 Nix store in the generated ISO image. 561 ''; 562 }; 563 564 isoImage.includeSystemBuildDependencies = mkOption { 565 default = false; 566 type = lib.types.bool; 567 description = '' 568 Set this option to include all the needed sources etc in the 569 image. It significantly increases image size. Use that when 570 you want to be able to keep all the sources needed to build your 571 system or when you are going to install the system on a computer 572 with slow or non-existent network connection. 573 ''; 574 }; 575 576 isoImage.makeBiosBootable = mkOption { 577 # Before this option was introduced, images were BIOS-bootable if the 578 # hostPlatform was x86-based. This option is enabled by default for 579 # backwards compatibility. 580 # 581 # Also note that syslinux package currently cannot be cross-compiled from 582 # non-x86 platforms, so the default is false on non-x86 build platforms. 583 default = pkgs.stdenv.buildPlatform.isx86 && pkgs.stdenv.hostPlatform.isx86; 584 defaultText = lib.literalMD '' 585 `true` if both build and host platforms are x86-based architectures, 586 e.g. i686 and x86_64. 587 ''; 588 type = lib.types.bool; 589 description = '' 590 Whether the ISO image should be a BIOS-bootable disk. 591 ''; 592 }; 593 594 isoImage.makeEfiBootable = mkOption { 595 default = false; 596 type = lib.types.bool; 597 description = '' 598 Whether the ISO image should be an EFI-bootable volume. 599 ''; 600 }; 601 602 isoImage.makeUsbBootable = mkOption { 603 default = false; 604 type = lib.types.bool; 605 description = '' 606 Whether the ISO image should be bootable from CD as well as USB. 607 ''; 608 }; 609 610 isoImage.efiSplashImage = mkOption { 611 default = pkgs.fetchurl { 612 url = "https://raw.githubusercontent.com/NixOS/nixos-artwork/a9e05d7deb38a8e005a2b52575a3f59a63a4dba0/bootloader/efi-background.png"; 613 sha256 = "18lfwmp8yq923322nlb9gxrh5qikj1wsk6g5qvdh31c4h5b1538x"; 614 }; 615 description = '' 616 The splash image to use in the EFI bootloader. 617 ''; 618 }; 619 620 isoImage.splashImage = mkOption { 621 default = pkgs.fetchurl { 622 url = "https://raw.githubusercontent.com/NixOS/nixos-artwork/a9e05d7deb38a8e005a2b52575a3f59a63a4dba0/bootloader/isolinux/bios-boot.png"; 623 sha256 = "1wp822zrhbg4fgfbwkr7cbkr4labx477209agzc0hr6k62fr6rxd"; 624 }; 625 description = '' 626 The splash image to use in the legacy-boot bootloader. 627 ''; 628 }; 629 630 isoImage.grubTheme = mkOption { 631 default = pkgs.nixos-grub2-theme; 632 type = types.nullOr (types.either types.path types.package); 633 description = '' 634 The grub2 theme used for UEFI boot. 635 ''; 636 }; 637 638 isoImage.syslinuxTheme = mkOption { 639 default = '' 640 MENU TITLE ${config.system.nixos.distroName} 641 MENU RESOLUTION 800 600 642 MENU CLEAR 643 MENU ROWS 6 644 MENU CMDLINEROW -4 645 MENU TIMEOUTROW -3 646 MENU TABMSGROW -2 647 MENU HELPMSGROW -1 648 MENU HELPMSGENDROW -1 649 MENU MARGIN 0 650 651 # FG:AARRGGBB BG:AARRGGBB shadow 652 MENU COLOR BORDER 30;44 #00000000 #00000000 none 653 MENU COLOR SCREEN 37;40 #FF000000 #00E2E8FF none 654 MENU COLOR TABMSG 31;40 #80000000 #00000000 none 655 MENU COLOR TIMEOUT 1;37;40 #FF000000 #00000000 none 656 MENU COLOR TIMEOUT_MSG 37;40 #FF000000 #00000000 none 657 MENU COLOR CMDMARK 1;36;40 #FF000000 #00000000 none 658 MENU COLOR CMDLINE 37;40 #FF000000 #00000000 none 659 MENU COLOR TITLE 1;36;44 #00000000 #00000000 none 660 MENU COLOR UNSEL 37;44 #FF000000 #00000000 none 661 MENU COLOR SEL 7;37;40 #FFFFFFFF #FF5277C3 std 662 ''; 663 type = types.str; 664 description = '' 665 The syslinux theme used for BIOS boot. 666 ''; 667 }; 668 669 isoImage.prependToMenuLabel = mkOption { 670 default = ""; 671 type = types.str; 672 example = "Install "; 673 description = '' 674 The string to prepend before the menu label for the NixOS system. 675 This will be directly prepended (without whitespace) to the NixOS version 676 string, like for example if it is set to `XXX`: 677 678 `XXXNixOS 99.99-pre666` 679 ''; 680 }; 681 682 isoImage.appendToMenuLabel = mkOption { 683 default = " Installer"; 684 type = types.str; 685 example = " Live System"; 686 description = '' 687 The string to append after the menu label for the NixOS system. 688 This will be directly appended (without whitespace) to the NixOS version 689 string, like for example if it is set to `XXX`: 690 691 `NixOS 99.99-pre666XXX` 692 ''; 693 }; 694 695 isoImage.forceTextMode = mkOption { 696 default = false; 697 type = types.bool; 698 example = true; 699 description = '' 700 Whether to use text mode instead of graphical grub. 701 A value of `true` means graphical mode is not tried to be used. 702 703 This is useful for validating that graphics mode usage is not at the root cause of a problem with the iso image. 704 705 If text mode is required off-handedly (e.g. for serial use) you can use the `T` key, after being prompted, to use text mode for the current boot. 706 ''; 707 }; 708 709 }; 710 711 # store them in lib so we can mkImageMediaOverride the 712 # entire file system layout in installation media (only) 713 config.lib.isoFileSystems = { 714 "/" = mkImageMediaOverride 715 { 716 fsType = "tmpfs"; 717 options = [ "mode=0755" ]; 718 }; 719 720 # Note that /dev/root is a symlink to the actual root device 721 # specified on the kernel command line, created in the stage 1 722 # init script. 723 "/iso" = mkImageMediaOverride 724 { device = "/dev/root"; 725 neededForBoot = true; 726 noCheck = true; 727 }; 728 729 # In stage 1, mount a tmpfs on top of /nix/store (the squashfs 730 # image) to make this a live CD. 731 "/nix/.ro-store" = mkImageMediaOverride 732 { fsType = "squashfs"; 733 device = "/iso/nix-store.squashfs"; 734 options = [ "loop" ]; 735 neededForBoot = true; 736 }; 737 738 "/nix/.rw-store" = mkImageMediaOverride 739 { fsType = "tmpfs"; 740 options = [ "mode=0755" ]; 741 neededForBoot = true; 742 }; 743 744 "/nix/store" = mkImageMediaOverride 745 { fsType = "overlay"; 746 device = "overlay"; 747 options = [ 748 "lowerdir=/nix/.ro-store" 749 "upperdir=/nix/.rw-store/store" 750 "workdir=/nix/.rw-store/work" 751 ]; 752 depends = [ 753 "/nix/.ro-store" 754 "/nix/.rw-store/store" 755 "/nix/.rw-store/work" 756 ]; 757 }; 758 }; 759 760 config = { 761 assertions = [ 762 { 763 # Syslinux (and isolinux) only supports x86-based architectures. 764 assertion = config.isoImage.makeBiosBootable -> pkgs.stdenv.hostPlatform.isx86; 765 message = "BIOS boot is only supported on x86-based architectures."; 766 } 767 { 768 assertion = !(stringLength config.isoImage.volumeID > 32); 769 # https://wiki.osdev.org/ISO_9660#The_Primary_Volume_Descriptor 770 # Volume Identifier can only be 32 bytes 771 message = let 772 length = stringLength config.isoImage.volumeID; 773 howmany = toString length; 774 toomany = toString (length - 32); 775 in 776 "isoImage.volumeID ${config.isoImage.volumeID} is ${howmany} characters. That is ${toomany} characters longer than the limit of 32."; 777 } 778 ]; 779 780 # Don't build the GRUB menu builder script, since we don't need it 781 # here and it causes a cyclic dependency. 782 boot.loader.grub.enable = false; 783 784 environment.systemPackages = [ grubPkgs.grub2 grubPkgs.grub2_efi ] 785 ++ optional (config.isoImage.makeBiosBootable) pkgs.syslinux 786 ; 787 788 # In stage 1 of the boot, mount the CD as the root FS by label so 789 # that we don't need to know its device. We pass the label of the 790 # root filesystem on the kernel command line, rather than in 791 # `fileSystems' below. This allows CD-to-USB converters such as 792 # UNetbootin to rewrite the kernel command line to pass the label or 793 # UUID of the USB stick. It would be nicer to write 794 # `root=/dev/disk/by-label/...' here, but UNetbootin doesn't 795 # recognise that. 796 boot.kernelParams = 797 [ "root=LABEL=${config.isoImage.volumeID}" 798 "boot.shell_on_fail" 799 ]; 800 801 fileSystems = config.lib.isoFileSystems; 802 803 boot.initrd.availableKernelModules = [ "squashfs" "iso9660" "uas" "overlay" ]; 804 805 boot.initrd.kernelModules = [ "loop" "overlay" ]; 806 807 # Closures to be copied to the Nix store on the CD, namely the init 808 # script and the top-level system configuration directory. 809 isoImage.storeContents = 810 [ config.system.build.toplevel ] ++ 811 optional config.isoImage.includeSystemBuildDependencies 812 config.system.build.toplevel.drvPath; 813 814 # Individual files to be included on the CD, outside of the Nix 815 # store on the CD. 816 isoImage.contents = 817 [ 818 { source = config.boot.kernelPackages.kernel + "/" + config.system.boot.loader.kernelFile; 819 target = "/boot/" + config.system.boot.loader.kernelFile; 820 } 821 { source = config.system.build.initialRamdisk + "/" + config.system.boot.loader.initrdFile; 822 target = "/boot/" + config.system.boot.loader.initrdFile; 823 } 824 { source = pkgs.writeText "version" config.system.nixos.label; 825 target = "/version.txt"; 826 } 827 ] ++ optionals (config.isoImage.makeBiosBootable) [ 828 { source = config.isoImage.splashImage; 829 target = "/isolinux/background.png"; 830 } 831 { source = pkgs.substituteAll { 832 name = "isolinux.cfg"; 833 src = pkgs.writeText "isolinux.cfg-in" isolinuxCfg; 834 bootRoot = "/boot"; 835 }; 836 target = "/isolinux/isolinux.cfg"; 837 } 838 { source = "${pkgs.syslinux}/share/syslinux"; 839 target = "/isolinux"; 840 } 841 ] ++ optionals config.isoImage.makeEfiBootable [ 842 { source = efiImg; 843 target = "/boot/efi.img"; 844 } 845 { source = "${efiDir}/EFI"; 846 target = "/EFI"; 847 } 848 { source = (pkgs.writeTextDir "grub/loopback.cfg" "source /EFI/boot/grub.cfg") + "/grub"; 849 target = "/boot/grub"; 850 } 851 { source = config.isoImage.efiSplashImage; 852 target = "/EFI/boot/efi-background.png"; 853 } 854 ] ++ optionals (config.boot.loader.grub.memtest86.enable && config.isoImage.makeBiosBootable) [ 855 { source = "${pkgs.memtest86plus}/memtest.bin"; 856 target = "/boot/memtest.bin"; 857 } 858 ] ++ optionals (config.isoImage.grubTheme != null) [ 859 { source = config.isoImage.grubTheme; 860 target = "/EFI/boot/grub-theme"; 861 } 862 ]; 863 864 boot.loader.timeout = 10; 865 866 # Create the ISO image. 867 system.build.isoImage = pkgs.callPackage ../../../lib/make-iso9660-image.nix ({ 868 inherit (config.isoImage) isoName compressImage volumeID contents; 869 bootable = config.isoImage.makeBiosBootable; 870 bootImage = "/isolinux/isolinux.bin"; 871 syslinux = if config.isoImage.makeBiosBootable then pkgs.syslinux else null; 872 squashfsContents = config.isoImage.storeContents; 873 squashfsCompression = config.isoImage.squashfsCompression; 874 } // optionalAttrs (config.isoImage.makeUsbBootable && config.isoImage.makeBiosBootable) { 875 usbBootable = true; 876 isohybridMbrImage = "${pkgs.syslinux}/share/syslinux/isohdpfx.bin"; 877 } // optionalAttrs config.isoImage.makeEfiBootable { 878 efiBootable = true; 879 efiBootImage = "boot/efi.img"; 880 }); 881 882 boot.postBootCommands = 883 '' 884 # After booting, register the contents of the Nix store on the 885 # CD in the Nix database in the tmpfs. 886 ${config.nix.package.out}/bin/nix-store --load-db < /nix/store/nix-path-registration 887 888 # nixos-rebuild also requires a "system" profile and an 889 # /etc/NIXOS tag. 890 touch /etc/NIXOS 891 ${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system 892 ''; 893 894 # Add vfat support to the initrd to enable people to copy the 895 # contents of the CD to a bootable USB stick. 896 boot.initrd.supportedFilesystems = [ "vfat" ]; 897 898 }; 899 900}