diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index bef3f7f2fe7..3b6305179f0 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -412,6 +412,7 @@
./system/boot/loader/efi.nix
./system/boot/loader/loader.nix
./system/boot/loader/generations-dir/generations-dir.nix
+ ./system/boot/loader/generic-extlinux-compatible
./system/boot/loader/grub/grub.nix
./system/boot/loader/grub/ipxe.nix
./system/boot/loader/grub/memtest.nix
diff --git a/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix b/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
new file mode 100644
index 00000000000..af39c7bb684
--- /dev/null
+++ b/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 /boot/extlinux.conf. For instance,
+ U-Boot's generic distro boot support uses this file format.
+
+ See U-boot's documentation
+ 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/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix b/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix
new file mode 100644
index 00000000000..261192c6d24
--- /dev/null
+++ b/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;
+ inherit (pkgs) bash;
+ path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
+}
diff --git a/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh b/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh
new file mode 100644
index 00000000000..8f2a496de8b
--- /dev/null
+++ b/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-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 -t -c [-d ] [-g ]" >&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/-/file to --.
+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?
+ copyToKernelsDir $(readlink -m "$path/kernel/../dtbs"); dtbs=$result
+
+ timestampEpoch=$(stat -L -c '%Z' $path)
+
+ timestamp=$(date "+%Y-%m-%d %H:%M" -d @$timestampEpoch)
+ nixosVersion="$(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 - $nixosVersion)"
+ fi
+ echo " LINUX ../nixos/$(basename $kernel)"
+ echo " INITRD ../nixos/$(basename $initrd)"
+ echo " FDTDIR ../nixos/$(basename $dtbs)"
+ echo " APPEND systemConfig=$path init=$path/init $extraParams"
+}
+
+tmpFile="$target/extlinux/extlinux.conf.tmp.$$"
+
+cat > $tmpFile <> $tmpFile
+
+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