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