about summary refs log tree commit diff
path: root/nixpkgs/nixos/modules/system/boot/loader
diff options
context:
space:
mode:
authorAlyssa Ross <hi@alyssa.is>2019-01-07 02:18:36 +0000
committerAlyssa Ross <hi@alyssa.is>2019-01-07 02:18:47 +0000
commit36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2 (patch)
treeb3faaf573407b32aa645237a4d16b82778a39a92 /nixpkgs/nixos/modules/system/boot/loader
parent4e31070265257dc67d120c27e0f75c2344fdfa9a (diff)
parentabf060725d7614bd3b9f96764262dfbc2f9c2199 (diff)
downloadnixlib-36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2.tar
nixlib-36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2.tar.gz
nixlib-36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2.tar.bz2
nixlib-36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2.tar.lz
nixlib-36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2.tar.xz
nixlib-36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2.tar.zst
nixlib-36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2.zip
Add 'nixpkgs/' from commit 'abf060725d7614bd3b9f96764262dfbc2f9c2199'
git-subtree-dir: nixpkgs
git-subtree-mainline: 4e31070265257dc67d120c27e0f75c2344fdfa9a
git-subtree-split: abf060725d7614bd3b9f96764262dfbc2f9c2199
Diffstat (limited to 'nixpkgs/nixos/modules/system/boot/loader')
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/efi.nix20
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/generations-dir/generations-dir-builder.sh106
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix65
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix44
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix8
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh141
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix678
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/grub/install-grub.pl612
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/grub/ipxe.nix64
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/grub/memtest.nix93
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/init-script/init-script-builder.sh93
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/init-script/init-script.nix51
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/loader.nix15
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.nix10
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.sh132
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix108
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.nix35
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.sh38
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py211
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix114
20 files changed, 2638 insertions, 0 deletions
diff --git a/nixpkgs/nixos/modules/system/boot/loader/efi.nix b/nixpkgs/nixos/modules/system/boot/loader/efi.nix
new file mode 100644
index 000000000000..6043c904c450
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/efi.nix
@@ -0,0 +1,20 @@
+{ lib, ... }:
+
+with lib;
+
+{
+  options.boot.loader.efi = {
+
+    canTouchEfiVariables = mkOption {
+      default = false;
+      type = types.bool;
+      description = "Whether the installation process is allowed to modify EFI boot variables.";
+    };
+
+    efiSysMountPoint = mkOption {
+      default = "/boot";
+      type = types.str;
+      description = "Where the EFI System Partition is mounted.";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/system/boot/loader/generations-dir/generations-dir-builder.sh b/nixpkgs/nixos/modules/system/boot/loader/generations-dir/generations-dir-builder.sh
new file mode 100644
index 000000000000..e723b9eb7cb3
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/generations-dir/generations-dir-builder.sh
@@ -0,0 +1,106 @@
+#! @bash@/bin/sh -e
+
+shopt -s nullglob
+
+export PATH=/empty
+for i in @path@; do PATH=$PATH:$i/bin; done
+
+default=$1
+if test -z "$1"; then
+    echo "Syntax: generations-dir-builder.sh <DEFAULT-CONFIG>"
+    exit 1
+fi
+
+echo "updating the boot generations directory..."
+
+mkdir -p /boot
+
+rm -Rf /boot/system* || true
+
+target=/boot/grub/menu.lst
+tmp=$target.tmp
+
+# Convert a path to a file in the Nix store such as
+# /nix/store/<hash>-<name>/file to <hash>-<name>-<file>.
+cleanName() {
+    local path="$1"
+    echo "$path" | sed 's|^/nix/store/||' | sed 's|/|-|g'
+}
+
+# Copy a file from the Nix store to /boot/kernels.
+declare -A filesCopied
+
+copyToKernelsDir() {
+    local src="$1"
+    local dst="/boot/kernels/$(cleanName $src)"
+    # Don't copy the file if $dst already exists.  This means that we
+    # have to create $dst atomically to prevent partially copied
+    # kernels or initrd if this script is ever interrupted.
+    if ! test -e $dst; then
+        local dstTmp=$dst.tmp.$$
+        cp $src $dstTmp
+        mv $dstTmp $dst
+    fi
+    filesCopied[$dst]=1
+    result=$dst
+}
+
+
+# Copy its kernel and initrd to /boot/kernels.
+addEntry() {
+    local path="$1"
+    local generation="$2"
+    local outdir=/boot/system-$generation
+
+    if ! test -e $path/kernel -a -e $path/initrd; then
+        return
+    fi
+
+    local kernel=$(readlink -f $path/kernel)
+    local initrd=$(readlink -f $path/initrd)
+
+    if test -n "@copyKernels@"; then
+        copyToKernelsDir $kernel; kernel=$result
+        copyToKernelsDir $initrd; initrd=$result
+    fi
+    
+    mkdir -p $outdir
+    ln -sf $(readlink -f $path) $outdir/system
+    ln -sf $(readlink -f $path/init) $outdir/init
+    ln -sf $initrd $outdir/initrd
+    ln -sf $kernel $outdir/kernel
+
+    if test $(readlink -f "$path") = "$default"; then
+      cp "$kernel" /boot/nixos-kernel
+      cp "$initrd" /boot/nixos-initrd
+      cp "$(readlink -f "$path/init")" /boot/nixos-init
+
+      mkdir -p /boot/default
+      # ln -sfT: overrides target even if it exists.
+      ln -sfT $(readlink -f $path) /boot/default/system
+      ln -sfT $(readlink -f $path/init) /boot/default/init
+      ln -sfT $initrd /boot/default/initrd
+      ln -sfT $kernel /boot/default/kernel
+    fi
+}
+
+if test -n "@copyKernels@"; then
+    mkdir -p /boot/kernels
+fi
+
+# Add all generations of the system profile to the menu, in reverse
+# (most recent to least recent) order.
+for generation in $(
+    (cd /nix/var/nix/profiles && ls -d system-*-link) \
+    | sed 's/system-\([0-9]\+\)-link/\1/' \
+    | sort -n -r); do
+    link=/nix/var/nix/profiles/system-$generation-link
+    addEntry $link $generation
+done
+
+# Remove obsolete files from /boot/kernels.
+for fn in /boot/kernels/*; do
+    if ! test "${filesCopied[$fn]}" = 1; then
+        rm -vf -- "$fn"
+    fi
+done
diff --git a/nixpkgs/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix b/nixpkgs/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix
new file mode 100644
index 000000000000..2d27611946e2
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix
@@ -0,0 +1,65 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  generationsDirBuilder = pkgs.substituteAll {
+    src = ./generations-dir-builder.sh;
+    isExecutable = true;
+    inherit (pkgs) bash;
+    path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
+    inherit (config.boot.loader.generationsDir) copyKernels;
+  };
+
+  # Temporary check, for nixos to cope both with nixpkgs stdenv-updates and trunk
+  inherit (pkgs.stdenv.hostPlatform) platform;
+
+in
+
+{
+  options = {
+
+    boot.loader.generationsDir = {
+
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Whether to create symlinks to the system generations under
+          <literal>/boot</literal>.  When enabled,
+          <literal>/boot/default/kernel</literal>,
+          <literal>/boot/default/initrd</literal>, etc., are updated to
+          point to the current generation's kernel image, initial RAM
+          disk, and other bootstrap files.
+
+          This optional is not necessary with boot loaders such as GNU GRUB
+          for which the menu is updated to point to the latest bootstrap
+          files.  However, it is needed for U-Boot on platforms where the
+          boot command line is stored in flash memory rather than in a
+          menu file.
+        '';
+      };
+
+      copyKernels = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Whether copy the necessary boot files into /boot, so
+          /nix/store is not needed by the boot loader.
+        '';
+      };
+
+    };
+
+  };
+
+
+  config = mkIf config.boot.loader.generationsDir.enable {
+
+    system.build.installBootLoader = generationsDirBuilder;
+    system.boot.loader.id = "generationsDir";
+    system.boot.loader.kernelFile = platform.kernelTarget;
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix b/nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
new file mode 100644
index 000000000000..af39c7bb6841
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
@@ -0,0 +1,44 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  blCfg = config.boot.loader;
+  cfg = blCfg.generic-extlinux-compatible;
+
+  timeoutStr = if blCfg.timeout == null then "-1" else toString blCfg.timeout;
+
+  builder = import ./extlinux-conf-builder.nix { inherit pkgs; };
+in
+{
+  options = {
+    boot.loader.generic-extlinux-compatible = {
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Whether to generate an extlinux-compatible configuration file
+          under <literal>/boot/extlinux.conf</literal>.  For instance,
+          U-Boot's generic distro boot support uses this file format.
+
+          See <link xlink:href="http://git.denx.de/?p=u-boot.git;a=blob;f=doc/README.distro;hb=refs/heads/master">U-boot's documentation</link>
+          for more information.
+        '';
+      };
+
+      configurationLimit = mkOption {
+        default = 20;
+        example = 10;
+        type = types.int;
+        description = ''
+          Maximum number of configurations in the boot menu.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    system.build.installBootLoader = "${builder} -g ${toString cfg.configurationLimit} -t ${timeoutStr} -c";
+    system.boot.loader.id = "generic-extlinux-compatible";
+  };
+}
diff --git a/nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix b/nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix
new file mode 100644
index 000000000000..576a07c1d272
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix
@@ -0,0 +1,8 @@
+{ pkgs }:
+
+pkgs.substituteAll {
+  src = ./extlinux-conf-builder.sh;
+  isExecutable = true;
+  path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
+  inherit (pkgs) bash;
+}
diff --git a/nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh b/nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh
new file mode 100644
index 000000000000..c780a89b102c
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh
@@ -0,0 +1,141 @@
+#! @bash@/bin/sh -e
+
+shopt -s nullglob
+
+export PATH=/empty
+for i in @path@; do PATH=$PATH:$i/bin; done
+
+usage() {
+    echo "usage: $0 -t <timeout> -c <path-to-default-configuration> [-d <boot-dir>] [-g <num-generations>]" >&2
+    exit 1
+}
+
+timeout=                # Timeout in centiseconds
+default=                # Default configuration
+target=/boot            # Target directory
+numGenerations=0        # Number of other generations to include in the menu
+
+while getopts "t:c:d:g:" opt; do
+    case "$opt" in
+        t) # U-Boot interprets '0' as infinite and negative as instant boot
+            if [ "$OPTARG" -lt 0 ]; then
+                timeout=0
+            elif [ "$OPTARG" = 0 ]; then
+                timeout=-10
+            else
+                timeout=$((OPTARG * 10))
+            fi
+            ;;
+        c) default="$OPTARG" ;;
+        d) target="$OPTARG" ;;
+        g) numGenerations="$OPTARG" ;;
+        \?) usage ;;
+    esac
+done
+
+[ "$timeout" = "" -o "$default" = "" ] && usage
+
+mkdir -p $target/nixos
+mkdir -p $target/extlinux
+
+# Convert a path to a file in the Nix store such as
+# /nix/store/<hash>-<name>/file to <hash>-<name>-<file>.
+cleanName() {
+    local path="$1"
+    echo "$path" | sed 's|^/nix/store/||' | sed 's|/|-|g'
+}
+
+# Copy a file from the Nix store to $target/nixos.
+declare -A filesCopied
+
+copyToKernelsDir() {
+    local src=$(readlink -f "$1")
+    local dst="$target/nixos/$(cleanName $src)"
+    # Don't copy the file if $dst already exists.  This means that we
+    # have to create $dst atomically to prevent partially copied
+    # kernels or initrd if this script is ever interrupted.
+    if ! test -e $dst; then
+        local dstTmp=$dst.tmp.$$
+        cp -r $src $dstTmp
+        mv $dstTmp $dst
+    fi
+    filesCopied[$dst]=1
+    result=$dst
+}
+
+# Copy its kernel, initrd and dtbs to $target/nixos, and echo out an
+# extlinux menu entry
+addEntry() {
+    local path=$(readlink -f "$1")
+    local tag="$2" # Generation number or 'default'
+
+    if ! test -e $path/kernel -a -e $path/initrd; then
+        return
+    fi
+
+    copyToKernelsDir "$path/kernel"; kernel=$result
+    copyToKernelsDir "$path/initrd"; initrd=$result
+    # XXX UGLY: maybe the system config should have a top-level "dtbs" entry?
+    dtbDir=$(readlink -m "$path/kernel/../dtbs")
+    if [ -d "$dtbDir" ]; then
+        copyToKernelsDir "$dtbDir"; dtbs=$result
+    fi
+
+    timestampEpoch=$(stat -L -c '%Z' $path)
+
+    timestamp=$(date "+%Y-%m-%d %H:%M" -d @$timestampEpoch)
+    nixosLabel="$(cat $path/nixos-version)"
+    extraParams="$(cat $path/kernel-params)"
+
+    echo
+    echo "LABEL nixos-$tag"
+    if [ "$tag" = "default" ]; then
+        echo "  MENU LABEL NixOS - Default"
+    else
+        echo "  MENU LABEL NixOS - Configuration $tag ($timestamp - $nixosLabel)"
+    fi
+    echo "  LINUX ../nixos/$(basename $kernel)"
+    echo "  INITRD ../nixos/$(basename $initrd)"
+    if [ -d "$dtbDir" ]; then
+        echo "  FDTDIR ../nixos/$(basename $dtbs)"
+    fi
+    echo "  APPEND systemConfig=$path init=$path/init $extraParams"
+}
+
+tmpFile="$target/extlinux/extlinux.conf.tmp.$$"
+
+cat > $tmpFile <<EOF
+# Generated file, all changes will be lost on nixos-rebuild!
+
+# Change this to e.g. nixos-42 to temporarily boot to an older configuration.
+DEFAULT nixos-default
+
+MENU TITLE ------------------------------------------------------------
+TIMEOUT $timeout
+EOF
+
+addEntry $default default >> $tmpFile
+
+if [ "$numGenerations" -gt 0 ]; then
+    # Add up to $numGenerations generations of the system profile to the menu,
+    # in reverse (most recent to least recent) order.
+    for generation in $(
+            (cd /nix/var/nix/profiles && ls -d system-*-link) \
+            | sed 's/system-\([0-9]\+\)-link/\1/' \
+            | sort -n -r \
+            | head -n $numGenerations); do
+        link=/nix/var/nix/profiles/system-$generation-link
+        addEntry $link $generation
+    done >> $tmpFile
+fi
+
+mv -f $tmpFile $target/extlinux/extlinux.conf
+
+# Remove obsolete files from $target/nixos.
+for fn in $target/nixos/*; do
+    if ! test "${filesCopied[$fn]}" = 1; then
+        echo "Removing no longer needed boot file: $fn"
+        chmod +w -- "$fn"
+        rm -rf -- "$fn"
+    fi
+done
diff --git a/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix b/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix
new file mode 100644
index 000000000000..3a33b3f65d36
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix
@@ -0,0 +1,678 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.boot.loader.grub;
+
+  efi = config.boot.loader.efi;
+
+  realGrub = if cfg.version == 1 then pkgs.grub
+    else if cfg.zfsSupport then pkgs.grub2.override { zfsSupport = true; }
+    else if cfg.trustedBoot.enable
+         then if cfg.trustedBoot.isHPLaptop
+              then pkgs.trustedGrub-for-HP
+              else pkgs.trustedGrub
+         else pkgs.grub2;
+
+  grub =
+    # Don't include GRUB if we're only generating a GRUB menu (e.g.,
+    # in EC2 instances).
+    if cfg.devices == ["nodev"]
+    then null
+    else realGrub;
+
+  grubEfi =
+    # EFI version of Grub v2
+    if cfg.efiSupport && (cfg.version == 2)
+    then realGrub.override { efiSupport = cfg.efiSupport; }
+    else null;
+
+  f = x: if x == null then "" else "" + x;
+
+  grubConfig = args:
+    let
+      efiSysMountPoint = if args.efiSysMountPoint == null then args.path else args.efiSysMountPoint;
+      efiSysMountPoint' = replaceChars [ "/" ] [ "-" ] efiSysMountPoint;
+    in
+    pkgs.writeText "grub-config.xml" (builtins.toXML
+    { splashImage = f cfg.splashImage;
+      splashMode = f cfg.splashMode;
+      backgroundColor = f cfg.backgroundColor;
+      grub = f grub;
+      grubTarget = f (grub.grubTarget or "");
+      shell = "${pkgs.runtimeShell}";
+      fullName = (builtins.parseDrvName realGrub.name).name;
+      fullVersion = (builtins.parseDrvName realGrub.name).version;
+      grubEfi = f grubEfi;
+      grubTargetEfi = if cfg.efiSupport && (cfg.version == 2) then f (grubEfi.grubTarget or "") else "";
+      bootPath = args.path;
+      storePath = config.boot.loader.grub.storePath;
+      bootloaderId = if args.efiBootloaderId == null then "NixOS${efiSysMountPoint'}" else args.efiBootloaderId;
+      timeout = if config.boot.loader.timeout == null then -1 else config.boot.loader.timeout;
+      inherit efiSysMountPoint;
+      inherit (args) devices;
+      inherit (efi) canTouchEfiVariables;
+      inherit (cfg)
+        version extraConfig extraPerEntryConfig extraEntries forceInstall useOSProber
+        extraEntriesBeforeNixOS extraPrepareConfig extraInitrd configurationLimit copyKernels
+        default fsIdentifier efiSupport efiInstallAsRemovable gfxmodeEfi gfxmodeBios;
+      path = (makeBinPath ([
+        pkgs.coreutils pkgs.gnused pkgs.gnugrep pkgs.findutils pkgs.diffutils pkgs.btrfs-progs
+        pkgs.utillinux ]
+        ++ (optional (cfg.efiSupport && (cfg.version == 2)) pkgs.efibootmgr)
+        ++ (optionals cfg.useOSProber [pkgs.busybox pkgs.os-prober])
+      )) + ":" + (makeSearchPathOutput "bin" "sbin" [
+        pkgs.mdadm pkgs.utillinux
+      ]);
+      font = if cfg.font == null then ""
+        else (if lib.last (lib.splitString "." cfg.font) == "pf2"
+             then cfg.font
+             else "${convertedFont}");
+    });
+
+  bootDeviceCounters = fold (device: attr: attr // { "${device}" = (attr."${device}" or 0) + 1; }) {}
+    (concatMap (args: args.devices) cfg.mirroredBoots);
+
+  convertedFont = (pkgs.runCommand "grub-font-converted.pf2" {}
+           (builtins.concatStringsSep " "
+             ([ "${realGrub}/bin/grub-mkfont"
+               cfg.font
+               "--output" "$out"
+             ] ++ (optional (cfg.fontSize!=null) "--size ${toString cfg.fontSize}")))
+         );
+
+  defaultSplash = "${pkgs.nixos-artwork.wallpapers.simple-dark-gray-bootloader}/share/artwork/gnome/nix-wallpaper-simple-dark-gray_bootloader.png";
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    boot.loader.grub = {
+
+      enable = mkOption {
+        default = !config.boot.isContainer;
+        type = types.bool;
+        description = ''
+          Whether to enable the GNU GRUB boot loader.
+        '';
+      };
+
+      version = mkOption {
+        default = 2;
+        example = 1;
+        type = types.int;
+        description = ''
+          The version of GRUB to use: <literal>1</literal> for GRUB
+          Legacy (versions 0.9x), or <literal>2</literal> (the
+          default) for GRUB 2.
+        '';
+      };
+
+      device = mkOption {
+        default = "";
+        example = "/dev/disk/by-id/wwn-0x500001234567890a";
+        type = types.str;
+        description = ''
+          The device on which the GRUB boot loader will be installed.
+          The special value <literal>nodev</literal> means that a GRUB
+          boot menu will be generated, but GRUB itself will not
+          actually be installed.  To install GRUB on multiple devices,
+          use <literal>boot.loader.grub.devices</literal>.
+        '';
+      };
+
+      devices = mkOption {
+        default = [];
+        example = [ "/dev/disk/by-id/wwn-0x500001234567890a" ];
+        type = types.listOf types.str;
+        description = ''
+          The devices on which the boot loader, GRUB, will be
+          installed. Can be used instead of <literal>device</literal> to
+          install GRUB onto multiple devices.
+        '';
+      };
+
+      mirroredBoots = mkOption {
+        default = [ ];
+        example = [
+          { path = "/boot1"; devices = [ "/dev/disk/by-id/wwn-0x500001234567890a" ]; }
+          { path = "/boot2"; devices = [ "/dev/disk/by-id/wwn-0x500009876543210a" ]; }
+        ];
+        description = ''
+          Mirror the boot configuration to multiple partitions and install grub
+          to the respective devices corresponding to those partitions.
+        '';
+
+        type = with types; listOf (submodule {
+          options = {
+
+            path = mkOption {
+              example = "/boot1";
+              type = types.str;
+              description = ''
+                The path to the boot directory where GRUB will be written. Generally
+                this boot path should double as an EFI path.
+              '';
+            };
+
+            efiSysMountPoint = mkOption {
+              default = null;
+              example = "/boot1/efi";
+              type = types.nullOr types.str;
+              description = ''
+                The path to the efi system mount point. Usually this is the same
+                partition as the above path and can be left as null.
+              '';
+            };
+
+            efiBootloaderId = mkOption {
+              default = null;
+              example = "NixOS-fsid";
+              type = types.nullOr types.str;
+              description = ''
+                The id of the bootloader to store in efi nvram.
+                The default is to name it NixOS and append the path or efiSysMountPoint.
+                This is only used if <literal>boot.loader.efi.canTouchEfiVariables</literal> is true.
+              '';
+            };
+
+            devices = mkOption {
+              default = [ ];
+              example = [ "/dev/disk/by-id/wwn-0x500001234567890a" "/dev/disk/by-id/wwn-0x500009876543210a" ];
+              type = types.listOf types.str;
+              description = ''
+                The path to the devices which will have the GRUB MBR written.
+                Note these are typically device paths and not paths to partitions.
+              '';
+            };
+
+          };
+        });
+      };
+
+      configurationName = mkOption {
+        default = "";
+        example = "Stable 2.6.21";
+        type = types.str;
+        description = ''
+          GRUB entry name instead of default.
+        '';
+      };
+
+      storePath = mkOption {
+        default = "/nix/store";
+        type = types.str;
+        description = ''
+          Path to the Nix store when looking for kernels at boot.
+          Only makes sense when copyKernels is false.
+        '';
+      };
+
+      extraPrepareConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Additional bash commands to be run at the script that
+          prepares the GRUB menu entries.
+        '';
+      };
+
+      extraConfig = mkOption {
+        default = "";
+        example = "serial; terminal_output.serial";
+        type = types.lines;
+        description = ''
+          Additional GRUB commands inserted in the configuration file
+          just before the menu entries.
+        '';
+      };
+
+      extraPerEntryConfig = mkOption {
+        default = "";
+        example = "root (hd0)";
+        type = types.lines;
+        description = ''
+          Additional GRUB commands inserted in the configuration file
+          at the start of each NixOS menu entry.
+        '';
+      };
+
+      extraEntries = mkOption {
+        default = "";
+        type = types.lines;
+        example = ''
+          # GRUB 1 example (not GRUB 2 compatible)
+          title Windows
+            chainloader (hd0,1)+1
+
+          # GRUB 2 example
+          menuentry "Windows 7" {
+            chainloader (hd0,4)+1
+          }
+
+          # GRUB 2 with UEFI example, chainloading another distro
+          menuentry "Fedora" {
+            set root=(hd1,1)
+            chainloader /efi/fedora/grubx64.efi
+          }
+        '';
+        description = ''
+          Any additional entries you want added to the GRUB boot menu.
+        '';
+      };
+
+      extraEntriesBeforeNixOS = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Whether extraEntries are included before the default option.
+        '';
+      };
+
+      extraFiles = mkOption {
+        type = types.attrsOf types.path;
+        default = {};
+        example = literalExample ''
+          { "memtest.bin" = "''${pkgs.memtest86plus}/memtest.bin"; }
+        '';
+        description = ''
+          A set of files to be copied to <filename>/boot</filename>.
+          Each attribute name denotes the destination file name in
+          <filename>/boot</filename>, while the corresponding
+          attribute value specifies the source file.
+        '';
+      };
+
+      extraInitrd = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/boot/extra_initramfs.gz";
+        description = ''
+          The path to a second initramfs to be supplied to the kernel.
+          This ramfs will not be copied to the store, so that it can
+          contain secrets such as LUKS keyfiles or ssh keys.
+          This implies that rolling back to a previous configuration
+          won't rollback the state of this file.
+        '';
+      };
+
+      useOSProber = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          If set to true, append entries for other OSs detected by os-prober.
+        '';
+      };
+
+      splashImage = mkOption {
+        type = types.nullOr types.path;
+        example = literalExample "./my-background.png";
+        description = ''
+          Background image used for GRUB.
+          Set to <literal>null</literal> to run GRUB in text mode.
+
+          <note><para>
+          For grub 1:
+          It must be a 640x480,
+          14-colour image in XPM format, optionally compressed with
+          <command>gzip</command> or <command>bzip2</command>.
+          </para></note>
+
+          <note><para>
+          For grub 2:
+          File must be one of .png, .tga, .jpg, or .jpeg. JPEG images must
+          not be progressive.
+          The image will be scaled if necessary to fit the screen.
+          </para></note>
+        '';
+      };
+
+      backgroundColor = mkOption {
+        type = types.nullOr types.string;
+        example = "#7EBAE4";
+        default = null;
+        description = ''
+          Background color to be used for GRUB to fill the areas the image isn't filling.
+
+          <note><para>
+          This options has no effect for GRUB 1.
+          </para></note>
+        '';
+      };
+
+      splashMode = mkOption {
+        type = types.enum [ "normal" "stretch" ];
+        default = "stretch";
+        description = ''
+          Whether to stretch the image or show the image in the top-left corner unstretched.
+
+          <note><para>
+          This options has no effect for GRUB 1.
+          </para></note>
+        '';
+      };
+
+      font = mkOption {
+        type = types.nullOr types.path;
+        default = "${realGrub}/share/grub/unicode.pf2";
+        description = ''
+          Path to a TrueType, OpenType, or pf2 font to be used by Grub.
+        '';
+      };
+
+      fontSize = mkOption {
+        type = types.nullOr types.int;
+        example = literalExample 16;
+        default = null;
+        description = ''
+          Font size for the grub menu. Ignored unless <literal>font</literal>
+          is set to a ttf or otf font.
+        '';
+      };
+
+      gfxmodeEfi = mkOption {
+        default = "auto";
+        example = "1024x768";
+        type = types.str;
+        description = ''
+          The gfxmode to pass to GRUB when loading a graphical boot interface under EFI.
+        '';
+      };
+
+      gfxmodeBios = mkOption {
+        default = "1024x768";
+        example = "auto";
+        type = types.str;
+        description = ''
+          The gfxmode to pass to GRUB when loading a graphical boot interface under BIOS.
+        '';
+      };
+
+      configurationLimit = mkOption {
+        default = 100;
+        example = 120;
+        type = types.int;
+        description = ''
+          Maximum of configurations in boot menu. GRUB has problems when
+          there are too many entries.
+        '';
+      };
+
+      copyKernels = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Whether the GRUB menu builder should copy kernels and initial
+          ramdisks to /boot.  This is done automatically if /boot is
+          on a different partition than /.
+        '';
+      };
+
+      default = mkOption {
+        default = "0";
+        type = types.either types.int types.str;
+        apply = toString;
+        description = ''
+          Index of the default menu item to be booted.
+        '';
+      };
+
+      fsIdentifier = mkOption {
+        default = "uuid";
+        type = types.enum [ "uuid" "label" "provided" ];
+        description = ''
+          Determines how GRUB will identify devices when generating the
+          configuration file. A value of uuid / label signifies that grub
+          will always resolve the uuid or label of the device before using
+          it in the configuration. A value of provided means that GRUB will
+          use the device name as show in <command>df</command> or
+          <command>mount</command>. Note, zfs zpools / datasets are ignored
+          and will always be mounted using their labels.
+        '';
+      };
+
+      zfsSupport = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Whether GRUB should be built against libzfs.
+          ZFS support is only available for GRUB v2.
+          This option is ignored for GRUB v1.
+        '';
+      };
+
+      efiSupport = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Whether GRUB should be built with EFI support.
+          EFI support is only available for GRUB v2.
+          This option is ignored for GRUB v1.
+        '';
+      };
+
+      efiInstallAsRemovable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Whether to invoke <literal>grub-install</literal> with
+          <literal>--removable</literal>.</para>
+
+          <para>Unless you turn this on, GRUB will install itself somewhere in
+          <literal>boot.loader.efi.efiSysMountPoint</literal> (exactly where
+          depends on other config variables). If you've set
+          <literal>boot.loader.efi.canTouchEfiVariables</literal> *AND* you
+          are currently booted in UEFI mode, then GRUB will use
+          <literal>efibootmgr</literal> to modify the boot order in the
+          EFI variables of your firmware to include this location. If you are
+          *not* booted in UEFI mode at the time GRUB is being installed, the
+          NVRAM will not be modified, and your system will not find GRUB at
+          boot time. However, GRUB will still return success so you may miss
+          the warning that gets printed ("<literal>efibootmgr: EFI variables
+          are not supported on this system.</literal>").</para>
+
+          <para>If you turn this feature on, GRUB will install itself in a
+          special location within <literal>efiSysMountPoint</literal> (namely
+          <literal>EFI/boot/boot$arch.efi</literal>) which the firmwares
+          are hardcoded to try first, regardless of NVRAM EFI variables.</para>
+
+          <para>To summarize, turn this on if:
+          <itemizedlist>
+            <listitem><para>You are installing NixOS and want it to boot in UEFI mode,
+            but you are currently booted in legacy mode</para></listitem>
+            <listitem><para>You want to make a drive that will boot regardless of
+            the NVRAM state of the computer (like a USB "removable" drive)</para></listitem>
+            <listitem><para>You simply dislike the idea of depending on NVRAM
+            state to make your drive bootable</para></listitem>
+          </itemizedlist>
+        '';
+      };
+
+      enableCryptodisk = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Enable support for encrypted partitions. GRUB should automatically
+          unlock the correct encrypted partition and look for filesystems.
+        '';
+      };
+
+      forceInstall = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Whether to try and forcibly install GRUB even if problems are
+          detected. It is not recommended to enable this unless you know what
+          you are doing.
+        '';
+      };
+
+      trustedBoot = {
+
+        enable = mkOption {
+          default = false;
+          type = types.bool;
+          description = ''
+            Enable trusted boot. GRUB will measure all critical components during
+            the boot process to offer TCG (TPM) support.
+          '';
+        };
+
+        systemHasTPM = mkOption {
+          default = "";
+          example = "YES_TPM_is_activated";
+          type = types.string;
+          description = ''
+            Assertion that the target system has an activated TPM. It is a safety
+            check before allowing the activation of 'trustedBoot.enable'. TrustedBoot
+            WILL FAIL TO BOOT YOUR SYSTEM if no TPM is available.
+          '';
+        };
+
+        isHPLaptop = mkOption {
+          default = false;
+          type = types.bool;
+          description = ''
+            Use a special version of TrustedGRUB that is needed by some HP laptops
+            and works only for the HP laptops.
+          '';
+        };
+
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkMerge [
+
+    { boot.loader.grub.splashImage = mkDefault (
+        if cfg.version == 1 then pkgs.fetchurl {
+          url = http://www.gnome-look.org/CONTENT/content-files/36909-soft-tux.xpm.gz;
+          sha256 = "14kqdx2lfqvh40h6fjjzqgff1mwk74dmbjvmqphi6azzra7z8d59";
+        }
+        # GRUB 1.97 doesn't support gzipped XPMs.
+        else defaultSplash);
+    }
+
+    (mkIf (cfg.splashImage == defaultSplash) {
+      boot.loader.grub.backgroundColor = mkDefault "#2F302F";
+      boot.loader.grub.splashMode = mkDefault "normal";
+    })
+
+    (mkIf cfg.enable {
+
+      boot.loader.grub.devices = optional (cfg.device != "") cfg.device;
+
+      boot.loader.grub.mirroredBoots = optionals (cfg.devices != [ ]) [
+        { path = "/boot"; inherit (cfg) devices; inherit (efi) efiSysMountPoint; }
+      ];
+
+      system.build.installBootLoader =
+        let
+          install-grub-pl = pkgs.substituteAll {
+            src = ./install-grub.pl;
+            inherit (pkgs) utillinux;
+            btrfsprogs = pkgs.btrfs-progs;
+          };
+        in pkgs.writeScript "install-grub.sh" (''
+        #!${pkgs.runtimeShell}
+        set -e
+        export PERL5LIB=${makePerlPath (with pkgs.perlPackages; [ FileSlurp XMLLibXML XMLSAX XMLSAXBase ListCompare ])}
+        ${optionalString cfg.enableCryptodisk "export GRUB_ENABLE_CRYPTODISK=y"}
+      '' + flip concatMapStrings cfg.mirroredBoots (args: ''
+        ${pkgs.perl}/bin/perl ${install-grub-pl} ${grubConfig args} $@
+      ''));
+
+      system.build.grub = grub;
+
+      # Common attribute for boot loaders so only one of them can be
+      # set at once.
+      system.boot.loader.id = "grub";
+
+      environment.systemPackages = optional (grub != null) grub;
+
+      boot.loader.grub.extraPrepareConfig =
+        concatStrings (mapAttrsToList (n: v: ''
+          ${pkgs.coreutils}/bin/cp -pf "${v}" "/boot/${n}"
+        '') config.boot.loader.grub.extraFiles);
+
+      assertions = [
+        {
+          assertion = !cfg.zfsSupport || cfg.version == 2;
+          message = "Only GRUB version 2 provides ZFS support";
+        }
+        {
+          assertion = cfg.mirroredBoots != [ ];
+          message = "You must set the option ‘boot.loader.grub.devices’ or "
+            + "'boot.loader.grub.mirroredBoots' to make the system bootable.";
+        }
+        {
+          assertion = cfg.efiSupport || all (c: c < 2) (mapAttrsToList (_: c: c) bootDeviceCounters);
+          message = "You cannot have duplicated devices in mirroredBoots";
+        }
+        {
+          assertion = !cfg.trustedBoot.enable || cfg.version == 2;
+          message = "Trusted GRUB is only available for GRUB 2";
+        }
+        {
+          assertion = !cfg.efiSupport || !cfg.trustedBoot.enable;
+          message = "Trusted GRUB does not have EFI support";
+        }
+        {
+          assertion = !cfg.zfsSupport || !cfg.trustedBoot.enable;
+          message = "Trusted GRUB does not have ZFS support";
+        }
+        {
+          assertion = !cfg.trustedBoot.enable || cfg.trustedBoot.systemHasTPM == "YES_TPM_is_activated";
+          message = "Trusted GRUB can break the system! Confirm that the system has an activated TPM by setting 'systemHasTPM'.";
+        }
+        {
+          assertion = cfg.efiInstallAsRemovable -> cfg.efiSupport;
+          message = "If you wish to to use boot.loader.grub.efiInstallAsRemovable, then turn on boot.loader.grub.efiSupport";
+        }
+        {
+          assertion = cfg.efiInstallAsRemovable -> !config.boot.loader.efi.canTouchEfiVariables;
+          message = "If you wish to to use boot.loader.grub.efiInstallAsRemovable, then turn off boot.loader.efi.canTouchEfiVariables";
+        }
+      ] ++ flip concatMap cfg.mirroredBoots (args: [
+        {
+          assertion = args.devices != [ ];
+          message = "A boot path cannot have an empty devices string in ${args.path}";
+        }
+        {
+          assertion = hasPrefix "/" args.path;
+          message = "Boot paths must be absolute, not ${args.path}";
+        }
+        {
+          assertion = if args.efiSysMountPoint == null then true else hasPrefix "/" args.efiSysMountPoint;
+          message = "EFI paths must be absolute, not ${args.efiSysMountPoint}";
+        }
+      ] ++ flip map args.devices (device: {
+        assertion = device == "nodev" || hasPrefix "/" device;
+        message = "GRUB devices must be absolute paths, not ${device} in ${args.path}";
+      }));
+    })
+
+  ];
+
+
+  imports =
+    [ (mkRemovedOptionModule [ "boot" "loader" "grub" "bootDevice" ] "")
+      (mkRenamedOptionModule [ "boot" "copyKernels" ] [ "boot" "loader" "grub" "copyKernels" ])
+      (mkRenamedOptionModule [ "boot" "extraGrubEntries" ] [ "boot" "loader" "grub" "extraEntries" ])
+      (mkRenamedOptionModule [ "boot" "extraGrubEntriesBeforeNixos" ] [ "boot" "loader" "grub" "extraEntriesBeforeNixOS" ])
+      (mkRenamedOptionModule [ "boot" "grubDevice" ] [ "boot" "loader" "grub" "device" ])
+      (mkRenamedOptionModule [ "boot" "bootMount" ] [ "boot" "loader" "grub" "bootDevice" ])
+      (mkRenamedOptionModule [ "boot" "grubSplashImage" ] [ "boot" "loader" "grub" "splashImage" ])
+    ];
+
+}
diff --git a/nixpkgs/nixos/modules/system/boot/loader/grub/install-grub.pl b/nixpkgs/nixos/modules/system/boot/loader/grub/install-grub.pl
new file mode 100644
index 000000000000..bda6a3136407
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/grub/install-grub.pl
@@ -0,0 +1,612 @@
+use strict;
+use warnings;
+use Class::Struct;
+use XML::LibXML;
+use File::Basename;
+use File::Path;
+use File::stat;
+use File::Copy;
+use File::Slurp;
+use File::Temp;
+require List::Compare;
+use POSIX;
+use Cwd;
+
+# system.build.toplevel path
+my $defaultConfig = $ARGV[1] or die;
+
+# Grub config XML generated by grubConfig function in grub.nix
+my $dom = XML::LibXML->load_xml(location => $ARGV[0]);
+
+sub get { my ($name) = @_; return $dom->findvalue("/expr/attrs/attr[\@name = '$name']/*/\@value"); }
+
+sub readFile {
+    my ($fn) = @_; local $/ = undef;
+    open FILE, "<$fn" or return undef; my $s = <FILE>; close FILE;
+    local $/ = "\n"; chomp $s; return $s;
+}
+
+sub writeFile {
+    my ($fn, $s) = @_;
+    open FILE, ">$fn" or die "cannot create $fn: $!\n";
+    print FILE $s or die;
+    close FILE or die;
+}
+
+sub runCommand {
+    my ($cmd) = @_;
+    open FILE, "$cmd 2>/dev/null |" or die "Failed to execute: $cmd\n";
+    my @ret = <FILE>;
+    close FILE;
+    return ($?, @ret);
+}
+
+my $grub = get("grub");
+my $grubVersion = int(get("version"));
+my $grubTarget = get("grubTarget");
+my $extraConfig = get("extraConfig");
+my $extraPrepareConfig = get("extraPrepareConfig");
+my $extraPerEntryConfig = get("extraPerEntryConfig");
+my $extraEntries = get("extraEntries");
+my $extraEntriesBeforeNixOS = get("extraEntriesBeforeNixOS") eq "true";
+my $extraInitrd = get("extraInitrd");
+my $splashImage = get("splashImage");
+my $splashMode = get("splashMode");
+my $backgroundColor = get("backgroundColor");
+my $configurationLimit = int(get("configurationLimit"));
+my $copyKernels = get("copyKernels") eq "true";
+my $timeout = int(get("timeout"));
+my $defaultEntry = get("default");
+my $fsIdentifier = get("fsIdentifier");
+my $grubEfi = get("grubEfi");
+my $grubTargetEfi = get("grubTargetEfi");
+my $bootPath = get("bootPath");
+my $storePath = get("storePath");
+my $canTouchEfiVariables = get("canTouchEfiVariables");
+my $efiInstallAsRemovable = get("efiInstallAsRemovable");
+my $efiSysMountPoint = get("efiSysMountPoint");
+my $gfxmodeEfi = get("gfxmodeEfi");
+my $gfxmodeBios = get("gfxmodeBios");
+my $bootloaderId = get("bootloaderId");
+my $forceInstall = get("forceInstall");
+my $font = get("font");
+$ENV{'PATH'} = get("path");
+
+die "unsupported GRUB version\n" if $grubVersion != 1 && $grubVersion != 2;
+
+print STDERR "updating GRUB $grubVersion menu...\n";
+
+mkpath("$bootPath/grub", 0, 0700);
+
+# Discover whether the bootPath is on the same filesystem as / and
+# /nix/store.  If not, then all kernels and initrds must be copied to
+# the bootPath.
+if (stat($bootPath)->dev != stat("/nix/store")->dev) {
+    $copyKernels = 1;
+}
+
+# Discover information about the location of the bootPath
+struct(Fs => {
+    device => '$',
+    type => '$',
+    mount => '$',
+});
+sub PathInMount {
+    my ($path, $mount) = @_;
+    my @splitMount = split /\//, $mount;
+    my @splitPath = split /\//, $path;
+    if ($#splitPath < $#splitMount) {
+        return 0;
+    }
+    for (my $i = 0; $i <= $#splitMount; $i++) {
+        if ($splitMount[$i] ne $splitPath[$i]) {
+            return 0;
+        }
+    }
+    return 1;
+}
+
+# Figure out what filesystem is used for the directory with init/initrd/kernel files
+sub GetFs {
+    my ($dir) = @_;
+    my $bestFs = Fs->new(device => "", type => "", mount => "");
+    foreach my $fs (read_file("/proc/self/mountinfo")) {
+        chomp $fs;
+        my @fields = split / /, $fs;
+        my $mountPoint = $fields[4];
+        next unless -d $mountPoint;
+        my @mountOptions = split /,/, $fields[5];
+
+        # Skip the optional fields.
+        my $n = 6; $n++ while $fields[$n] ne "-"; $n++;
+        my $fsType = $fields[$n];
+        my $device = $fields[$n + 1];
+        my @superOptions = split /,/, $fields[$n + 2];
+
+        # Skip the bind-mount on /nix/store.
+        next if $mountPoint eq "/nix/store" && (grep { $_ eq "rw" } @superOptions);
+        # Skip mount point generated by systemd-efi-boot-generator?
+        next if $fsType eq "autofs";
+
+        # Ensure this matches the intended directory
+        next unless PathInMount($dir, $mountPoint);
+
+        # Is it better than our current match?
+        if (length($mountPoint) > length($bestFs->mount)) {
+            $bestFs = Fs->new(device => $device, type => $fsType, mount => $mountPoint);
+        }
+    }
+    return $bestFs;
+}
+struct (Grub => {
+    path => '$',
+    search => '$',
+});
+my $driveid = 1;
+sub GrubFs {
+    my ($dir) = @_;
+    my $fs = GetFs($dir);
+    my $path = substr($dir, length($fs->mount));
+    if (substr($path, 0, 1) ne "/") {
+      $path = "/$path";
+    }
+    my $search = "";
+
+    if ($grubVersion > 1) {
+        # ZFS is completely separate logic as zpools are always identified by a label
+        # or custom UUID
+        if ($fs->type eq 'zfs') {
+            my $sid = index($fs->device, '/');
+
+            if ($sid < 0) {
+                $search = '--label ' . $fs->device;
+                $path = '/@' . $path;
+            } else {
+                $search = '--label ' . substr($fs->device, 0, $sid);
+                $path = '/' . substr($fs->device, $sid) . '/@' . $path;
+            }
+        } else {
+            my %types = ('uuid' => '--fs-uuid', 'label' => '--label');
+
+            if ($fsIdentifier eq 'provided') {
+                # If the provided dev is identifying the partition using a label or uuid,
+                # we should get the label / uuid and do a proper search
+                my @matches = $fs->device =~ m/\/dev\/disk\/by-(label|uuid)\/(.*)/;
+                if ($#matches > 1) {
+                    die "Too many matched devices"
+                } elsif ($#matches == 1) {
+                    $search = "$types{$matches[0]} $matches[1]"
+                }
+            } else {
+                # Determine the identifying type
+                $search = $types{$fsIdentifier} . ' ';
+
+                # Based on the type pull in the identifier from the system
+                my ($status, @devInfo) = runCommand("@utillinux@/bin/blkid -o export @{[$fs->device]}");
+                if ($status != 0) {
+                    die "Failed to get blkid info (returned $status) for @{[$fs->mount]} on @{[$fs->device]}";
+                }
+                my @matches = join("", @devInfo) =~ m/@{[uc $fsIdentifier]}=([^\n]*)/;
+                if ($#matches != 0) {
+                    die "Couldn't find a $types{$fsIdentifier} for @{[$fs->device]}\n"
+                }
+                $search .= $matches[0];
+            }
+
+            # BTRFS is a special case in that we need to fix the referrenced path based on subvolumes
+            if ($fs->type eq 'btrfs') {
+                my ($status, @id_info) = runCommand("@btrfsprogs@/bin/btrfs subvol show @{[$fs->mount]}");
+                if ($status != 0) {
+                    die "Failed to retrieve subvolume info for @{[$fs->mount]}\n";
+                }
+                my @ids = join("\n", @id_info) =~ m/^(?!\/\n).*Subvolume ID:[ \t\n]*([0-9]+)/s;
+                if ($#ids > 0) {
+                    die "Btrfs subvol name for @{[$fs->device]} listed multiple times in mount\n"
+                } elsif ($#ids == 0) {
+                    my ($status, @path_info) = runCommand("@btrfsprogs@/bin/btrfs subvol list @{[$fs->mount]}");
+                    if ($status != 0) {
+                        die "Failed to find @{[$fs->mount]} subvolume id from btrfs\n";
+                    }
+                    my @paths = join("", @path_info) =~ m/ID $ids[0] [^\n]* path ([^\n]*)/;
+                    if ($#paths > 0) {
+                        die "Btrfs returned multiple paths for a single subvolume id, mountpoint @{[$fs->mount]}\n";
+                    } elsif ($#paths != 0) {
+                        die "Btrfs did not return a path for the subvolume at @{[$fs->mount]}\n";
+                    }
+                    $path = "/$paths[0]$path";
+                }
+            }
+        }
+        if (not $search eq "") {
+            $search = "search --set=drive$driveid " . $search;
+            $path = "(\$drive$driveid)$path";
+            $driveid += 1;
+        }
+    }
+    return Grub->new(path => $path, search => $search);
+}
+my $grubBoot = GrubFs($bootPath);
+my $grubStore;
+if ($copyKernels == 0) {
+    $grubStore = GrubFs($storePath);
+}
+my $extraInitrdPath;
+if ($extraInitrd) {
+    if (! -f $extraInitrd) {
+        print STDERR "Warning: the specified extraInitrd " . $extraInitrd . " doesn't exist. Your system won't boot without it.\n";
+    }
+    $extraInitrdPath = GrubFs($extraInitrd);
+}
+
+# Generate the header.
+my $conf .= "# Automatically generated.  DO NOT EDIT THIS FILE!\n";
+
+if ($grubVersion == 1) {
+    $conf .= "
+        default $defaultEntry
+        timeout $timeout
+    ";
+    if ($splashImage) {
+        copy $splashImage, "$bootPath/background.xpm.gz" or die "cannot copy $splashImage to $bootPath\n";
+        $conf .= "splashimage " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/background.xpm.gz\n";
+    }
+}
+
+else {
+    if ($copyKernels == 0) {
+        $conf .= "
+            " . $grubStore->search;
+    }
+    # FIXME: should use grub-mkconfig.
+    $conf .= "
+        " . $grubBoot->search . "
+        if [ -s \$prefix/grubenv ]; then
+          load_env
+        fi
+
+        # ‘grub-reboot’ sets a one-time saved entry, which we process here and
+        # then delete.
+        if [ \"\${next_entry}\" ]; then
+          set default=\"\${next_entry}\"
+          set next_entry=
+          save_env next_entry
+          set timeout=1
+        else
+          set default=$defaultEntry
+          set timeout=$timeout
+        fi
+
+        # Setup the graphics stack for bios and efi systems
+        if [ \"\${grub_platform}\" = \"efi\" ]; then
+          insmod efi_gop
+          insmod efi_uga
+        else
+          insmod vbe
+        fi
+    ";
+
+    if ($font) {
+        copy $font, "$bootPath/converted-font.pf2" or die "cannot copy $font to $bootPath\n";
+        $conf .= "
+            insmod font
+            if loadfont " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/converted-font.pf2; then
+              insmod gfxterm
+              if [ \"\${grub_platform}\" = \"efi\" ]; then
+                set gfxmode=$gfxmodeEfi
+                set gfxpayload=keep
+              else
+                set gfxmode=$gfxmodeBios
+                set gfxpayload=text
+              fi
+              terminal_output gfxterm
+            fi
+        ";
+    }
+    if ($splashImage) {
+        # Keeps the image's extension.
+        my ($filename, $dirs, $suffix) = fileparse($splashImage, qr"\..[^.]*$");
+        # The module for jpg is jpeg.
+        if ($suffix eq ".jpg") {
+            $suffix = ".jpeg";
+        }
+		if ($backgroundColor) {
+			$conf .= "
+		    background_color '$backgroundColor'
+		    ";
+		}
+        copy $splashImage, "$bootPath/background$suffix" or die "cannot copy $splashImage to $bootPath\n";
+        $conf .= "
+            insmod " . substr($suffix, 1) . "
+            if background_image --mode '$splashMode' " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/background$suffix; then
+              set color_normal=white/black
+              set color_highlight=black/white
+            else
+              set menu_color_normal=cyan/blue
+              set menu_color_highlight=white/blue
+            fi
+        ";
+    }
+}
+
+$conf .= "$extraConfig\n";
+
+
+# Generate the menu entries.
+$conf .= "\n";
+
+my %copied;
+mkpath("$bootPath/kernels", 0, 0755) if $copyKernels;
+
+sub copyToKernelsDir {
+    my ($path) = @_;
+    return $grubStore->path . substr($path, length("/nix/store")) unless $copyKernels;
+    $path =~ /\/nix\/store\/(.*)/ or die;
+    my $name = $1; $name =~ s/\//-/g;
+    my $dst = "$bootPath/kernels/$name";
+    # Don't copy the file if $dst already exists.  This means that we
+    # have to create $dst atomically to prevent partially copied
+    # kernels or initrd if this script is ever interrupted.
+    if (! -e $dst) {
+        my $tmp = "$dst.tmp";
+        copy $path, $tmp or die "cannot copy $path to $tmp\n";
+        rename $tmp, $dst or die "cannot rename $tmp to $dst\n";
+    }
+    $copied{$dst} = 1;
+    return ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/kernels/$name";
+}
+
+sub addEntry {
+    my ($name, $path) = @_;
+    return unless -e "$path/kernel" && -e "$path/initrd";
+
+    my $kernel = copyToKernelsDir(Cwd::abs_path("$path/kernel"));
+    my $initrd = copyToKernelsDir(Cwd::abs_path("$path/initrd"));
+    if ($extraInitrd) {
+        $initrd .= " " .$extraInitrdPath->path;
+    }
+    my $xen = -e "$path/xen.gz" ? copyToKernelsDir(Cwd::abs_path("$path/xen.gz")) : undef;
+
+    # FIXME: $confName
+
+    my $kernelParams =
+        "systemConfig=" . Cwd::abs_path($path) . " " .
+        "init=" . Cwd::abs_path("$path/init") . " " .
+        readFile("$path/kernel-params");
+    my $xenParams = $xen && -e "$path/xen-params" ? readFile("$path/xen-params") : "";
+
+    if ($grubVersion == 1) {
+        $conf .= "title $name\n";
+        $conf .= "  $extraPerEntryConfig\n" if $extraPerEntryConfig;
+        $conf .= "  kernel $xen $xenParams\n" if $xen;
+        $conf .= "  " . ($xen ? "module" : "kernel") . " $kernel $kernelParams\n";
+        $conf .= "  " . ($xen ? "module" : "initrd") . " $initrd\n\n";
+    } else {
+        $conf .= "menuentry \"$name\" {\n";
+        $conf .= $grubBoot->search . "\n";
+        if ($copyKernels == 0) {
+            $conf .= $grubStore->search . "\n";
+        }
+        if ($extraInitrd) {
+            $conf .= $extraInitrdPath->search . "\n";
+        }
+        $conf .= "  $extraPerEntryConfig\n" if $extraPerEntryConfig;
+        $conf .= "  multiboot $xen $xenParams\n" if $xen;
+        $conf .= "  " . ($xen ? "module" : "linux") . " $kernel $kernelParams\n";
+        $conf .= "  " . ($xen ? "module" : "initrd") . " $initrd\n";
+        $conf .= "}\n\n";
+    }
+}
+
+
+# Add default entries.
+$conf .= "$extraEntries\n" if $extraEntriesBeforeNixOS;
+
+addEntry("NixOS - Default", $defaultConfig);
+
+$conf .= "$extraEntries\n" unless $extraEntriesBeforeNixOS;
+
+my $grubBootPath = $grubBoot->path;
+# extraEntries could refer to @bootRoot@, which we have to substitute
+$conf =~ s/\@bootRoot\@/$grubBootPath/g;
+
+# Emit submenus for all system profiles.
+sub addProfile {
+    my ($profile, $description) = @_;
+
+    # Add entries for all generations of this profile.
+    $conf .= "submenu \"$description\" {\n" if $grubVersion == 2;
+
+    sub nrFromGen { my ($x) = @_; $x =~ /\/\w+-(\d+)-link/; return $1; }
+
+    my @links = sort
+        { nrFromGen($b) <=> nrFromGen($a) }
+        (glob "$profile-*-link");
+
+    my $curEntry = 0;
+    foreach my $link (@links) {
+        last if $curEntry++ >= $configurationLimit;
+        if (! -e "$link/nixos-version") {
+            warn "skipping corrupt system profile entry ‘$link’\n";
+            next;
+        }
+        my $date = strftime("%F", localtime(lstat($link)->mtime));
+        my $version =
+            -e "$link/nixos-version"
+            ? readFile("$link/nixos-version")
+            : basename((glob(dirname(Cwd::abs_path("$link/kernel")) . "/lib/modules/*"))[0]);
+        addEntry("NixOS - Configuration " . nrFromGen($link) . " ($date - $version)", $link);
+    }
+
+    $conf .= "}\n" if $grubVersion == 2;
+}
+
+addProfile "/nix/var/nix/profiles/system", "NixOS - All configurations";
+
+if ($grubVersion == 2) {
+    for my $profile (glob "/nix/var/nix/profiles/system-profiles/*") {
+        my $name = basename($profile);
+        next unless $name =~ /^\w+$/;
+        addProfile $profile, "NixOS - Profile '$name'";
+    }
+}
+
+# Run extraPrepareConfig in sh
+if ($extraPrepareConfig ne "") {
+  system((get("shell"), "-c", $extraPrepareConfig));
+}
+
+# write the GRUB config.
+my $confFile = $grubVersion == 1 ? "$bootPath/grub/menu.lst" : "$bootPath/grub/grub.cfg";
+my $tmpFile = $confFile . ".tmp";
+writeFile($tmpFile, $conf);
+
+
+# check whether to install GRUB EFI or not
+sub getEfiTarget {
+    if ($grubVersion == 1) {
+        return "no"
+    } elsif (($grub ne "") && ($grubEfi ne "")) {
+        # EFI can only be installed when target is set;
+        # A target is also required then for non-EFI grub
+        if (($grubTarget eq "") || ($grubTargetEfi eq "")) { die }
+        else { return "both" }
+    } elsif (($grub ne "") && ($grubEfi eq "")) {
+        # TODO: It would be safer to disallow non-EFI grub installation if no taget is given.
+        #       If no target is given, then grub auto-detects the target which can lead to errors.
+        #       E.g. it seems as if grub would auto-detect a EFI target based on the availability
+        #       of a EFI partition.
+        #       However, it seems as auto-detection is currently relied on for non-x86_64 and non-i386
+        #       architectures in NixOS. That would have to be fixed in the nixos modules first.
+        return "no"
+    } elsif (($grub eq "") && ($grubEfi ne "")) {
+        # EFI can only be installed when target is set;
+        if ($grubTargetEfi eq "") { die }
+        else {return "only" }
+    } else {
+        # prevent an installation if neither grub nor grubEfi is given
+        return "neither"
+    }
+}
+
+my $efiTarget = getEfiTarget();
+
+# Append entries detected by os-prober
+if (get("useOSProber") eq "true") {
+    my $targetpackage = ($efiTarget eq "no") ? $grub : $grubEfi;
+    system(get("shell"), "-c", "pkgdatadir=$targetpackage/share/grub $targetpackage/etc/grub.d/30_os-prober >> $tmpFile");
+}
+
+# Atomically switch to the new config
+rename $tmpFile, $confFile or die "cannot rename $tmpFile to $confFile\n";
+
+
+# Remove obsolete files from $bootPath/kernels.
+foreach my $fn (glob "$bootPath/kernels/*") {
+    next if defined $copied{$fn};
+    print STDERR "removing obsolete file $fn\n";
+    unlink $fn;
+}
+
+
+#
+# Install GRUB if the parameters changed from the last time we installed it.
+#
+
+struct(GrubState => {
+    name => '$',
+    version => '$',
+    efi => '$',
+    devices => '$',
+    efiMountPoint => '$',
+});
+sub readGrubState {
+    my $defaultGrubState = GrubState->new(name => "", version => "", efi => "", devices => "", efiMountPoint => "" );
+    open FILE, "<$bootPath/grub/state" or return $defaultGrubState;
+    local $/ = "\n";
+    my $name = <FILE>;
+    chomp($name);
+    my $version = <FILE>;
+    chomp($version);
+    my $efi = <FILE>;
+    chomp($efi);
+    my $devices = <FILE>;
+    chomp($devices);
+    my $efiMountPoint = <FILE>;
+    chomp($efiMountPoint);
+    close FILE;
+    my $grubState = GrubState->new(name => $name, version => $version, efi => $efi, devices => $devices, efiMountPoint => $efiMountPoint );
+    return $grubState
+}
+
+sub getDeviceTargets {
+    my @devices = ();
+    foreach my $dev ($dom->findnodes('/expr/attrs/attr[@name = "devices"]/list/string/@value')) {
+        $dev = $dev->findvalue(".") or die;
+        push(@devices, $dev);
+    }
+    return @devices;
+}
+my @deviceTargets = getDeviceTargets();
+my $prevGrubState = readGrubState();
+my @prevDeviceTargets = split/,/, $prevGrubState->devices;
+
+my $devicesDiffer = scalar (List::Compare->new( '-u', '-a', \@deviceTargets, \@prevDeviceTargets)->get_symmetric_difference());
+my $nameDiffer = get("fullName") ne $prevGrubState->name;
+my $versionDiffer = get("fullVersion") ne $prevGrubState->version;
+my $efiDiffer = $efiTarget ne $prevGrubState->efi;
+my $efiMountPointDiffer = $efiSysMountPoint ne $prevGrubState->efiMountPoint;
+if (($ENV{'NIXOS_INSTALL_GRUB'} // "") eq "1") {
+    warn "NIXOS_INSTALL_GRUB env var deprecated, use NIXOS_INSTALL_BOOTLOADER";
+    $ENV{'NIXOS_INSTALL_BOOTLOADER'} = "1";
+}
+my $requireNewInstall = $devicesDiffer || $nameDiffer || $versionDiffer || $efiDiffer || $efiMountPointDiffer || (($ENV{'NIXOS_INSTALL_BOOTLOADER'} // "") eq "1");
+
+# install a symlink so that grub can detect the boot drive
+my $tmpDir = File::Temp::tempdir(CLEANUP => 1) or die "Failed to create temporary space";
+symlink "$bootPath", "$tmpDir/boot" or die "Failed to symlink $tmpDir/boot";
+
+# install non-EFI GRUB
+if (($requireNewInstall != 0) && ($efiTarget eq "no" || $efiTarget eq "both")) {
+    foreach my $dev (@deviceTargets) {
+        next if $dev eq "nodev";
+        print STDERR "installing the GRUB $grubVersion boot loader on $dev...\n";
+        my @command = ("$grub/sbin/grub-install", "--recheck", "--root-directory=$tmpDir", Cwd::abs_path($dev));
+        if ($forceInstall eq "true") {
+            push @command, "--force";
+        }
+        if ($grubTarget ne "") {
+            push @command, "--target=$grubTarget";
+        }
+        (system @command) == 0 or die "$0: installation of GRUB on $dev failed\n";
+    }
+}
+
+
+# install EFI GRUB
+if (($requireNewInstall != 0) && ($efiTarget eq "only" || $efiTarget eq "both")) {
+    print STDERR "installing the GRUB $grubVersion EFI boot loader into $efiSysMountPoint...\n";
+    my @command = ("$grubEfi/sbin/grub-install", "--recheck", "--target=$grubTargetEfi", "--boot-directory=$bootPath", "--efi-directory=$efiSysMountPoint");
+    if ($forceInstall eq "true") {
+        push @command, "--force";
+    }
+    if ($canTouchEfiVariables eq "true") {
+        push @command, "--bootloader-id=$bootloaderId";
+    } else {
+        push @command, "--no-nvram";
+        push @command, "--removable" if $efiInstallAsRemovable eq "true";
+    }
+
+    (system @command) == 0 or die "$0: installation of GRUB EFI into $efiSysMountPoint failed\n";
+}
+
+
+# update GRUB state file
+if ($requireNewInstall != 0) {
+    open FILE, ">$bootPath/grub/state" or die "cannot create $bootPath/grub/state: $!\n";
+    print FILE get("fullName"), "\n" or die;
+    print FILE get("fullVersion"), "\n" or die;
+    print FILE $efiTarget, "\n" or die;
+    print FILE join( ",", @deviceTargets ), "\n" or die;
+    print FILE $efiSysMountPoint, "\n" or die;
+    close FILE or die;
+}
diff --git a/nixpkgs/nixos/modules/system/boot/loader/grub/ipxe.nix b/nixpkgs/nixos/modules/system/boot/loader/grub/ipxe.nix
new file mode 100644
index 000000000000..249c2761934d
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/grub/ipxe.nix
@@ -0,0 +1,64 @@
+# This module adds a scripted iPXE entry to the GRUB boot menu.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  scripts = builtins.attrNames config.boot.loader.grub.ipxe;
+
+  grubEntry = name:
+    ''
+      menuentry "iPXE - ${name}" {
+        linux16 @bootRoot@/ipxe.lkrn
+        initrd16 @bootRoot@/${name}.ipxe
+      }
+
+    '';
+
+  scriptFile = name:
+    let
+      value = builtins.getAttr name config.boot.loader.grub.ipxe;
+    in
+    if builtins.typeOf value == "path" then value
+    else builtins.toFile "${name}.ipxe" value;
+in
+{
+  options =
+    { boot.loader.grub.ipxe = mkOption {
+        type = types.attrsOf (types.either types.path types.str);
+        description =
+          ''
+            Set of iPXE scripts available for
+            booting from the GRUB boot menu.
+          '';
+        default = { };
+        example = literalExample ''
+          { demo = '''
+              #!ipxe
+              dhcp
+              chain http://boot.ipxe.org/demo/boot.php
+            ''';
+          }
+        '';
+      };
+    };
+
+  config = mkIf (builtins.length scripts != 0) {
+
+    boot.loader.grub.extraEntries =
+      if config.boot.loader.grub.version == 2 then
+        toString (map grubEntry scripts)
+      else
+        throw "iPXE is not supported with GRUB 1.";
+
+    boot.loader.grub.extraFiles =
+      { "ipxe.lkrn" = "${pkgs.ipxe}/ipxe.lkrn"; }
+      //
+      builtins.listToAttrs ( map
+        (name: { name = name+".ipxe"; value = scriptFile name; })
+        scripts
+      );
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/system/boot/loader/grub/memtest.nix b/nixpkgs/nixos/modules/system/boot/loader/grub/memtest.nix
new file mode 100644
index 000000000000..94e5a14174b0
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/grub/memtest.nix
@@ -0,0 +1,93 @@
+# This module adds Memtest86+ to the GRUB boot menu.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  memtest86 = pkgs.memtest86plus;
+  cfg = config.boot.loader.grub.memtest86;
+in
+
+{
+  options = {
+
+    boot.loader.grub.memtest86 = {
+
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Make Memtest86+, a memory testing program, available from the
+          GRUB boot menu.
+        '';
+      };
+
+      params = mkOption {
+        default = [];
+        example = [ "console=ttyS0,115200" ];
+        type = types.listOf types.str;
+        description = ''
+          Parameters added to the Memtest86+ command line. As of memtest86+ 5.01
+          the following list of (apparently undocumented) parameters are
+          accepted:
+
+          <itemizedlist>
+
+          <listitem>
+            <para><literal>console=...</literal>, set up a serial console.
+            Examples:
+            <literal>console=ttyS0</literal>,
+            <literal>console=ttyS0,9600</literal> or
+            <literal>console=ttyS0,115200n8</literal>.</para>
+          </listitem>
+
+          <listitem>
+            <para><literal>btrace</literal>, enable boot trace.</para>
+          </listitem>
+
+          <listitem>
+            <para><literal>maxcpus=N</literal>, limit number of CPUs.</para>
+          </listitem>
+
+          <listitem>
+            <para><literal>onepass</literal>, run one pass and exit if there
+            are no errors.</para>
+          </listitem>
+
+          <listitem>
+            <para><literal>tstlist=...</literal>, list of tests to run.
+            Example: <literal>0,1,2</literal>.</para>
+          </listitem>
+
+          <listitem>
+            <para><literal>cpumask=...</literal>, set a CPU mask, to select CPUs
+            to use for testing.</para>
+          </listitem>
+
+          </itemizedlist>
+
+          This list of command line options was obtained by reading the
+          Memtest86+ source code.
+        '';
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    boot.loader.grub.extraEntries =
+      if config.boot.loader.grub.version == 2 then
+        ''
+          menuentry "Memtest86+" {
+            linux16 @bootRoot@/memtest.bin ${toString cfg.params}
+          }
+        ''
+      else
+        throw "Memtest86+ is not supported with GRUB 1.";
+
+    boot.loader.grub.extraFiles."memtest.bin" = "${memtest86}/memtest.bin";
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/system/boot/loader/init-script/init-script-builder.sh b/nixpkgs/nixos/modules/system/boot/loader/init-script/init-script-builder.sh
new file mode 100644
index 000000000000..08d4ab14c9ca
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/init-script/init-script-builder.sh
@@ -0,0 +1,93 @@
+#! @bash@/bin/sh -e
+
+shopt -s nullglob
+
+export PATH=/empty
+for i in @path@; do PATH=$PATH:$i/bin; done
+
+if test $# -ne 1; then
+    echo "Usage: init-script-builder.sh DEFAULT-CONFIG"
+    exit 1
+fi
+
+defaultConfig="$1"
+
+
+[ "$(stat -f -c '%i' /)" = "$(stat -f -c '%i' /boot)" ] || {
+  # see grub-menu-builder.sh
+  echo "WARNING: /boot being on a different filesystem not supported by init-script-builder.sh"
+}
+
+
+
+target="/sbin/init"
+targetOther="/boot/init-other-configurations-contents.txt"
+
+tmp="$target.tmp"
+tmpOther="$targetOther.tmp"
+
+
+configurationCounter=0
+numAlienEntries=`cat <<EOF | egrep '^[[:space:]]*title' | wc -l
+@extraEntries@
+EOF`
+
+
+
+
+# Add an entry to $targetOther
+addEntry() {
+    local name="$1"
+    local path="$2"
+    local shortSuffix="$3"
+
+    configurationCounter=$((configurationCounter + 1))
+
+    local stage2=$path/init
+
+    content="$(
+      echo "#!/bin/sh"
+      echo "# $name"
+      echo "# created by init-script-builder.sh"
+      echo "export systemConfig=$(readlink -f $path)"
+      echo "exec $stage2"
+    )"
+
+    [ "$path" != "$defaultConfig" ] || { 
+      echo "$content" > $tmp
+      echo "# older configurations: $targetOther" >> $tmp
+      chmod +x $tmp
+    }
+
+    echo -e "$content\n\n" >> $tmpOther
+}
+
+
+mkdir -p /boot /sbin
+
+addEntry "NixOS - Default" $defaultConfig ""
+
+# Add all generations of the system profile to the menu, in reverse
+# (most recent to least recent) order.
+for link in $((ls -d $defaultConfig/fine-tune/* ) | sort -n); do
+    date=$(stat --printf="%y\n" $link | sed 's/\..*//')
+    addEntry "NixOS - variation" $link ""
+done
+
+for generation in $(
+    (cd /nix/var/nix/profiles && ls -d system-*-link) \
+    | sed 's/system-\([0-9]\+\)-link/\1/' \
+    | sort -n -r); do
+    link=/nix/var/nix/profiles/system-$generation-link
+    date=$(stat --printf="%y\n" $link | sed 's/\..*//')
+    if [ -d $link/kernel ]; then
+      kernelVersion=$(cd $(dirname $(readlink -f $link/kernel))/lib/modules && echo *)
+      suffix="($date - $kernelVersion)"
+    else
+      suffix="($date)"
+    fi
+    addEntry "NixOS - Configuration $generation $suffix" $link "$generation ($date)"
+done
+
+mv $tmpOther $targetOther
+mv $tmp $target
diff --git a/nixpkgs/nixos/modules/system/boot/loader/init-script/init-script.nix b/nixpkgs/nixos/modules/system/boot/loader/init-script/init-script.nix
new file mode 100644
index 000000000000..374d9524ff1e
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/init-script/init-script.nix
@@ -0,0 +1,51 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  initScriptBuilder = pkgs.substituteAll {
+    src = ./init-script-builder.sh;
+    isExecutable = true;
+    inherit (pkgs) bash;
+    path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
+  };
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    boot.loader.initScript = {
+
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Some systems require a /sbin/init script which is started.
+          Or having it makes starting NixOS easier.
+          This applies to some kind of hosting services and user mode linux.
+
+          Additionally this script will create
+          /boot/init-other-configurations-contents.txt containing
+          contents of remaining configurations. You can copy paste them into
+          /sbin/init manually running a rescue system or such.
+        '';
+      };
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.boot.loader.initScript.enable {
+
+    system.build.installBootLoader = initScriptBuilder;
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/system/boot/loader/loader.nix b/nixpkgs/nixos/modules/system/boot/loader/loader.nix
new file mode 100644
index 000000000000..7fbda9ef0f57
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/loader.nix
@@ -0,0 +1,15 @@
+{ lib, ... }:
+
+with lib;
+
+{
+    options = {
+        boot.loader.timeout =  mkOption {
+            default = 5;
+            type = types.nullOr types.int;
+            description = ''
+              Timeout (in seconds) until loader boots the default menu item. Use null if the loader menu should be displayed indefinitely.
+            '';
+        };
+    };
+}
\ No newline at end of file
diff --git a/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.nix b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.nix
new file mode 100644
index 000000000000..7eb52e3d021f
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.nix
@@ -0,0 +1,10 @@
+{ pkgs, configTxt }:
+
+pkgs.substituteAll {
+  src = ./raspberrypi-builder.sh;
+  isExecutable = true;
+  inherit (pkgs) bash;
+  path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
+  firmware = pkgs.raspberrypifw;
+  inherit configTxt;
+}
diff --git a/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.sh b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.sh
new file mode 100644
index 000000000000..0fb07de10c04
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.sh
@@ -0,0 +1,132 @@
+#! @bash@/bin/sh -e
+
+shopt -s nullglob
+
+export PATH=/empty
+for i in @path@; do PATH=$PATH:$i/bin; done
+
+usage() {
+    echo "usage: $0 -c <path-to-default-configuration> [-d <boot-dir>]" >&2
+    exit 1
+}
+
+default=                # Default configuration
+target=/boot            # Target directory
+
+while getopts "c:d:" opt; do
+    case "$opt" in
+        c) default="$OPTARG" ;;
+        d) target="$OPTARG" ;;
+        \?) usage ;;
+    esac
+done
+
+echo "updating the boot generations directory..."
+
+mkdir -p $target/old
+
+# Convert a path to a file in the Nix store such as
+# /nix/store/<hash>-<name>/file to <hash>-<name>-<file>.
+cleanName() {
+    local path="$1"
+    echo "$path" | sed 's|^/nix/store/||' | sed 's|/|-|g'
+}
+
+# Copy a file from the Nix store to $target/kernels.
+declare -A filesCopied
+
+copyToKernelsDir() {
+    local src="$1"
+    local dst="$target/old/$(cleanName $src)"
+    # Don't copy the file if $dst already exists.  This means that we
+    # have to create $dst atomically to prevent partially copied
+    # kernels or initrd if this script is ever interrupted.
+    if ! test -e $dst; then
+        local dstTmp=$dst.tmp.$$
+        cp $src $dstTmp
+        mv $dstTmp $dst
+    fi
+    filesCopied[$dst]=1
+    result=$dst
+}
+
+copyForced() {
+    local src="$1"
+    local dst="$2"
+    cp $src $dst.tmp
+    mv $dst.tmp $dst
+}
+
+outdir=$target/old
+mkdir -p $outdir || true
+
+# Copy its kernel and initrd to $target/old.
+addEntry() {
+    local path="$1"
+    local generation="$2"
+
+    if ! test -e $path/kernel -a -e $path/initrd; then
+        return
+    fi
+
+    local kernel=$(readlink -f $path/kernel)
+    local initrd=$(readlink -f $path/initrd)
+    local dtb_path=$(readlink -f $path/kernel-modules/dtbs)
+
+    if test -n "@copyKernels@"; then
+        copyToKernelsDir $kernel; kernel=$result
+        copyToKernelsDir $initrd; initrd=$result
+    fi
+
+    echo $(readlink -f $path) > $outdir/$generation-system
+    echo $(readlink -f $path/init) > $outdir/$generation-init
+    cp $path/kernel-params $outdir/$generation-cmdline.txt
+    echo $initrd > $outdir/$generation-initrd
+    echo $kernel > $outdir/$generation-kernel
+
+    if test "$generation" = "default"; then
+      copyForced $kernel $target/kernel.img
+      copyForced $initrd $target/initrd
+      for dtb in $dtb_path/{broadcom,}/bcm*.dtb; do
+        dst="$target/$(basename $dtb)"
+        copyForced $dtb "$dst"
+        filesCopied[$dst]=1
+      done
+      cp "$(readlink -f "$path/init")" $target/nixos-init
+      echo "`cat $path/kernel-params` init=$path/init" >$target/cmdline.txt
+    fi
+}
+
+addEntry $default default
+
+# Add all generations of the system profile to the menu, in reverse
+# (most recent to least recent) order.
+for generation in $(
+    (cd /nix/var/nix/profiles && ls -d system-*-link) \
+    | sed 's/system-\([0-9]\+\)-link/\1/' \
+    | sort -n -r); do
+    link=/nix/var/nix/profiles/system-$generation-link
+    addEntry $link $generation
+done
+
+# Add the firmware files
+fwdir=@firmware@/share/raspberrypi/boot/
+copyForced $fwdir/bootcode.bin  $target/bootcode.bin
+copyForced $fwdir/fixup.dat     $target/fixup.dat
+copyForced $fwdir/fixup_cd.dat  $target/fixup_cd.dat
+copyForced $fwdir/fixup_db.dat  $target/fixup_db.dat
+copyForced $fwdir/fixup_x.dat   $target/fixup_x.dat
+copyForced $fwdir/start.elf     $target/start.elf
+copyForced $fwdir/start_cd.elf  $target/start_cd.elf
+copyForced $fwdir/start_db.elf  $target/start_db.elf
+copyForced $fwdir/start_x.elf   $target/start_x.elf
+
+# Add the config.txt
+copyForced @configTxt@ $target/config.txt
+
+# Remove obsolete files from $target and $target/old.
+for fn in $target/old/*linux* $target/old/*initrd-initrd* $target/bcm*.dtb; do
+    if ! test "${filesCopied[$fn]}" = 1; then
+        rm -vf -- "$fn"
+    fi
+done
diff --git a/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix
new file mode 100644
index 000000000000..7e089507ff20
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix
@@ -0,0 +1,108 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.boot.loader.raspberryPi;
+
+  inherit (pkgs.stdenv.hostPlatform) platform;
+
+  builderUboot = import ./uboot-builder.nix { inherit pkgs configTxt; inherit (cfg) version; };
+  builderGeneric = import ./raspberrypi-builder.nix { inherit pkgs configTxt; };
+
+  builder = 
+    if cfg.uboot.enable then
+      "${builderUboot} -g ${toString cfg.uboot.configurationLimit} -t ${timeoutStr} -c"
+    else
+      "${builderGeneric} -c";
+
+  blCfg = config.boot.loader;
+  timeoutStr = if blCfg.timeout == null then "-1" else toString blCfg.timeout;
+
+  isAarch64 = pkgs.stdenv.isAarch64;
+  optional = pkgs.stdenv.lib.optionalString;
+
+  configTxt =
+    pkgs.writeText "config.txt" (''
+      # U-Boot used to need this to work, regardless of whether UART is actually used or not.
+      # TODO: check when/if this can be removed.
+      enable_uart=1
+
+      # Prevent the firmware from smashing the framebuffer setup done by the mainline kernel
+      # when attempting to show low-voltage or overtemperature warnings.
+      avoid_warnings=1
+    '' + optional isAarch64 ''
+      # Boot in 64-bit mode.
+      arm_control=0x200
+    '' + (if cfg.uboot.enable then ''
+      kernel=u-boot-rpi.bin
+    '' else ''
+      kernel=kernel.img
+      initramfs initrd followkernel
+    '') + optional (cfg.firmwareConfig != null) cfg.firmwareConfig);
+
+in
+
+{
+  options = {
+
+    boot.loader.raspberryPi = {
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Whether to create files with the system generations in
+          <literal>/boot</literal>.
+          <literal>/boot/old</literal> will hold files from old generations.
+        '';
+      };
+
+      version = mkOption {
+        default = 2;
+        type = types.enum [ 0 1 2 3 ];
+        description = ''
+        '';
+      };
+
+      uboot = {
+        enable = mkOption {
+          default = false;
+          type = types.bool;
+          description = ''
+            Enable using uboot as bootmanager for the raspberry pi.
+          '';
+        };
+
+        configurationLimit = mkOption {
+          default = 20;
+          example = 10;
+          type = types.int;
+          description = ''
+            Maximum number of configurations in the boot menu.
+          '';
+        };
+
+      };
+
+      firmwareConfig = mkOption {
+        default = null;
+        type = types.nullOr types.string;
+        description = ''
+          Extra options that will be appended to <literal>/boot/config.txt</literal> file.
+          For possible values, see: https://www.raspberrypi.org/documentation/configuration/config-txt/
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = singleton {
+      assertion = !pkgs.stdenv.isAarch64 || cfg.version == 3;
+      message = "Only Raspberry Pi 3 supports aarch64.";
+    };
+
+    system.build.installBootLoader = builder;
+    system.boot.loader.id = "raspberrypi";
+    system.boot.loader.kernelFile = platform.kernelTarget;
+  };
+}
diff --git a/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.nix b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.nix
new file mode 100644
index 000000000000..e929c33c6ee3
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.nix
@@ -0,0 +1,35 @@
+{ pkgs, version, configTxt }:
+
+let
+  isAarch64 = pkgs.stdenv.isAarch64;
+
+  uboot =
+    if version == 0 then
+      pkgs.ubootRaspberryPiZero
+    else if version == 1 then
+      pkgs.ubootRaspberryPi
+    else if version == 2 then
+      pkgs.ubootRaspberryPi2
+    else
+      if isAarch64 then
+        pkgs.ubootRaspberryPi3_64bit
+      else
+        pkgs.ubootRaspberryPi3_32bit;
+
+  extlinuxConfBuilder =
+    import ../generic-extlinux-compatible/extlinux-conf-builder.nix {
+      inherit pkgs;
+    };
+in
+pkgs.substituteAll {
+  src = ./uboot-builder.sh;
+  isExecutable = true;
+  inherit (pkgs) bash;
+  path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
+  firmware = pkgs.raspberrypifw;
+  inherit uboot;
+  inherit configTxt;
+  inherit extlinuxConfBuilder;
+  inherit version;
+}
+
diff --git a/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.sh b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.sh
new file mode 100644
index 000000000000..ea591427179f
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.sh
@@ -0,0 +1,38 @@
+#! @bash@/bin/sh -e
+
+target=/boot # Target directory
+
+while getopts "t:c:d:g:" opt; do
+    case "$opt" in
+        d) target="$OPTARG" ;;
+        *) ;;
+    esac
+done
+
+copyForced() {
+    local src="$1"
+    local dst="$2"
+    cp $src $dst.tmp
+    mv $dst.tmp $dst
+}
+
+# Call the extlinux builder
+"@extlinuxConfBuilder@" "$@"
+
+# Add the firmware files
+fwdir=@firmware@/share/raspberrypi/boot/
+copyForced $fwdir/bootcode.bin  $target/bootcode.bin
+copyForced $fwdir/fixup.dat     $target/fixup.dat
+copyForced $fwdir/fixup_cd.dat  $target/fixup_cd.dat
+copyForced $fwdir/fixup_db.dat  $target/fixup_db.dat
+copyForced $fwdir/fixup_x.dat   $target/fixup_x.dat
+copyForced $fwdir/start.elf     $target/start.elf
+copyForced $fwdir/start_cd.elf  $target/start_cd.elf
+copyForced $fwdir/start_db.elf  $target/start_db.elf
+copyForced $fwdir/start_x.elf   $target/start_x.elf
+
+# Add the uboot file
+copyForced @uboot@/u-boot.bin $target/u-boot-rpi.bin
+
+# Add the config.txt
+copyForced @configTxt@ $target/config.txt
diff --git a/nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
new file mode 100644
index 000000000000..6016a85ea061
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
@@ -0,0 +1,211 @@
+#! @python3@/bin/python3 -B
+import argparse
+import shutil
+import os
+import sys
+import errno
+import subprocess
+import glob
+import tempfile
+import errno
+import warnings
+import ctypes
+libc = ctypes.CDLL("libc.so.6")
+import re
+import datetime
+import glob
+import os.path
+
+def copy_if_not_exists(source, dest):
+    if not os.path.exists(dest):
+        shutil.copyfile(source, dest)
+
+def system_dir(profile, generation):
+    if profile:
+        return "/nix/var/nix/profiles/system-profiles/%s-%d-link" % (profile, generation)
+    else:
+        return "/nix/var/nix/profiles/system-%d-link" % (generation)
+
+BOOT_ENTRY = """title NixOS{profile}
+version Generation {generation} {description}
+linux {kernel}
+initrd {initrd}
+options {kernel_params}
+"""
+
+def write_loader_conf(profile, generation):
+    with open("@efiSysMountPoint@/loader/loader.conf.tmp", 'w') as f:
+        if "@timeout@" != "":
+            f.write("timeout @timeout@\n")
+        if profile:
+            f.write("default nixos-%s-generation-%d\n" % (profile, generation))
+        else:
+            f.write("default nixos-generation-%d\n" % (generation))
+        if not @editor@:
+            f.write("editor 0\n");
+        f.write("console-mode @consoleMode@\n");
+    os.rename("@efiSysMountPoint@/loader/loader.conf.tmp", "@efiSysMountPoint@/loader/loader.conf")
+
+def profile_path(profile, generation, name):
+    return os.readlink("%s/%s" % (system_dir(profile, generation), name))
+
+def copy_from_profile(profile, generation, name, dry_run=False):
+    store_file_path = profile_path(profile, generation, name)
+    suffix = os.path.basename(store_file_path)
+    store_dir = os.path.basename(os.path.dirname(store_file_path))
+    efi_file_path = "/efi/nixos/%s-%s.efi" % (store_dir, suffix)
+    if not dry_run:
+        copy_if_not_exists(store_file_path, "@efiSysMountPoint@%s" % (efi_file_path))
+    return efi_file_path
+
+def describe_generation(generation_dir):
+    try:
+        with open("%s/nixos-version" % generation_dir) as f:
+            nixos_version = f.read()
+    except IOError:
+        nixos_version = "Unknown"
+
+    kernel_dir = os.path.dirname(os.path.realpath("%s/kernel" % generation_dir))
+    module_dir = glob.glob("%s/lib/modules/*" % kernel_dir)[0]
+    kernel_version = os.path.basename(module_dir)
+
+    build_time = int(os.path.getctime(generation_dir))
+    build_date = datetime.datetime.fromtimestamp(build_time).strftime('%F')
+
+    description = "NixOS {}, Linux Kernel {}, Built on {}".format(
+        nixos_version, kernel_version, build_date
+    )
+
+    return description
+
+def write_entry(profile, generation, machine_id):
+    kernel = copy_from_profile(profile, generation, "kernel")
+    initrd = copy_from_profile(profile, generation, "initrd")
+    try:
+        append_initrd_secrets = profile_path(profile, generation, "append-initrd-secrets")
+        subprocess.check_call([append_initrd_secrets, "@efiSysMountPoint@%s" % (initrd)])
+    except FileNotFoundError:
+        pass
+    if profile:
+        entry_file = "@efiSysMountPoint@/loader/entries/nixos-%s-generation-%d.conf" % (profile, generation)
+    else:
+        entry_file = "@efiSysMountPoint@/loader/entries/nixos-generation-%d.conf" % (generation)
+    generation_dir = os.readlink(system_dir(profile, generation))
+    tmp_path = "%s.tmp" % (entry_file)
+    kernel_params = "systemConfig=%s init=%s/init " % (generation_dir, generation_dir)
+
+    with open("%s/kernel-params" % (generation_dir)) as params_file:
+        kernel_params = kernel_params + params_file.read()
+    with open(tmp_path, 'w') as f:
+        f.write(BOOT_ENTRY.format(profile=" [" + profile + "]" if profile else "",
+                    generation=generation,
+                    kernel=kernel,
+                    initrd=initrd,
+                    kernel_params=kernel_params,
+                    description=describe_generation(generation_dir)))
+        if machine_id is not None:
+            f.write("machine-id %s\n" % machine_id)
+    os.rename(tmp_path, entry_file)
+
+def mkdir_p(path):
+    try:
+        os.makedirs(path)
+    except OSError as e:
+        if e.errno != errno.EEXIST or not os.path.isdir(path):
+            raise
+
+def get_generations(profile=None):
+    gen_list = subprocess.check_output([
+        "@nix@/bin/nix-env",
+        "--list-generations",
+        "-p",
+        "/nix/var/nix/profiles/%s" % ("system-profiles/" + profile if profile else "system"),
+        "--option", "build-users-group", ""],
+        universal_newlines=True)
+    gen_lines = gen_list.split('\n')
+    gen_lines.pop()
+    return [ (profile, int(line.split()[0])) for line in gen_lines ]
+
+def remove_old_entries(gens):
+    rex_profile = re.compile("^@efiSysMountPoint@/loader/entries/nixos-(.*)-generation-.*\.conf$")
+    rex_generation = re.compile("^@efiSysMountPoint@/loader/entries/nixos.*-generation-(.*)\.conf$")
+    known_paths = []
+    for gen in gens:
+        known_paths.append(copy_from_profile(*gen, "kernel", True))
+        known_paths.append(copy_from_profile(*gen, "initrd", True))
+    for path in glob.iglob("@efiSysMountPoint@/loader/entries/nixos*-generation-[1-9]*.conf"):
+        try:
+            if rex_profile.match(path):
+                prof = rex_profile.sub(r"\1", path)
+            else:
+                prof = "system"
+            gen = int(rex_generation.sub(r"\1", path))
+            if not (prof, gen) in gens:
+                os.unlink(path)
+        except ValueError:
+            pass
+    for path in glob.iglob("@efiSysMountPoint@/efi/nixos/*"):
+        if not path in known_paths:
+            os.unlink(path)
+
+def get_profiles():
+    if os.path.isdir("/nix/var/nix/profiles/system-profiles/"):
+        return [x
+            for x in os.listdir("/nix/var/nix/profiles/system-profiles/")
+            if not x.endswith("-link")]
+    else:
+        return []
+
+def main():
+    parser = argparse.ArgumentParser(description='Update NixOS-related systemd-boot files')
+    parser.add_argument('default_config', metavar='DEFAULT-CONFIG', help='The default NixOS config to boot')
+    args = parser.parse_args()
+
+    try:
+        with open("/etc/machine-id") as machine_file:
+            machine_id = machine_file.readlines()[0]
+    except IOError as e:
+        if e.errno != errno.ENOENT:
+            raise
+        # Since systemd version 232 a machine ID is required and it might not
+        # be there on newly installed systems, so let's generate one so that
+        # bootctl can find it and we can also pass it to write_entry() later.
+        cmd = ["@systemd@/bin/systemd-machine-id-setup", "--print"]
+        machine_id = subprocess.check_output(cmd).rstrip()
+
+    if os.getenv("NIXOS_INSTALL_GRUB") == "1":
+        warnings.warn("NIXOS_INSTALL_GRUB env var deprecated, use NIXOS_INSTALL_BOOTLOADER", DeprecationWarning)
+        os.environ["NIXOS_INSTALL_BOOTLOADER"] = "1"
+
+    if os.getenv("NIXOS_INSTALL_BOOTLOADER") == "1":
+        # bootctl uses fopen() with modes "wxe" and fails if the file exists.
+        if os.path.exists("@efiSysMountPoint@/loader/loader.conf"):
+            os.unlink("@efiSysMountPoint@/loader/loader.conf")
+
+        if "@canTouchEfiVariables@" == "1":
+            subprocess.check_call(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@", "install"])
+        else:
+            subprocess.check_call(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@", "--no-variables", "install"])
+
+    mkdir_p("@efiSysMountPoint@/efi/nixos")
+    mkdir_p("@efiSysMountPoint@/loader/entries")
+
+    gens = get_generations()
+    for profile in get_profiles():
+        gens += get_generations(profile)
+    remove_old_entries(gens)
+    for gen in gens:
+        write_entry(*gen, machine_id)
+        if os.readlink(system_dir(*gen)) == args.default_config:
+            write_loader_conf(*gen)
+
+    # Since fat32 provides little recovery facilities after a crash,
+    # it can leave the system in an unbootable state, when a crash/outage
+    # happens shortly after an update. To decrease the likelihood of this
+    # event sync the efi filesystem after each update.
+    rc = libc.syncfs(os.open("@efiSysMountPoint@", os.O_RDONLY))
+    if rc != 0:
+        print("could not sync @efiSysMountPoint@: {}".format(os.strerror(rc)), file=sys.stderr)
+
+if __name__ == '__main__':
+    main()
diff --git a/nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix b/nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
new file mode 100644
index 000000000000..feed863efd66
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
@@ -0,0 +1,114 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.boot.loader.systemd-boot;
+
+  efi = config.boot.loader.efi;
+
+  gummibootBuilder = pkgs.substituteAll {
+    src = ./systemd-boot-builder.py;
+
+    isExecutable = true;
+
+    inherit (pkgs) python3;
+
+    systemd = config.systemd.package;
+
+    nix = config.nix.package.out;
+
+    timeout = if config.boot.loader.timeout != null then config.boot.loader.timeout else "";
+
+    editor = if cfg.editor then "True" else "False";
+
+    inherit (cfg) consoleMode;
+
+    inherit (efi) efiSysMountPoint canTouchEfiVariables;
+  };
+in {
+
+  imports =
+    [ (mkRenamedOptionModule [ "boot" "loader" "gummiboot" "enable" ] [ "boot" "loader" "systemd-boot" "enable" ])
+    ];
+
+  options.boot.loader.systemd-boot = {
+    enable = mkOption {
+      default = false;
+
+      type = types.bool;
+
+      description = "Whether to enable the systemd-boot (formerly gummiboot) EFI boot manager";
+    };
+
+    editor = mkOption {
+      default = true;
+
+      type = types.bool;
+
+      description = ''
+        Whether to allow editing the kernel command-line before
+        boot. It is recommended to set this to false, as it allows
+        gaining root access by passing init=/bin/sh as a kernel
+        parameter. However, it is enabled by default for backwards
+        compatibility.
+      '';
+    };
+
+    consoleMode = mkOption {
+      default = "keep";
+
+      type = types.enum [ "0" "1" "2" "auto" "max" "keep" ];
+
+      description = ''
+        The resolution of the console. The following values are valid:
+        </para>
+        <para>
+        <itemizedlist>
+          <listitem><para>
+            <literal>"0"</literal>: Standard UEFI 80x25 mode
+          </para></listitem>
+          <listitem><para>
+            <literal>"1"</literal>: 80x50 mode, not supported by all devices
+          </para></listitem>
+          <listitem><para>
+            <literal>"2"</literal>: The first non-standard mode provided by the device firmware, if any
+          </para></listitem>
+          <listitem><para>
+            <literal>"auto"</literal>: Pick a suitable mode automatically using heuristics
+          </para></listitem>
+          <listitem><para>
+            <literal>"max"</literal>: Pick the highest-numbered available mode
+          </para></listitem>
+          <listitem><para>
+            <literal>"keep"</literal>: Keep the mode selected by firmware (the default)
+          </para></listitem>
+        </itemizedlist>
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = (config.boot.kernelPackages.kernel.features or { efiBootStub = true; }) ? efiBootStub;
+
+        message = "This kernel does not support the EFI boot stub";
+      }
+    ];
+
+    boot.loader.grub.enable = mkDefault false;
+
+    boot.loader.supportsInitrdSecrets = true;
+
+    system = {
+      build.installBootLoader = gummibootBuilder;
+
+      boot.loader.id = "systemd-boot";
+
+      requiredKernelConfig = with config.lib.kernelConfig; [
+        (isYes "EFI_STUB")
+      ];
+    };
+  };
+}