Merge #361424: refactor lib.packagesFromDirectoryRecursive (v2)
[NixPkgs.git] / nixos / modules / installer / cd-dvd / iso-image.nix
blobcf2cdb8eecef008d57269dc8ab8a026f51a671d8
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     }
339     ${lib.optionalString (config.isoImage.grubTheme != null) ''
340     hiddenentry 'GUI mode' --hotkey 'g' {
341       $(find ${config.isoImage.grubTheme} -iname '*.pf2' -printf "loadfont (\$root)/EFI/BOOT/grub-theme/%P\n")
342       set textmode=false
343       terminal_output gfxterm
344     }
345     ''}
347     # If the parameter iso_path is set, append the findiso parameter to the kernel
348     # line. We need this to allow the nixos iso to be booted from grub directly.
349     if [ \''${iso_path} ] ; then
350       set isoboot="findiso=\''${iso_path}"
351     fi
353     #
354     # Menu entries
355     #
357     ${buildMenuGrub2}
358     submenu "HiDPI, Quirks and Accessibility" --class hidpi --class submenu {
359       ${grubMenuCfg}
360       submenu "Suggests resolution @720p" --class hidpi-720p {
361         ${grubMenuCfg}
362         ${buildMenuAdditionalParamsGrub2 "video=1280x720@60"}
363       }
364       submenu "Suggests resolution @1080p" --class hidpi-1080p {
365         ${grubMenuCfg}
366         ${buildMenuAdditionalParamsGrub2 "video=1920x1080@60"}
367       }
369       # If we boot into a graphical environment where X is autoran
370       # and always crashes, it makes the media unusable. Allow the user
371       # to disable this.
372       submenu "Disable display-manager" --class quirk-disable-displaymanager {
373         ${grubMenuCfg}
374         ${buildMenuAdditionalParamsGrub2 "systemd.mask=display-manager.service"}
375       }
377       # Some laptop and convertibles have the panel installed in an
378       # inconvenient way, rotated away from the keyboard.
379       # Those entries makes it easier to use the installer.
380       submenu "" {return}
381       submenu "Rotate framebuffer Clockwise" --class rotate-90cw {
382         ${grubMenuCfg}
383         ${buildMenuAdditionalParamsGrub2 "fbcon=rotate:1"}
384       }
385       submenu "Rotate framebuffer Upside-Down" --class rotate-180 {
386         ${grubMenuCfg}
387         ${buildMenuAdditionalParamsGrub2 "fbcon=rotate:2"}
388       }
389       submenu "Rotate framebuffer Counter-Clockwise" --class rotate-90ccw {
390         ${grubMenuCfg}
391         ${buildMenuAdditionalParamsGrub2 "fbcon=rotate:3"}
392       }
394       # As a proof of concept, mainly. (Not sure it has accessibility merits.)
395       submenu "" {return}
396       submenu "Use black on white" --class accessibility-blakconwhite {
397         ${grubMenuCfg}
398         ${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"}
399       }
401       # Serial access is a must!
402       submenu "" {return}
403       submenu "Serial console=ttyS0,115200n8" --class serial {
404         ${grubMenuCfg}
405         ${buildMenuAdditionalParamsGrub2 "console=ttyS0,115200n8"}
406       }
407     }
409     ${lib.optionalString (refindBinary != null) ''
410     # GRUB apparently cannot do "chainloader" operations on "CD".
411     if [ "\$root" != "cd0" ]; then
412       menuentry 'rEFInd' --class refind {
413         # Force root to be the FAT partition
414         # Otherwise it breaks rEFInd's boot
415         search --set=root --no-floppy --fs-uuid 1234-5678
416         chainloader (\$root)/EFI/BOOT/${refindBinary}
417       }
418     fi
419     ''}
420     menuentry 'Firmware Setup' --class settings {
421       fwsetup
422       clear
423       echo ""
424       echo "If you see this message, your EFI system doesn't support this feature."
425       echo ""
426     }
427     menuentry 'Shutdown' --class shutdown {
428       halt
429     }
430     EOF
432     grub-script-check $out/EFI/BOOT/grub.cfg
434     ${refind}
435   '';
437   efiImg = pkgs.runCommand "efi-image_eltorito" {
438     nativeBuildInputs = [ pkgs.buildPackages.mtools pkgs.buildPackages.libfaketime pkgs.buildPackages.dosfstools ];
439     strictDeps = true;
440   }
441     # Be careful about determinism: du --apparent-size,
442     #   dates (cp -p, touch, mcopy -m, faketime for label), IDs (mkfs.vfat -i)
443     ''
444       mkdir ./contents && cd ./contents
445       mkdir -p ./EFI/BOOT
446       cp -rp "${efiDir}"/EFI/BOOT/{grub.cfg,*.EFI,*.efi} ./EFI/BOOT
448       # Rewrite dates for everything in the FS
449       find . -exec touch --date=2000-01-01 {} +
451       # Round up to the nearest multiple of 1MB, for more deterministic du output
452       usage_size=$(( $(du -s --block-size=1M --apparent-size . | tr -cd '[:digit:]') * 1024 * 1024 ))
453       # Make the image 110% as big as the files need to make up for FAT overhead
454       image_size=$(( ($usage_size * 110) / 100 ))
455       # Make the image fit blocks of 1M
456       block_size=$((1024*1024))
457       image_size=$(( ($image_size / $block_size + 1) * $block_size ))
458       echo "Usage size: $usage_size"
459       echo "Image size: $image_size"
460       truncate --size=$image_size "$out"
461       mkfs.vfat --invariant -i 12345678 -n EFIBOOT "$out"
463       # Force a fixed order in mcopy for better determinism, and avoid file globbing
464       for d in $(find EFI -type d | sort); do
465         faketime "2000-01-01 00:00:00" mmd -i "$out" "::/$d"
466       done
468       for f in $(find EFI -type f | sort); do
469         mcopy -pvm -i "$out" "$f" "::/$f"
470       done
472       # Verify the FAT partition.
473       fsck.vfat -vn "$out"
474     ''; # */
479   options = {
481     isoImage.isoName = lib.mkOption {
482       default = "${config.isoImage.isoBaseName}.iso";
483       type = lib.types.str;
484       description = ''
485         Name of the generated ISO image file.
486       '';
487     };
489     isoImage.isoBaseName = lib.mkOption {
490       default = config.system.nixos.distroId;
491       type = lib.types.str;
492       description = ''
493         Prefix of the name of the generated ISO image file.
494       '';
495     };
497     isoImage.compressImage = lib.mkOption {
498       default = false;
499       type = lib.types.bool;
500       description = ''
501         Whether the ISO image should be compressed using
502         {command}`zstd`.
503       '';
504     };
506     isoImage.squashfsCompression = lib.mkOption {
507       default = "zstd -Xcompression-level 19";
508       type = lib.types.nullOr lib.types.str;
509       description = ''
510         Compression settings to use for the squashfs nix store.
511         `null` disables compression.
512       '';
513       example = "zstd -Xcompression-level 6";
514     };
516     isoImage.edition = lib.mkOption {
517       default = "";
518       type = lib.types.str;
519       description = ''
520         Specifies which edition string to use in the volume ID of the generated
521         ISO image.
522       '';
523     };
525     isoImage.volumeID = lib.mkOption {
526       # nixos-$EDITION-$RELEASE-$ARCH
527       default = "nixos${lib.optionalString (config.isoImage.edition != "") "-${config.isoImage.edition}"}-${config.system.nixos.release}-${pkgs.stdenv.hostPlatform.uname.processor}";
528       type = lib.types.str;
529       description = ''
530         Specifies the label or volume ID of the generated ISO image.
531         Note that the label is used by stage 1 of the boot process to
532         mount the CD, so it should be reasonably distinctive.
533       '';
534     };
536     isoImage.contents = lib.mkOption {
537       example = lib.literalExpression ''
538         [ { source = pkgs.memtest86 + "/memtest.bin";
539             target = "boot/memtest.bin";
540           }
541         ]
542       '';
543       description = ''
544         This option lists files to be copied to fixed locations in the
545         generated ISO image.
546       '';
547     };
549     isoImage.storeContents = lib.mkOption {
550       example = lib.literalExpression "[ pkgs.stdenv ]";
551       description = ''
552         This option lists additional derivations to be included in the
553         Nix store in the generated ISO image.
554       '';
555     };
557     isoImage.includeSystemBuildDependencies = lib.mkOption {
558       default = false;
559       type = lib.types.bool;
560       description = ''
561         Set this option to include all the needed sources etc in the
562         image. It significantly increases image size. Use that when
563         you want to be able to keep all the sources needed to build your
564         system or when you are going to install the system on a computer
565         with slow or non-existent network connection.
566       '';
567     };
569     isoImage.makeBiosBootable = lib.mkOption {
570       # Before this option was introduced, images were BIOS-bootable if the
571       # hostPlatform was x86-based. This option is enabled by default for
572       # backwards compatibility.
573       #
574       # Also note that syslinux package currently cannot be cross-compiled from
575       # non-x86 platforms, so the default is false on non-x86 build platforms.
576       default = pkgs.stdenv.buildPlatform.isx86 && pkgs.stdenv.hostPlatform.isx86;
577       defaultText = lib.literalMD ''
578         `true` if both build and host platforms are x86-based architectures,
579         e.g. i686 and x86_64.
580       '';
581       type = lib.types.bool;
582       description = ''
583         Whether the ISO image should be a BIOS-bootable disk.
584       '';
585     };
587     isoImage.makeEfiBootable = lib.mkOption {
588       default = false;
589       type = lib.types.bool;
590       description = ''
591         Whether the ISO image should be an EFI-bootable volume.
592       '';
593     };
595     isoImage.makeUsbBootable = lib.mkOption {
596       default = false;
597       type = lib.types.bool;
598       description = ''
599         Whether the ISO image should be bootable from CD as well as USB.
600       '';
601     };
603     isoImage.efiSplashImage = lib.mkOption {
604       default = pkgs.fetchurl {
605           url = "https://raw.githubusercontent.com/NixOS/nixos-artwork/a9e05d7deb38a8e005a2b52575a3f59a63a4dba0/bootloader/efi-background.png";
606           sha256 = "18lfwmp8yq923322nlb9gxrh5qikj1wsk6g5qvdh31c4h5b1538x";
607         };
608       description = ''
609         The splash image to use in the EFI bootloader.
610       '';
611     };
613     isoImage.splashImage = lib.mkOption {
614       default = pkgs.fetchurl {
615           url = "https://raw.githubusercontent.com/NixOS/nixos-artwork/a9e05d7deb38a8e005a2b52575a3f59a63a4dba0/bootloader/isolinux/bios-boot.png";
616           sha256 = "1wp822zrhbg4fgfbwkr7cbkr4labx477209agzc0hr6k62fr6rxd";
617         };
618       description = ''
619         The splash image to use in the legacy-boot bootloader.
620       '';
621     };
623     isoImage.grubTheme = lib.mkOption {
624       default = pkgs.nixos-grub2-theme;
625       type = lib.types.nullOr (lib.types.either lib.types.path lib.types.package);
626       description = ''
627         The grub2 theme used for UEFI boot.
628       '';
629     };
631     isoImage.syslinuxTheme = lib.mkOption {
632       default = ''
633         MENU TITLE ${config.system.nixos.distroName}
634         MENU RESOLUTION 800 600
635         MENU CLEAR
636         MENU ROWS 6
637         MENU CMDLINEROW -4
638         MENU TIMEOUTROW -3
639         MENU TABMSGROW  -2
640         MENU HELPMSGROW -1
641         MENU HELPMSGENDROW -1
642         MENU MARGIN 0
644         #                                FG:AARRGGBB  BG:AARRGGBB   shadow
645         MENU COLOR BORDER       30;44      #00000000    #00000000   none
646         MENU COLOR SCREEN       37;40      #FF000000    #00E2E8FF   none
647         MENU COLOR TABMSG       31;40      #80000000    #00000000   none
648         MENU COLOR TIMEOUT      1;37;40    #FF000000    #00000000   none
649         MENU COLOR TIMEOUT_MSG  37;40      #FF000000    #00000000   none
650         MENU COLOR CMDMARK      1;36;40    #FF000000    #00000000   none
651         MENU COLOR CMDLINE      37;40      #FF000000    #00000000   none
652         MENU COLOR TITLE        1;36;44    #00000000    #00000000   none
653         MENU COLOR UNSEL        37;44      #FF000000    #00000000   none
654         MENU COLOR SEL          7;37;40    #FFFFFFFF    #FF5277C3   std
655       '';
656       type = lib.types.str;
657       description = ''
658         The syslinux theme used for BIOS boot.
659       '';
660     };
662     isoImage.prependToMenuLabel = lib.mkOption {
663       default = "";
664       type = lib.types.str;
665       example = "Install ";
666       description = ''
667         The string to prepend before the menu label for the NixOS system.
668         This will be directly prepended (without whitespace) to the NixOS version
669         string, like for example if it is set to `XXX`:
671         `XXXNixOS 99.99-pre666`
672       '';
673     };
675     isoImage.appendToMenuLabel = lib.mkOption {
676       default = " Installer";
677       type = lib.types.str;
678       example = " Live System";
679       description = ''
680         The string to append after the menu label for the NixOS system.
681         This will be directly appended (without whitespace) to the NixOS version
682         string, like for example if it is set to `XXX`:
684         `NixOS 99.99-pre666XXX`
685       '';
686     };
688     isoImage.forceTextMode = lib.mkOption {
689       default = false;
690       type = lib.types.bool;
691       example = true;
692       description = ''
693         Whether to use text mode instead of graphical grub.
694         A value of `true` means graphical mode is not tried to be used.
696         This is useful for validating that graphics mode usage is not at the root cause of a problem with the iso image.
698         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.
699       '';
700     };
702   };
704   # store them in lib so we can mkImageMediaOverride the
705   # entire file system layout in installation media (only)
706   config.lib.isoFileSystems = {
707     "/" = lib.mkImageMediaOverride
708       {
709         fsType = "tmpfs";
710         options = [ "mode=0755" ];
711       };
713     # Note that /dev/root is a symlink to the actual root device
714     # specified on the kernel command line, created in the stage 1
715     # init script.
716     "/iso" = lib.mkImageMediaOverride
717       { device = "/dev/root";
718         neededForBoot = true;
719         noCheck = true;
720       };
722     # In stage 1, mount a tmpfs on top of /nix/store (the squashfs
723     # image) to make this a live CD.
724     "/nix/.ro-store" = lib.mkImageMediaOverride
725       { fsType = "squashfs";
726         device = "/iso/nix-store.squashfs";
727         options = [ "loop" ] ++ lib.optional (config.boot.kernelPackages.kernel.kernelAtLeast "6.2") "threads=multi";
728         neededForBoot = true;
729       };
731     "/nix/.rw-store" = lib.mkImageMediaOverride
732       { fsType = "tmpfs";
733         options = [ "mode=0755" ];
734         neededForBoot = true;
735       };
737     "/nix/store" = lib.mkImageMediaOverride
738       { fsType = "overlay";
739         device = "overlay";
740         options = [
741           "lowerdir=/nix/.ro-store"
742           "upperdir=/nix/.rw-store/store"
743           "workdir=/nix/.rw-store/work"
744         ];
745         depends = [
746           "/nix/.ro-store"
747           "/nix/.rw-store/store"
748           "/nix/.rw-store/work"
749         ];
750       };
751   };
753   config = {
754     assertions = [
755       {
756         # Syslinux (and isolinux) only supports x86-based architectures.
757         assertion = config.isoImage.makeBiosBootable -> pkgs.stdenv.hostPlatform.isx86;
758         message = "BIOS boot is only supported on x86-based architectures.";
759       }
760       {
761         assertion = !(lib.stringLength config.isoImage.volumeID > 32);
762         # https://wiki.osdev.org/ISO_9660#The_Primary_Volume_Descriptor
763         # Volume Identifier can only be 32 bytes
764         message = let
765           length = lib.stringLength config.isoImage.volumeID;
766           howmany = toString length;
767           toomany = toString (length - 32);
768         in
769         "isoImage.volumeID ${config.isoImage.volumeID} is ${howmany} characters. That is ${toomany} characters longer than the limit of 32.";
770       }
771     ];
773     # Don't build the GRUB menu builder script, since we don't need it
774     # here and it causes a cyclic dependency.
775     boot.loader.grub.enable = false;
777     environment.systemPackages =  [ grubPkgs.grub2 ]
778       ++ lib.optional (config.isoImage.makeBiosBootable) pkgs.syslinux
779     ;
780     system.extraDependencies = [ grubPkgs.grub2_efi ];
782     # In stage 1 of the boot, mount the CD as the root FS by label so
783     # that we don't need to know its device.  We pass the label of the
784     # root filesystem on the kernel command line, rather than in
785     # `fileSystems' below.  This allows CD-to-USB converters such as
786     # UNetbootin to rewrite the kernel command line to pass the label or
787     # UUID of the USB stick.  It would be nicer to write
788     # `root=/dev/disk/by-label/...' here, but UNetbootin doesn't
789     # recognise that.
790     boot.kernelParams =
791       [ "root=LABEL=${config.isoImage.volumeID}"
792         "boot.shell_on_fail"
793       ];
795     fileSystems = config.lib.isoFileSystems;
797     boot.initrd.availableKernelModules = [ "squashfs" "iso9660" "uas" "overlay" ];
799     boot.initrd.kernelModules = [ "loop" "overlay" ];
801     # Closures to be copied to the Nix store on the CD, namely the init
802     # script and the top-level system configuration directory.
803     isoImage.storeContents =
804       [ config.system.build.toplevel ] ++
805       lib.optional config.isoImage.includeSystemBuildDependencies
806         config.system.build.toplevel.drvPath;
808     # Individual files to be included on the CD, outside of the Nix
809     # store on the CD.
810     isoImage.contents =
811       [
812         { source = config.boot.kernelPackages.kernel + "/" + config.system.boot.loader.kernelFile;
813           target = "/boot/" + config.system.boot.loader.kernelFile;
814         }
815         { source = config.system.build.initialRamdisk + "/" + config.system.boot.loader.initrdFile;
816           target = "/boot/" + config.system.boot.loader.initrdFile;
817         }
818         { source = pkgs.writeText "version" config.system.nixos.label;
819           target = "/version.txt";
820         }
821       ] ++ lib.optionals (config.isoImage.makeBiosBootable) [
822         { source = config.isoImage.splashImage;
823           target = "/isolinux/background.png";
824         }
825         { source = pkgs.substituteAll  {
826             name = "isolinux.cfg";
827             src = pkgs.writeText "isolinux.cfg-in" isolinuxCfg;
828             bootRoot = "/boot";
829           };
830           target = "/isolinux/isolinux.cfg";
831         }
832         { source = "${pkgs.syslinux}/share/syslinux";
833           target = "/isolinux";
834         }
835       ] ++ lib.optionals config.isoImage.makeEfiBootable [
836         { source = efiImg;
837           target = "/boot/efi.img";
838         }
839         { source = "${efiDir}/EFI";
840           target = "/EFI";
841         }
842         { source = (pkgs.writeTextDir "grub/loopback.cfg" "source /EFI/BOOT/grub.cfg") + "/grub";
843           target = "/boot/grub";
844         }
845         { source = config.isoImage.efiSplashImage;
846           target = "/EFI/BOOT/efi-background.png";
847         }
848       ] ++ lib.optionals (config.boot.loader.grub.memtest86.enable && config.isoImage.makeBiosBootable) [
849         { source = "${pkgs.memtest86plus}/memtest.bin";
850           target = "/boot/memtest.bin";
851         }
852       ] ++ lib.optionals (config.isoImage.grubTheme != null) [
853         { source = config.isoImage.grubTheme;
854           target = "/EFI/BOOT/grub-theme";
855         }
856       ];
858     boot.loader.timeout = 10;
860     # Create the ISO image.
861     system.build.isoImage = pkgs.callPackage ../../../lib/make-iso9660-image.nix ({
862       inherit (config.isoImage) isoName compressImage volumeID contents;
863       bootable = config.isoImage.makeBiosBootable;
864       bootImage = "/isolinux/isolinux.bin";
865       syslinux = if config.isoImage.makeBiosBootable then pkgs.syslinux else null;
866       squashfsContents = config.isoImage.storeContents;
867       squashfsCompression = config.isoImage.squashfsCompression;
868     } // lib.optionalAttrs (config.isoImage.makeUsbBootable && config.isoImage.makeBiosBootable) {
869       usbBootable = true;
870       isohybridMbrImage = "${pkgs.syslinux}/share/syslinux/isohdpfx.bin";
871     } // lib.optionalAttrs config.isoImage.makeEfiBootable {
872       efiBootable = true;
873       efiBootImage = "boot/efi.img";
874     });
876     boot.postBootCommands =
877       ''
878         # After booting, register the contents of the Nix store on the
879         # CD in the Nix database in the tmpfs.
880         ${config.nix.package.out}/bin/nix-store --load-db < /nix/store/nix-path-registration
882         # nixos-rebuild also requires a "system" profile and an
883         # /etc/NIXOS tag.
884         touch /etc/NIXOS
885         ${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system
886       '';
888     # Add vfat support to the initrd to enable people to copy the
889     # contents of the CD to a bootable USB stick.
890     boot.initrd.supportedFilesystems = [ "vfat" ];
892   };