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