nixos/preload: init
[NixPkgs.git] / nixos / modules / installer / cd-dvd / iso-image.nix
blob0b5135c088eafbe8da964efcd96eea04f9179284
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.
5 { config, lib, pkgs, ... }:
7 with lib;
9 let
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
30           linux ${defaults.image} \''${isoboot} ${defaults.params} ${
31             option.params or ""
32           }
33           initrd ${defaults.initrd}
34         }
35       '')
36       options
37     )
38   ;
40   /**
41    * Builds the default options.
42    */
43   buildMenuGrub2 = buildMenuAdditionalParamsGrub2 "";
45   targetArch =
46     if config.boot.loader.grub.forcei686 then
47       "ia32"
48     else
49       pkgs.stdenv.hostPlatform.efiArch;
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     };
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   ;
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;
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;
91   # The configuration file for syslinux.
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.
104   baseIsolinuxCfg = ''
105     SERIAL 0 115200
106     TIMEOUT ${builtins.toString syslinuxTimeout}
107     UI vesamenu.c32
108     MENU BACKGROUND /isolinux/background.png
110     ${config.isoImage.syslinuxTheme}
112     DEFAULT boot
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}
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}
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}
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}
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   '';
149   isolinuxMemtest86Entry = ''
150     LABEL memtest
151     MENU LABEL Memtest86+
152     LINUX /boot/memtest.bin
153     APPEND ${toString config.boot.loader.grub.memtest86.params}
154   '';
156   isolinuxCfg = concatStringsSep "\n"
157     ([ baseIsolinuxCfg ] ++ optional config.boot.loader.grub.memtest86.enable isolinuxMemtest86Entry);
159   refindBinary = if targetArch == "x64" || targetArch == "aa64" then "refind_${targetArch}.efi" else null;
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   ;
172   grubPkgs = if config.boot.loader.grub.forcei686 then pkgs.pkgsi686Linux else pkgs;
174   grubMenuCfg = ''
175     #
176     # Menu configuration
177     #
179     # Search using a "marker file"
180     search --set=root --file /EFI/nixos-installer-image
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     ]}
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
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   '';
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/
244     # Add a marker so GRUB can find the filesystem.
245     touch $out/EFI/nixos-installer-image
247     # ALWAYS required modules.
248     MODULES=(
249       # Basic modules for filesystems and partition schemes
250       "fat"
251       "iso9660"
252       "part_gpt"
253       "part_msdos"
255       # Basic stuff
256       "normal"
257       "boot"
258       "linux"
259       "configfile"
260       "loopback"
261       "chain"
262       "halt"
264       # Allows rebooting into firmware setup interface
265       "efifwsetup"
267       # EFI Graphics Output Protocol
268       "efi_gop"
270       # User commands
271       "ls"
273       # System commands
274       "search"
275       "search_label"
276       "search_fs_uuid"
277       "search_fs_file"
278       "echo"
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"
284       # Graphical mode stuff
285       "gfxmenu"
286       "gfxterm"
287       "gfxterm_background"
288       "gfxterm_menu"
289       "test"
290       "loadenv"
291       "all_video"
292       "videoinfo"
294       # File types for graphical mode
295       "png"
296     )
298     echo "Building GRUB with modules:"
299     for mod in ''${MODULES[@]}; do
300       echo " - $mod"
301     done
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
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/
322     cat <<EOF > $out/EFI/boot/grub.cfg
324     set textmode=${boolToString (config.isoImage.forceTextMode)}
325     set timeout=${toString grubEfiTimeout}
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 ""
335     ${grubMenuCfg}
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     }
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
355     #
356     # Menu entries
357     #
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       }
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       }
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       }
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       }
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     }
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
434     grub-script-check $out/EFI/boot/grub.cfg
436     ${refind}
437   '';
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
450       # Rewrite dates for everything in the FS
451       find . -exec touch --date=2000-01-01 {} +
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"
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
470       for f in $(find EFI -type f | sort); do
471         mcopy -pvm -i "$out" "$f" "::/$f"
472       done
474       # Verify the FAT partition.
475       fsck.vfat -vn "$out"
476     ''; # */
481   options = {
483     isoImage.isoName = mkOption {
484       default = "${config.isoImage.isoBaseName}.iso";
485       type = lib.types.str;
486       description = lib.mdDoc ''
487         Name of the generated ISO image file.
488       '';
489     };
491     isoImage.isoBaseName = mkOption {
492       default = config.system.nixos.distroId;
493       type = lib.types.str;
494       description = lib.mdDoc ''
495         Prefix of the name of the generated ISO image file.
496       '';
497     };
499     isoImage.compressImage = mkOption {
500       default = false;
501       type = lib.types.bool;
502       description = lib.mdDoc ''
503         Whether the ISO image should be compressed using
504         {command}`zstd`.
505       '';
506     };
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.str;
516       description = lib.mdDoc ''
517         Compression settings to use for the squashfs nix store.
518       '';
519       example = "zstd -Xcompression-level 6";
520     };
522     isoImage.edition = mkOption {
523       default = "";
524       type = lib.types.str;
525       description = lib.mdDoc ''
526         Specifies which edition string to use in the volume ID of the generated
527         ISO image.
528       '';
529     };
531     isoImage.volumeID = mkOption {
532       # nixos-$EDITION-$RELEASE-$ARCH
533       default = "nixos${optionalString (config.isoImage.edition != "") "-${config.isoImage.edition}"}-${config.system.nixos.release}-${pkgs.stdenv.hostPlatform.uname.processor}";
534       type = lib.types.str;
535       description = lib.mdDoc ''
536         Specifies the label or volume ID of the generated ISO image.
537         Note that the label is used by stage 1 of the boot process to
538         mount the CD, so it should be reasonably distinctive.
539       '';
540     };
542     isoImage.contents = mkOption {
543       example = literalExpression ''
544         [ { source = pkgs.memtest86 + "/memtest.bin";
545             target = "boot/memtest.bin";
546           }
547         ]
548       '';
549       description = lib.mdDoc ''
550         This option lists files to be copied to fixed locations in the
551         generated ISO image.
552       '';
553     };
555     isoImage.storeContents = mkOption {
556       example = literalExpression "[ pkgs.stdenv ]";
557       description = lib.mdDoc ''
558         This option lists additional derivations to be included in the
559         Nix store in the generated ISO image.
560       '';
561     };
563     isoImage.includeSystemBuildDependencies = mkOption {
564       default = false;
565       type = lib.types.bool;
566       description = lib.mdDoc ''
567         Set this option to include all the needed sources etc in the
568         image. It significantly increases image size. Use that when
569         you want to be able to keep all the sources needed to build your
570         system or when you are going to install the system on a computer
571         with slow or non-existent network connection.
572       '';
573     };
575     isoImage.makeBiosBootable = mkOption {
576       # Before this option was introduced, images were BIOS-bootable if the
577       # hostPlatform was x86-based. This option is enabled by default for
578       # backwards compatibility.
579       #
580       # Also note that syslinux package currently cannot be cross-compiled from
581       # non-x86 platforms, so the default is false on non-x86 build platforms.
582       default = pkgs.stdenv.buildPlatform.isx86 && pkgs.stdenv.hostPlatform.isx86;
583       defaultText = lib.literalMD ''
584         `true` if both build and host platforms are x86-based architectures,
585         e.g. i686 and x86_64.
586       '';
587       type = lib.types.bool;
588       description = lib.mdDoc ''
589         Whether the ISO image should be a BIOS-bootable disk.
590       '';
591     };
593     isoImage.makeEfiBootable = mkOption {
594       default = false;
595       type = lib.types.bool;
596       description = lib.mdDoc ''
597         Whether the ISO image should be an EFI-bootable volume.
598       '';
599     };
601     isoImage.makeUsbBootable = mkOption {
602       default = false;
603       type = lib.types.bool;
604       description = lib.mdDoc ''
605         Whether the ISO image should be bootable from CD as well as USB.
606       '';
607     };
609     isoImage.efiSplashImage = mkOption {
610       default = pkgs.fetchurl {
611           url = "https://raw.githubusercontent.com/NixOS/nixos-artwork/a9e05d7deb38a8e005a2b52575a3f59a63a4dba0/bootloader/efi-background.png";
612           sha256 = "18lfwmp8yq923322nlb9gxrh5qikj1wsk6g5qvdh31c4h5b1538x";
613         };
614       description = lib.mdDoc ''
615         The splash image to use in the EFI bootloader.
616       '';
617     };
619     isoImage.splashImage = mkOption {
620       default = pkgs.fetchurl {
621           url = "https://raw.githubusercontent.com/NixOS/nixos-artwork/a9e05d7deb38a8e005a2b52575a3f59a63a4dba0/bootloader/isolinux/bios-boot.png";
622           sha256 = "1wp822zrhbg4fgfbwkr7cbkr4labx477209agzc0hr6k62fr6rxd";
623         };
624       description = lib.mdDoc ''
625         The splash image to use in the legacy-boot bootloader.
626       '';
627     };
629     isoImage.grubTheme = mkOption {
630       default = pkgs.nixos-grub2-theme;
631       type = types.nullOr (types.either types.path types.package);
632       description = lib.mdDoc ''
633         The grub2 theme used for UEFI boot.
634       '';
635     };
637     isoImage.syslinuxTheme = mkOption {
638       default = ''
639         MENU TITLE ${config.system.nixos.distroName}
640         MENU RESOLUTION 800 600
641         MENU CLEAR
642         MENU ROWS 6
643         MENU CMDLINEROW -4
644         MENU TIMEOUTROW -3
645         MENU TABMSGROW  -2
646         MENU HELPMSGROW -1
647         MENU HELPMSGENDROW -1
648         MENU MARGIN 0
650         #                                FG:AARRGGBB  BG:AARRGGBB   shadow
651         MENU COLOR BORDER       30;44      #00000000    #00000000   none
652         MENU COLOR SCREEN       37;40      #FF000000    #00E2E8FF   none
653         MENU COLOR TABMSG       31;40      #80000000    #00000000   none
654         MENU COLOR TIMEOUT      1;37;40    #FF000000    #00000000   none
655         MENU COLOR TIMEOUT_MSG  37;40      #FF000000    #00000000   none
656         MENU COLOR CMDMARK      1;36;40    #FF000000    #00000000   none
657         MENU COLOR CMDLINE      37;40      #FF000000    #00000000   none
658         MENU COLOR TITLE        1;36;44    #00000000    #00000000   none
659         MENU COLOR UNSEL        37;44      #FF000000    #00000000   none
660         MENU COLOR SEL          7;37;40    #FFFFFFFF    #FF5277C3   std
661       '';
662       type = types.str;
663       description = lib.mdDoc ''
664         The syslinux theme used for BIOS boot.
665       '';
666     };
668     isoImage.prependToMenuLabel = mkOption {
669       default = "";
670       type = types.str;
671       example = "Install ";
672       description = lib.mdDoc ''
673         The string to prepend before the menu label for the NixOS system.
674         This will be directly prepended (without whitespace) to the NixOS version
675         string, like for example if it is set to `XXX`:
677         `XXXNixOS 99.99-pre666`
678       '';
679     };
681     isoImage.appendToMenuLabel = mkOption {
682       default = " Installer";
683       type = types.str;
684       example = " Live System";
685       description = lib.mdDoc ''
686         The string to append after the menu label for the NixOS system.
687         This will be directly appended (without whitespace) to the NixOS version
688         string, like for example if it is set to `XXX`:
690         `NixOS 99.99-pre666XXX`
691       '';
692     };
694     isoImage.forceTextMode = mkOption {
695       default = false;
696       type = types.bool;
697       example = true;
698       description = lib.mdDoc ''
699         Whether to use text mode instead of graphical grub.
700         A value of `true` means graphical mode is not tried to be used.
702         This is useful for validating that graphics mode usage is not at the root cause of a problem with the iso image.
704         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.
705       '';
706     };
708   };
710   # store them in lib so we can mkImageMediaOverride the
711   # entire file system layout in installation media (only)
712   config.lib.isoFileSystems = {
713     "/" = mkImageMediaOverride
714       {
715         fsType = "tmpfs";
716         options = [ "mode=0755" ];
717       };
719     # Note that /dev/root is a symlink to the actual root device
720     # specified on the kernel command line, created in the stage 1
721     # init script.
722     "/iso" = mkImageMediaOverride
723       { device = "/dev/root";
724         neededForBoot = true;
725         noCheck = true;
726       };
728     # In stage 1, mount a tmpfs on top of /nix/store (the squashfs
729     # image) to make this a live CD.
730     "/nix/.ro-store" = mkImageMediaOverride
731       { fsType = "squashfs";
732         device = "/iso/nix-store.squashfs";
733         options = [ "loop" ];
734         neededForBoot = true;
735       };
737     "/nix/.rw-store" = mkImageMediaOverride
738       { fsType = "tmpfs";
739         options = [ "mode=0755" ];
740         neededForBoot = true;
741       };
743     "/nix/store" = mkImageMediaOverride
744       { fsType = "overlay";
745         device = "overlay";
746         options = [
747           "lowerdir=/nix/.ro-store"
748           "upperdir=/nix/.rw-store/store"
749           "workdir=/nix/.rw-store/work"
750         ];
751         depends = [
752           "/nix/.ro-store"
753           "/nix/.rw-store/store"
754           "/nix/.rw-store/work"
755         ];
756       };
757   };
759   config = {
760     assertions = [
761       {
762         # Syslinux (and isolinux) only supports x86-based architectures.
763         assertion = config.isoImage.makeBiosBootable -> pkgs.stdenv.hostPlatform.isx86;
764         message = "BIOS boot is only supported on x86-based architectures.";
765       }
766       {
767         assertion = !(stringLength config.isoImage.volumeID > 32);
768         # https://wiki.osdev.org/ISO_9660#The_Primary_Volume_Descriptor
769         # Volume Identifier can only be 32 bytes
770         message = let
771           length = stringLength config.isoImage.volumeID;
772           howmany = toString length;
773           toomany = toString (length - 32);
774         in
775         "isoImage.volumeID ${config.isoImage.volumeID} is ${howmany} characters. That is ${toomany} characters longer than the limit of 32.";
776       }
777     ];
779     # Don't build the GRUB menu builder script, since we don't need it
780     # here and it causes a cyclic dependency.
781     boot.loader.grub.enable = false;
783     environment.systemPackages =  [ grubPkgs.grub2 grubPkgs.grub2_efi ]
784       ++ optional (config.isoImage.makeBiosBootable) pkgs.syslinux
785     ;
787     # In stage 1 of the boot, mount the CD as the root FS by label so
788     # that we don't need to know its device.  We pass the label of the
789     # root filesystem on the kernel command line, rather than in
790     # `fileSystems' below.  This allows CD-to-USB converters such as
791     # UNetbootin to rewrite the kernel command line to pass the label or
792     # UUID of the USB stick.  It would be nicer to write
793     # `root=/dev/disk/by-label/...' here, but UNetbootin doesn't
794     # recognise that.
795     boot.kernelParams =
796       [ "root=LABEL=${config.isoImage.volumeID}"
797         "boot.shell_on_fail"
798       ];
800     fileSystems = config.lib.isoFileSystems;
802     boot.initrd.availableKernelModules = [ "squashfs" "iso9660" "uas" "overlay" ];
804     boot.initrd.kernelModules = [ "loop" "overlay" ];
806     # Closures to be copied to the Nix store on the CD, namely the init
807     # script and the top-level system configuration directory.
808     isoImage.storeContents =
809       [ config.system.build.toplevel ] ++
810       optional config.isoImage.includeSystemBuildDependencies
811         config.system.build.toplevel.drvPath;
813     # Create the squashfs image that contains the Nix store.
814     system.build.squashfsStore = pkgs.callPackage ../../../lib/make-squashfs.nix {
815       storeContents = config.isoImage.storeContents;
816       comp = config.isoImage.squashfsCompression;
817     };
819     # Individual files to be included on the CD, outside of the Nix
820     # store on the CD.
821     isoImage.contents =
822       [
823         { source = config.boot.kernelPackages.kernel + "/" + config.system.boot.loader.kernelFile;
824           target = "/boot/" + config.system.boot.loader.kernelFile;
825         }
826         { source = config.system.build.initialRamdisk + "/" + config.system.boot.loader.initrdFile;
827           target = "/boot/" + config.system.boot.loader.initrdFile;
828         }
829         { source = config.system.build.squashfsStore;
830           target = "/nix-store.squashfs";
831         }
832         { source = pkgs.writeText "version" config.system.nixos.label;
833           target = "/version.txt";
834         }
835       ] ++ optionals (config.isoImage.makeBiosBootable) [
836         { source = config.isoImage.splashImage;
837           target = "/isolinux/background.png";
838         }
839         { source = pkgs.substituteAll  {
840             name = "isolinux.cfg";
841             src = pkgs.writeText "isolinux.cfg-in" isolinuxCfg;
842             bootRoot = "/boot";
843           };
844           target = "/isolinux/isolinux.cfg";
845         }
846         { source = "${pkgs.syslinux}/share/syslinux";
847           target = "/isolinux";
848         }
849       ] ++ optionals config.isoImage.makeEfiBootable [
850         { source = efiImg;
851           target = "/boot/efi.img";
852         }
853         { source = "${efiDir}/EFI";
854           target = "/EFI";
855         }
856         { source = (pkgs.writeTextDir "grub/loopback.cfg" "source /EFI/boot/grub.cfg") + "/grub";
857           target = "/boot/grub";
858         }
859         { source = config.isoImage.efiSplashImage;
860           target = "/EFI/boot/efi-background.png";
861         }
862       ] ++ optionals (config.boot.loader.grub.memtest86.enable && config.isoImage.makeBiosBootable) [
863         { source = "${pkgs.memtest86plus}/memtest.bin";
864           target = "/boot/memtest.bin";
865         }
866       ] ++ optionals (config.isoImage.grubTheme != null) [
867         { source = config.isoImage.grubTheme;
868           target = "/EFI/boot/grub-theme";
869         }
870       ];
872     boot.loader.timeout = 10;
874     # Create the ISO image.
875     system.build.isoImage = pkgs.callPackage ../../../lib/make-iso9660-image.nix ({
876       inherit (config.isoImage) isoName compressImage volumeID contents;
877       bootable = config.isoImage.makeBiosBootable;
878       bootImage = "/isolinux/isolinux.bin";
879       syslinux = if config.isoImage.makeBiosBootable then pkgs.syslinux else null;
880     } // optionalAttrs (config.isoImage.makeUsbBootable && config.isoImage.makeBiosBootable) {
881       usbBootable = true;
882       isohybridMbrImage = "${pkgs.syslinux}/share/syslinux/isohdpfx.bin";
883     } // optionalAttrs config.isoImage.makeEfiBootable {
884       efiBootable = true;
885       efiBootImage = "boot/efi.img";
886     });
888     boot.postBootCommands =
889       ''
890         # After booting, register the contents of the Nix store on the
891         # CD in the Nix database in the tmpfs.
892         ${config.nix.package.out}/bin/nix-store --load-db < /nix/store/nix-path-registration
894         # nixos-rebuild also requires a "system" profile and an
895         # /etc/NIXOS tag.
896         touch /etc/NIXOS
897         ${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system
898       '';
900     # Add vfat support to the initrd to enable people to copy the
901     # contents of the CD to a bootable USB stick.
902     boot.initrd.supportedFilesystems = [ "vfat" ];
904   };