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