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