summary refs log tree commit diff
path: root/nixos/modules/installer/cd-dvd/iso-image.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/installer/cd-dvd/iso-image.nix')
-rw-r--r--nixos/modules/installer/cd-dvd/iso-image.nix811
1 files changed, 811 insertions, 0 deletions
diff --git a/nixos/modules/installer/cd-dvd/iso-image.nix b/nixos/modules/installer/cd-dvd/iso-image.nix
new file mode 100644
index 00000000000..3ff1b3d670e
--- /dev/null
+++ b/nixos/modules/installer/cd-dvd/iso-image.nix
@@ -0,0 +1,811 @@
+# This module creates a bootable ISO image containing the given NixOS
+# configuration.  The derivation for the ISO image will be placed in
+# config.system.build.isoImage.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  /**
+   * Given a list of `options`, concats the result of mapping each options
+   * to a menuentry for use in grub.
+   *
+   *  * defaults: {name, image, params, initrd}
+   *  * options: [ option... ]
+   *  * option: {name, params, class}
+   */
+  menuBuilderGrub2 =
+  defaults: options: lib.concatStrings
+    (
+      map
+      (option: ''
+        menuentry '${defaults.name} ${
+        # Name appended to menuentry defaults to params if no specific name given.
+        option.name or (if option ? params then "(${option.params})" else "")
+        }' ${if option ? class then " --class ${option.class}" else ""} {
+          linux ${defaults.image} \''${isoboot} ${defaults.params} ${
+            option.params or ""
+          }
+          initrd ${defaults.initrd}
+        }
+      '')
+      options
+    )
+  ;
+
+  /**
+   * Given a `config`, builds the default options.
+   */
+  buildMenuGrub2 = config:
+    buildMenuAdditionalParamsGrub2 config ""
+  ;
+
+  /**
+   * Given a `config` and params to add to `params`, build a set of default options.
+   * Use this one when creating a variant (e.g. hidpi)
+   */
+  buildMenuAdditionalParamsGrub2 = config: additional:
+  let
+    finalCfg = {
+      name = "NixOS ${config.system.nixos.label}${config.isoImage.appendToMenuLabel}";
+      params = "init=${config.system.build.toplevel}/init ${additional} ${toString config.boot.kernelParams}";
+      image = "/boot/${config.system.boot.loader.kernelFile}";
+      initrd = "/boot/initrd";
+    };
+  in
+    menuBuilderGrub2
+    finalCfg
+    [
+      { class = "installer"; }
+      { class = "nomodeset"; params = "nomodeset"; }
+      { class = "copytoram"; params = "copytoram"; }
+      { class = "debug";     params = "debug"; }
+    ]
+  ;
+
+  # Timeout in syslinux is in units of 1/10 of a second.
+  # 0 is used to disable timeouts.
+  syslinuxTimeout = if config.boot.loader.timeout == null then
+      0
+    else
+      max (config.boot.loader.timeout * 10) 1;
+
+
+  max = x: y: if x > y then x else y;
+
+  # The configuration file for syslinux.
+
+  # Notes on syslinux configuration and UNetbootin compatiblity:
+  #   * Do not use '/syslinux/syslinux.cfg' as the path for this
+  #     configuration. UNetbootin will not parse the file and use it as-is.
+  #     This results in a broken configuration if the partition label does
+  #     not match the specified config.isoImage.volumeID. For this reason
+  #     we're using '/isolinux/isolinux.cfg'.
+  #   * Use APPEND instead of adding command-line arguments directly after
+  #     the LINUX entries.
+  #   * COM32 entries (chainload, reboot, poweroff) are not recognized. They
+  #     result in incorrect boot entries.
+
+  baseIsolinuxCfg = ''
+    SERIAL 0 115200
+    TIMEOUT ${builtins.toString syslinuxTimeout}
+    UI vesamenu.c32
+    MENU TITLE NixOS
+    MENU BACKGROUND /isolinux/background.png
+    MENU RESOLUTION 800 600
+    MENU CLEAR
+    MENU ROWS 6
+    MENU CMDLINEROW -4
+    MENU TIMEOUTROW -3
+    MENU TABMSGROW  -2
+    MENU HELPMSGROW -1
+    MENU HELPMSGENDROW -1
+    MENU MARGIN 0
+
+    #                                FG:AARRGGBB  BG:AARRGGBB   shadow
+    MENU COLOR BORDER       30;44      #00000000    #00000000   none
+    MENU COLOR SCREEN       37;40      #FF000000    #00E2E8FF   none
+    MENU COLOR TABMSG       31;40      #80000000    #00000000   none
+    MENU COLOR TIMEOUT      1;37;40    #FF000000    #00000000   none
+    MENU COLOR TIMEOUT_MSG  37;40      #FF000000    #00000000   none
+    MENU COLOR CMDMARK      1;36;40    #FF000000    #00000000   none
+    MENU COLOR CMDLINE      37;40      #FF000000    #00000000   none
+    MENU COLOR TITLE        1;36;44    #00000000    #00000000   none
+    MENU COLOR UNSEL        37;44      #FF000000    #00000000   none
+    MENU COLOR SEL          7;37;40    #FFFFFFFF    #FF5277C3   std
+
+    DEFAULT boot
+
+    LABEL boot
+    MENU LABEL NixOS ${config.system.nixos.label}${config.isoImage.appendToMenuLabel}
+    LINUX /boot/${config.system.boot.loader.kernelFile}
+    APPEND init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams}
+    INITRD /boot/${config.system.boot.loader.initrdFile}
+
+    # A variant to boot with 'nomodeset'
+    LABEL boot-nomodeset
+    MENU LABEL NixOS ${config.system.nixos.label}${config.isoImage.appendToMenuLabel} (nomodeset)
+    LINUX /boot/${config.system.boot.loader.kernelFile}
+    APPEND init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams} nomodeset
+    INITRD /boot/${config.system.boot.loader.initrdFile}
+
+    # A variant to boot with 'copytoram'
+    LABEL boot-copytoram
+    MENU LABEL NixOS ${config.system.nixos.label}${config.isoImage.appendToMenuLabel} (copytoram)
+    LINUX /boot/${config.system.boot.loader.kernelFile}
+    APPEND init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams} copytoram
+    INITRD /boot/${config.system.boot.loader.initrdFile}
+
+    # A variant to boot with verbose logging to the console
+    LABEL boot-debug
+    MENU LABEL NixOS ${config.system.nixos.label}${config.isoImage.appendToMenuLabel} (debug)
+    LINUX /boot/${config.system.boot.loader.kernelFile}
+    APPEND init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams} loglevel=7
+    INITRD /boot/${config.system.boot.loader.initrdFile}
+
+    # A variant to boot with a serial console enabled
+    LABEL boot-serial
+    MENU LABEL NixOS ${config.system.nixos.label}${config.isoImage.appendToMenuLabel} (serial console=ttyS0,115200n8)
+    LINUX /boot/${config.system.boot.loader.kernelFile}
+    APPEND init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams} console=ttyS0,115200n8
+    INITRD /boot/${config.system.boot.loader.initrdFile}
+  '';
+
+  isolinuxMemtest86Entry = ''
+    LABEL memtest
+    MENU LABEL Memtest86+
+    LINUX /boot/memtest.bin
+    APPEND ${toString config.boot.loader.grub.memtest86.params}
+  '';
+
+  isolinuxCfg = concatStringsSep "\n"
+    ([ baseIsolinuxCfg ] ++ optional config.boot.loader.grub.memtest86.enable isolinuxMemtest86Entry);
+
+  refindBinary = if targetArch == "x64" || targetArch == "aa64" then "refind_${targetArch}.efi" else null;
+
+  # Setup instructions for rEFInd.
+  refind =
+    if refindBinary != null then
+      ''
+      # Adds rEFInd to the ISO.
+      cp -v ${pkgs.refind}/share/refind/${refindBinary} $out/EFI/boot/
+      ''
+    else
+      "# No refind for ${targetArch}"
+  ;
+
+  grubPkgs = if config.boot.loader.grub.forcei686 then pkgs.pkgsi686Linux else pkgs;
+
+  grubMenuCfg = ''
+    #
+    # Menu configuration
+    #
+
+    # Search using a "marker file"
+    search --set=root --file /EFI/nixos-installer-image
+
+    insmod gfxterm
+    insmod png
+    set gfxpayload=keep
+    set gfxmode=${concatStringsSep "," [
+      # GRUB will use the first valid mode listed here.
+      # `auto` will sometimes choose the smallest valid mode it detects.
+      # So instead we'll list a lot of possibly valid modes :/
+      #"3840x2160"
+      #"2560x1440"
+      "1920x1080"
+      "1366x768"
+      "1280x720"
+      "1024x768"
+      "800x600"
+      "auto"
+    ]}
+
+    # Fonts can be loaded?
+    # (This font is assumed to always be provided as a fallback by NixOS)
+    if loadfont (\$root)/EFI/boot/unicode.pf2; then
+      set with_fonts=true
+    fi
+    if [ "\$textmode" != "true" -a "\$with_fonts" == "true" ]; then
+      # Use graphical term, it can be either with background image or a theme.
+      # input is "console", while output is "gfxterm".
+      # This enables "serial" input and output only when possible.
+      # Otherwise the failure mode is to not even enable gfxterm.
+      if test "\$with_serial" == "yes"; then
+        terminal_output gfxterm serial
+        terminal_input  console serial
+      else
+        terminal_output gfxterm
+        terminal_input  console
+      fi
+    else
+      # Sets colors for the non-graphical term.
+      set menu_color_normal=cyan/blue
+      set menu_color_highlight=white/blue
+    fi
+
+    ${ # When there is a theme configured, use it, otherwise use the background image.
+    if config.isoImage.grubTheme != null then ''
+      # Sets theme.
+      set theme=(\$root)/EFI/boot/grub-theme/theme.txt
+      # Load theme fonts
+      $(find ${config.isoImage.grubTheme} -iname '*.pf2' -printf "loadfont (\$root)/EFI/boot/grub-theme/%P\n")
+    '' else ''
+      if background_image (\$root)/EFI/boot/efi-background.png; then
+        # Black background means transparent background when there
+        # is a background image set... This seems undocumented :(
+        set color_normal=black/black
+        set color_highlight=white/blue
+      else
+        # Falls back again to proper colors.
+        set menu_color_normal=cyan/blue
+        set menu_color_highlight=white/blue
+      fi
+    ''}
+  '';
+
+  # The EFI boot image.
+  # Notes about grub:
+  #  * Yes, the grubMenuCfg has to be repeated in all submenus. Otherwise you
+  #    will get white-on-black console-like text on sub-menus. *sigh*
+  efiDir = pkgs.runCommand "efi-directory" {
+    nativeBuildInputs = [ pkgs.buildPackages.grub2_efi ];
+    strictDeps = true;
+  } ''
+    mkdir -p $out/EFI/boot/
+
+    # Add a marker so GRUB can find the filesystem.
+    touch $out/EFI/nixos-installer-image
+
+    # ALWAYS required modules.
+    MODULES="fat iso9660 part_gpt part_msdos \
+             normal boot linux configfile loopback chain halt \
+             efifwsetup efi_gop \
+             ls search search_label search_fs_uuid search_fs_file \
+             gfxmenu gfxterm gfxterm_background gfxterm_menu test all_video loadenv \
+             exfat ext2 ntfs btrfs hfsplus udf \
+             videoinfo png \
+             echo serial \
+            "
+
+    echo "Building GRUB with modules:"
+    for mod in $MODULES; do
+      echo " - $mod"
+    done
+
+    # Modules that may or may not be available per-platform.
+    echo "Adding additional modules:"
+    for mod in efi_uga; do
+      if [ -f ${grubPkgs.grub2_efi}/lib/grub/${grubPkgs.grub2_efi.grubTarget}/$mod.mod ]; then
+        echo " - $mod"
+        MODULES+=" $mod"
+      fi
+    done
+
+    # Make our own efi program, we can't rely on "grub-install" since it seems to
+    # probe for devices, even with --skip-fs-probe.
+    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} \
+      $MODULES
+    cp ${grubPkgs.grub2_efi}/share/grub/unicode.pf2 $out/EFI/boot/
+
+    cat <<EOF > $out/EFI/boot/grub.cfg
+
+    set with_fonts=false
+    set textmode=false
+    # If you want to use serial for "terminal_*" commands, you need to set one up:
+    #   Example manual configuration:
+    #    → serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1
+    # This uses the defaults, and makes the serial terminal available.
+    set with_serial=no
+    if serial; then set with_serial=yes ;fi
+    export with_serial
+    clear
+    set timeout=10
+
+    # This message will only be viewable when "gfxterm" is not used.
+    echo ""
+    echo "Loading graphical boot menu..."
+    echo ""
+    echo "Press 't' to use the text boot menu on this console..."
+    echo ""
+
+    ${grubMenuCfg}
+
+    hiddenentry 'Text mode' --hotkey 't' {
+      loadfont (\$root)/EFI/boot/unicode.pf2
+      set textmode=true
+      terminal_output gfxterm console
+    }
+    hiddenentry 'GUI mode' --hotkey 'g' {
+      $(find ${config.isoImage.grubTheme} -iname '*.pf2' -printf "loadfont (\$root)/EFI/boot/grub-theme/%P\n")
+      set textmode=false
+      terminal_output gfxterm
+    }
+
+
+    # If the parameter iso_path is set, append the findiso parameter to the kernel
+    # line. We need this to allow the nixos iso to be booted from grub directly.
+    if [ \''${iso_path} ] ; then
+      set isoboot="findiso=\''${iso_path}"
+    fi
+
+    #
+    # Menu entries
+    #
+
+    ${buildMenuGrub2 config}
+    submenu "HiDPI, Quirks and Accessibility" --class hidpi --class submenu {
+      ${grubMenuCfg}
+      submenu "Suggests resolution @720p" --class hidpi-720p {
+        ${grubMenuCfg}
+        ${buildMenuAdditionalParamsGrub2 config "video=1280x720@60"}
+      }
+      submenu "Suggests resolution @1080p" --class hidpi-1080p {
+        ${grubMenuCfg}
+        ${buildMenuAdditionalParamsGrub2 config "video=1920x1080@60"}
+      }
+
+      # If we boot into a graphical environment where X is autoran
+      # and always crashes, it makes the media unusable. Allow the user
+      # to disable this.
+      submenu "Disable display-manager" --class quirk-disable-displaymanager {
+        ${grubMenuCfg}
+        ${buildMenuAdditionalParamsGrub2 config "systemd.mask=display-manager.service"}
+      }
+
+      # Some laptop and convertibles have the panel installed in an
+      # inconvenient way, rotated away from the keyboard.
+      # Those entries makes it easier to use the installer.
+      submenu "" {return}
+      submenu "Rotate framebuffer Clockwise" --class rotate-90cw {
+        ${grubMenuCfg}
+        ${buildMenuAdditionalParamsGrub2 config "fbcon=rotate:1"}
+      }
+      submenu "Rotate framebuffer Upside-Down" --class rotate-180 {
+        ${grubMenuCfg}
+        ${buildMenuAdditionalParamsGrub2 config "fbcon=rotate:2"}
+      }
+      submenu "Rotate framebuffer Counter-Clockwise" --class rotate-90ccw {
+        ${grubMenuCfg}
+        ${buildMenuAdditionalParamsGrub2 config "fbcon=rotate:3"}
+      }
+
+      # As a proof of concept, mainly. (Not sure it has accessibility merits.)
+      submenu "" {return}
+      submenu "Use black on white" --class accessibility-blakconwhite {
+        ${grubMenuCfg}
+        ${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"}
+      }
+
+      # Serial access is a must!
+      submenu "" {return}
+      submenu "Serial console=ttyS0,115200n8" --class serial {
+        ${grubMenuCfg}
+        ${buildMenuAdditionalParamsGrub2 config "console=ttyS0,115200n8"}
+      }
+    }
+
+    ${lib.optionalString (refindBinary != null) ''
+    # GRUB apparently cannot do "chainloader" operations on "CD".
+    if [ "\$root" != "cd0" ]; then
+      # Force root to be the FAT partition
+      # Otherwise it breaks rEFInd's boot
+      search --set=root --no-floppy --fs-uuid 1234-5678
+      menuentry 'rEFInd' --class refind {
+        chainloader (\$root)/EFI/boot/${refindBinary}
+      }
+    fi
+    ''}
+    menuentry 'Firmware Setup' --class settings {
+      fwsetup
+      clear
+      echo ""
+      echo "If you see this message, your EFI system doesn't support this feature."
+      echo ""
+    }
+    menuentry 'Shutdown' --class shutdown {
+      halt
+    }
+    EOF
+
+    ${refind}
+  '';
+
+  efiImg = pkgs.runCommand "efi-image_eltorito" {
+    nativeBuildInputs = [ pkgs.buildPackages.mtools pkgs.buildPackages.libfaketime pkgs.buildPackages.dosfstools ];
+    strictDeps = true;
+  }
+    # Be careful about determinism: du --apparent-size,
+    #   dates (cp -p, touch, mcopy -m, faketime for label), IDs (mkfs.vfat -i)
+    ''
+      mkdir ./contents && cd ./contents
+      cp -rp "${efiDir}"/EFI .
+      mkdir ./boot
+      cp -p "${config.boot.kernelPackages.kernel}/${config.system.boot.loader.kernelFile}" \
+        "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}" ./boot/
+
+      # Rewrite dates for everything in the FS
+      find . -exec touch --date=2000-01-01 {} +
+
+      # Round up to the nearest multiple of 1MB, for more deterministic du output
+      usage_size=$(( $(du -s --block-size=1M --apparent-size . | tr -cd '[:digit:]') * 1024 * 1024 ))
+      # Make the image 110% as big as the files need to make up for FAT overhead
+      image_size=$(( ($usage_size * 110) / 100 ))
+      # Make the image fit blocks of 1M
+      block_size=$((1024*1024))
+      image_size=$(( ($image_size / $block_size + 1) * $block_size ))
+      echo "Usage size: $usage_size"
+      echo "Image size: $image_size"
+      truncate --size=$image_size "$out"
+      faketime "2000-01-01 00:00:00" mkfs.vfat -i 12345678 -n EFIBOOT "$out"
+
+      # Force a fixed order in mcopy for better determinism, and avoid file globbing
+      for d in $(find EFI boot -type d | sort); do
+        faketime "2000-01-01 00:00:00" mmd -i "$out" "::/$d"
+      done
+
+      for f in $(find EFI boot -type f | sort); do
+        mcopy -pvm -i "$out" "$f" "::/$f"
+      done
+
+      # Verify the FAT partition.
+      fsck.vfat -vn "$out"
+    ''; # */
+
+  # Name used by UEFI for architectures.
+  targetArch =
+    if pkgs.stdenv.isi686 || config.boot.loader.grub.forcei686 then
+      "ia32"
+    else if pkgs.stdenv.isx86_64 then
+      "x64"
+    else if pkgs.stdenv.isAarch32 then
+      "arm"
+    else if pkgs.stdenv.isAarch64 then
+      "aa64"
+    else
+      throw "Unsupported architecture";
+
+  # Syslinux (and isolinux) only supports x86-based architectures.
+  canx86BiosBoot = pkgs.stdenv.hostPlatform.isx86;
+
+in
+
+{
+  options = {
+
+    isoImage.isoName = mkOption {
+      default = "${config.isoImage.isoBaseName}.iso";
+      description = ''
+        Name of the generated ISO image file.
+      '';
+    };
+
+    isoImage.isoBaseName = mkOption {
+      default = "nixos";
+      description = ''
+        Prefix of the name of the generated ISO image file.
+      '';
+    };
+
+    isoImage.compressImage = mkOption {
+      default = false;
+      description = ''
+        Whether the ISO image should be compressed using
+        <command>zstd</command>.
+      '';
+    };
+
+    isoImage.squashfsCompression = mkOption {
+      default = with pkgs.stdenv.targetPlatform; "xz -Xdict-size 100% "
+                + lib.optionalString (isx86_32 || isx86_64) "-Xbcj x86"
+                # Untested but should also reduce size for these platforms
+                + lib.optionalString (isAarch32 || isAarch64) "-Xbcj arm"
+                + lib.optionalString (isPowerPC) "-Xbcj powerpc"
+                + lib.optionalString (isSparc) "-Xbcj sparc";
+      description = ''
+        Compression settings to use for the squashfs nix store.
+      '';
+      example = "zstd -Xcompression-level 6";
+    };
+
+    isoImage.edition = mkOption {
+      default = "";
+      description = ''
+        Specifies which edition string to use in the volume ID of the generated
+        ISO image.
+      '';
+    };
+
+    isoImage.volumeID = mkOption {
+      # nixos-$EDITION-$RELEASE-$ARCH
+      default = "nixos${optionalString (config.isoImage.edition != "") "-${config.isoImage.edition}"}-${config.system.nixos.release}-${pkgs.stdenv.hostPlatform.uname.processor}";
+      description = ''
+        Specifies the label or volume ID of the generated ISO image.
+        Note that the label is used by stage 1 of the boot process to
+        mount the CD, so it should be reasonably distinctive.
+      '';
+    };
+
+    isoImage.contents = mkOption {
+      example = literalExpression ''
+        [ { source = pkgs.memtest86 + "/memtest.bin";
+            target = "boot/memtest.bin";
+          }
+        ]
+      '';
+      description = ''
+        This option lists files to be copied to fixed locations in the
+        generated ISO image.
+      '';
+    };
+
+    isoImage.storeContents = mkOption {
+      example = literalExpression "[ pkgs.stdenv ]";
+      description = ''
+        This option lists additional derivations to be included in the
+        Nix store in the generated ISO image.
+      '';
+    };
+
+    isoImage.includeSystemBuildDependencies = mkOption {
+      default = false;
+      description = ''
+        Set this option to include all the needed sources etc in the
+        image. It significantly increases image size. Use that when
+        you want to be able to keep all the sources needed to build your
+        system or when you are going to install the system on a computer
+        with slow or non-existent network connection.
+      '';
+    };
+
+    isoImage.makeEfiBootable = mkOption {
+      default = false;
+      description = ''
+        Whether the ISO image should be an efi-bootable volume.
+      '';
+    };
+
+    isoImage.makeUsbBootable = mkOption {
+      default = false;
+      description = ''
+        Whether the ISO image should be bootable from CD as well as USB.
+      '';
+    };
+
+    isoImage.efiSplashImage = mkOption {
+      default = pkgs.fetchurl {
+          url = "https://raw.githubusercontent.com/NixOS/nixos-artwork/a9e05d7deb38a8e005a2b52575a3f59a63a4dba0/bootloader/efi-background.png";
+          sha256 = "18lfwmp8yq923322nlb9gxrh5qikj1wsk6g5qvdh31c4h5b1538x";
+        };
+      description = ''
+        The splash image to use in the EFI bootloader.
+      '';
+    };
+
+    isoImage.splashImage = mkOption {
+      default = pkgs.fetchurl {
+          url = "https://raw.githubusercontent.com/NixOS/nixos-artwork/a9e05d7deb38a8e005a2b52575a3f59a63a4dba0/bootloader/isolinux/bios-boot.png";
+          sha256 = "1wp822zrhbg4fgfbwkr7cbkr4labx477209agzc0hr6k62fr6rxd";
+        };
+      description = ''
+        The splash image to use in the legacy-boot bootloader.
+      '';
+    };
+
+    isoImage.grubTheme = mkOption {
+      default = pkgs.nixos-grub2-theme;
+      type = types.nullOr (types.either types.path types.package);
+      description = ''
+        The grub2 theme used for UEFI boot.
+      '';
+    };
+
+    isoImage.appendToMenuLabel = mkOption {
+      default = " Installer";
+      example = " Live System";
+      description = ''
+        The string to append after the menu label for the NixOS system.
+        This will be directly appended (without whitespace) to the NixOS version
+        string, like for example if it is set to <literal>XXX</literal>:
+
+        <para><literal>NixOS 99.99-pre666XXX</literal></para>
+      '';
+    };
+
+  };
+
+  # store them in lib so we can mkImageMediaOverride the
+  # entire file system layout in installation media (only)
+  config.lib.isoFileSystems = {
+    "/" = mkImageMediaOverride
+      {
+        fsType = "tmpfs";
+        options = [ "mode=0755" ];
+      };
+
+    # Note that /dev/root is a symlink to the actual root device
+    # specified on the kernel command line, created in the stage 1
+    # init script.
+    "/iso" = mkImageMediaOverride
+      { device = "/dev/root";
+        neededForBoot = true;
+        noCheck = true;
+      };
+
+    # In stage 1, mount a tmpfs on top of /nix/store (the squashfs
+    # image) to make this a live CD.
+    "/nix/.ro-store" = mkImageMediaOverride
+      { fsType = "squashfs";
+        device = "/iso/nix-store.squashfs";
+        options = [ "loop" ];
+        neededForBoot = true;
+      };
+
+    "/nix/.rw-store" = mkImageMediaOverride
+      { fsType = "tmpfs";
+        options = [ "mode=0755" ];
+        neededForBoot = true;
+      };
+
+    "/nix/store" = mkImageMediaOverride
+      { fsType = "overlay";
+        device = "overlay";
+        options = [
+          "lowerdir=/nix/.ro-store"
+          "upperdir=/nix/.rw-store/store"
+          "workdir=/nix/.rw-store/work"
+        ];
+        depends = [
+          "/nix/.ro-store"
+          "/nix/.rw-store/store"
+          "/nix/.rw-store/work"
+        ];
+      };
+  };
+
+  config = {
+    assertions = [
+      {
+        assertion = !(stringLength config.isoImage.volumeID > 32);
+        # https://wiki.osdev.org/ISO_9660#The_Primary_Volume_Descriptor
+        # Volume Identifier can only be 32 bytes
+        message = let
+          length = stringLength config.isoImage.volumeID;
+          howmany = toString length;
+          toomany = toString (length - 32);
+        in
+        "isoImage.volumeID ${config.isoImage.volumeID} is ${howmany} characters. That is ${toomany} characters longer than the limit of 32.";
+      }
+    ];
+
+    boot.loader.grub.version = 2;
+
+    # Don't build the GRUB menu builder script, since we don't need it
+    # here and it causes a cyclic dependency.
+    boot.loader.grub.enable = false;
+
+    environment.systemPackages =  [ grubPkgs.grub2 grubPkgs.grub2_efi ]
+      ++ optional canx86BiosBoot pkgs.syslinux
+    ;
+
+    # In stage 1 of the boot, mount the CD as the root FS by label so
+    # that we don't need to know its device.  We pass the label of the
+    # root filesystem on the kernel command line, rather than in
+    # `fileSystems' below.  This allows CD-to-USB converters such as
+    # UNetbootin to rewrite the kernel command line to pass the label or
+    # UUID of the USB stick.  It would be nicer to write
+    # `root=/dev/disk/by-label/...' here, but UNetbootin doesn't
+    # recognise that.
+    boot.kernelParams =
+      [ "root=LABEL=${config.isoImage.volumeID}"
+        "boot.shell_on_fail"
+      ];
+
+    fileSystems = config.lib.isoFileSystems;
+
+    boot.initrd.availableKernelModules = [ "squashfs" "iso9660" "uas" "overlay" ];
+
+    boot.initrd.kernelModules = [ "loop" "overlay" ];
+
+    # Closures to be copied to the Nix store on the CD, namely the init
+    # script and the top-level system configuration directory.
+    isoImage.storeContents =
+      [ config.system.build.toplevel ] ++
+      optional config.isoImage.includeSystemBuildDependencies
+        config.system.build.toplevel.drvPath;
+
+    # Create the squashfs image that contains the Nix store.
+    system.build.squashfsStore = pkgs.callPackage ../../../lib/make-squashfs.nix {
+      storeContents = config.isoImage.storeContents;
+      comp = config.isoImage.squashfsCompression;
+    };
+
+    # Individual files to be included on the CD, outside of the Nix
+    # store on the CD.
+    isoImage.contents =
+      [
+        { source = config.boot.kernelPackages.kernel + "/" + config.system.boot.loader.kernelFile;
+          target = "/boot/" + config.system.boot.loader.kernelFile;
+        }
+        { source = config.system.build.initialRamdisk + "/" + config.system.boot.loader.initrdFile;
+          target = "/boot/" + config.system.boot.loader.initrdFile;
+        }
+        { source = config.system.build.squashfsStore;
+          target = "/nix-store.squashfs";
+        }
+        { source = pkgs.writeText "version" config.system.nixos.label;
+          target = "/version.txt";
+        }
+      ] ++ optionals canx86BiosBoot [
+        { source = config.isoImage.splashImage;
+          target = "/isolinux/background.png";
+        }
+        { source = pkgs.substituteAll  {
+            name = "isolinux.cfg";
+            src = pkgs.writeText "isolinux.cfg-in" isolinuxCfg;
+            bootRoot = "/boot";
+          };
+          target = "/isolinux/isolinux.cfg";
+        }
+        { source = "${pkgs.syslinux}/share/syslinux";
+          target = "/isolinux";
+        }
+      ] ++ optionals config.isoImage.makeEfiBootable [
+        { source = efiImg;
+          target = "/boot/efi.img";
+        }
+        { source = "${efiDir}/EFI";
+          target = "/EFI";
+        }
+        { source = (pkgs.writeTextDir "grub/loopback.cfg" "source /EFI/boot/grub.cfg") + "/grub";
+          target = "/boot/grub";
+        }
+        { source = config.isoImage.efiSplashImage;
+          target = "/EFI/boot/efi-background.png";
+        }
+      ] ++ optionals (config.boot.loader.grub.memtest86.enable && canx86BiosBoot) [
+        { source = "${pkgs.memtest86plus}/memtest.bin";
+          target = "/boot/memtest.bin";
+        }
+      ] ++ optionals (config.isoImage.grubTheme != null) [
+        { source = config.isoImage.grubTheme;
+          target = "/EFI/boot/grub-theme";
+        }
+      ];
+
+    boot.loader.timeout = 10;
+
+    # Create the ISO image.
+    system.build.isoImage = pkgs.callPackage ../../../lib/make-iso9660-image.nix ({
+      inherit (config.isoImage) isoName compressImage volumeID contents;
+      bootable = canx86BiosBoot;
+      bootImage = "/isolinux/isolinux.bin";
+      syslinux = if canx86BiosBoot then pkgs.syslinux else null;
+    } // optionalAttrs (config.isoImage.makeUsbBootable && canx86BiosBoot) {
+      usbBootable = true;
+      isohybridMbrImage = "${pkgs.syslinux}/share/syslinux/isohdpfx.bin";
+    } // optionalAttrs config.isoImage.makeEfiBootable {
+      efiBootable = true;
+      efiBootImage = "boot/efi.img";
+    });
+
+    boot.postBootCommands =
+      ''
+        # After booting, register the contents of the Nix store on the
+        # CD in the Nix database in the tmpfs.
+        ${config.nix.package.out}/bin/nix-store --load-db < /nix/store/nix-path-registration
+
+        # nixos-rebuild also requires a "system" profile and an
+        # /etc/NIXOS tag.
+        touch /etc/NIXOS
+        ${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system
+      '';
+
+    # Add vfat support to the initrd to enable people to copy the
+    # contents of the CD to a bootable USB stick.
+    boot.initrd.supportedFilesystems = [ "vfat" ];
+
+  };
+
+}