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