summary refs log tree commit diff
path: root/nixos/modules/system
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/system')
-rw-r--r--nixos/modules/system/activation/activation-script.nix272
-rw-r--r--nixos/modules/system/activation/no-clone.nix8
-rwxr-xr-xnixos/modules/system/activation/switch-to-configuration.pl856
-rw-r--r--nixos/modules/system/activation/top-level.nix355
-rw-r--r--nixos/modules/system/boot/binfmt.nix325
-rw-r--r--nixos/modules/system/boot/emergency-mode.nix37
-rw-r--r--nixos/modules/system/boot/grow-partition.nix53
-rw-r--r--nixos/modules/system/boot/initrd-network.nix148
-rw-r--r--nixos/modules/system/boot/initrd-openvpn.nix81
-rw-r--r--nixos/modules/system/boot/initrd-ssh.nix215
-rw-r--r--nixos/modules/system/boot/kernel.nix350
-rw-r--r--nixos/modules/system/boot/kernel_config.nix117
-rw-r--r--nixos/modules/system/boot/kexec.nix32
-rw-r--r--nixos/modules/system/boot/loader/efi.nix20
-rw-r--r--nixos/modules/system/boot/loader/generations-dir/generations-dir-builder.sh106
-rw-r--r--nixos/modules/system/boot/loader/generations-dir/generations-dir.nix62
-rw-r--r--nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix82
-rw-r--r--nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix8
-rw-r--r--nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh157
-rw-r--r--nixos/modules/system/boot/loader/grub/grub.nix848
-rw-r--r--nixos/modules/system/boot/loader/grub/install-grub.pl780
-rw-r--r--nixos/modules/system/boot/loader/grub/ipxe.nix64
-rw-r--r--nixos/modules/system/boot/loader/grub/memtest.nix116
-rw-r--r--nixos/modules/system/boot/loader/init-script/init-script-builder.sh92
-rw-r--r--nixos/modules/system/boot/loader/init-script/init-script.nix51
-rw-r--r--nixos/modules/system/boot/loader/loader.nix20
-rw-r--r--nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.nix9
-rw-r--r--nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.sh143
-rw-r--r--nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix105
-rw-r--r--nixos/modules/system/boot/loader/raspberrypi/uboot-builder.nix37
-rw-r--r--nixos/modules/system/boot/loader/raspberrypi/uboot-builder.sh38
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py316
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix303
-rw-r--r--nixos/modules/system/boot/luksroot.nix941
-rw-r--r--nixos/modules/system/boot/modprobe.nix70
-rw-r--r--nixos/modules/system/boot/networkd.nix1797
-rw-r--r--nixos/modules/system/boot/pbkdf2-sha512.c38
-rw-r--r--nixos/modules/system/boot/plymouth.nix237
-rw-r--r--nixos/modules/system/boot/resolved.nix183
-rw-r--r--nixos/modules/system/boot/shutdown.nix27
-rw-r--r--nixos/modules/system/boot/stage-1-init.sh642
-rw-r--r--nixos/modules/system/boot/stage-1.nix717
-rwxr-xr-xnixos/modules/system/boot/stage-2-init.sh176
-rw-r--r--nixos/modules/system/boot/stage-2.nix108
-rw-r--r--nixos/modules/system/boot/systemd-nspawn.nix125
-rw-r--r--nixos/modules/system/boot/systemd.nix1064
-rw-r--r--nixos/modules/system/boot/timesyncd.nix74
-rw-r--r--nixos/modules/system/boot/tmp.nix64
-rw-r--r--nixos/modules/system/build.nix21
-rw-r--r--nixos/modules/system/etc/etc-activation.nix12
-rw-r--r--nixos/modules/system/etc/etc.nix201
-rw-r--r--nixos/modules/system/etc/setup-etc.pl146
-rw-r--r--nixos/modules/system/etc/test.nix70
53 files changed, 12919 insertions, 0 deletions
diff --git a/nixos/modules/system/activation/activation-script.nix b/nixos/modules/system/activation/activation-script.nix
new file mode 100644
index 00000000000..c04d0fc16b2
--- /dev/null
+++ b/nixos/modules/system/activation/activation-script.nix
@@ -0,0 +1,272 @@
+# generate the script used to activate the configuration.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  addAttributeName = mapAttrs (a: v: v // {
+    text = ''
+      #### Activation script snippet ${a}:
+      _localstatus=0
+      ${v.text}
+
+      if (( _localstatus > 0 )); then
+        printf "Activation script snippet '%s' failed (%s)\n" "${a}" "$_localstatus"
+      fi
+    '';
+  });
+
+  systemActivationScript = set: onlyDry: let
+    set' = mapAttrs (_: v: if isString v then (noDepEntry v) // { supportsDryActivation = false; } else v) set;
+    withHeadlines = addAttributeName set';
+    # When building a dry activation script, this replaces all activation scripts
+    # that do not support dry mode with a comment that does nothing. Filtering these
+    # activation scripts out so they don't get generated into the dry activation script
+    # does not work because when an activation script that supports dry mode depends on
+    # an activation script that does not, the dependency cannot be resolved and the eval
+    # fails.
+    withDrySnippets = mapAttrs (a: v: if onlyDry && !v.supportsDryActivation then v // {
+      text = "#### Activation script snippet ${a} does not support dry activation.";
+    } else v) withHeadlines;
+  in
+    ''
+      #!${pkgs.runtimeShell}
+
+      systemConfig='@out@'
+
+      export PATH=/empty
+      for i in ${toString path}; do
+          PATH=$PATH:$i/bin:$i/sbin
+      done
+
+      _status=0
+      trap "_status=1 _localstatus=\$?" ERR
+
+      # Ensure a consistent umask.
+      umask 0022
+
+      ${textClosureMap id (withDrySnippets) (attrNames withDrySnippets)}
+
+    '' + optionalString (!onlyDry) ''
+      # Make this configuration the current configuration.
+      # The readlink is there to ensure that when $systemConfig = /system
+      # (which is a symlink to the store), /run/current-system is still
+      # used as a garbage collection root.
+      ln -sfn "$(readlink -f "$systemConfig")" /run/current-system
+
+      # Prevent the current configuration from being garbage-collected.
+      mkdir -p /nix/var/nix/gcroots
+      ln -sfn /run/current-system /nix/var/nix/gcroots/current-system
+
+      exit $_status
+    '';
+
+  path = with pkgs; map getBin
+    [ coreutils
+      gnugrep
+      findutils
+      getent
+      stdenv.cc.libc # nscd in update-users-groups.pl
+      shadow
+      nettools # needed for hostname
+      util-linux # needed for mount and mountpoint
+    ];
+
+  scriptType = withDry: with types;
+    let scriptOptions =
+      { deps = mkOption
+          { type = types.listOf types.str;
+            default = [ ];
+            description = "List of dependencies. The script will run after these.";
+          };
+        text = mkOption
+          { type = types.lines;
+            description = "The content of the script.";
+          };
+      } // optionalAttrs withDry {
+        supportsDryActivation = mkOption
+          { type = types.bool;
+            default = false;
+            description = ''
+              Whether this activation script supports being dry-activated.
+              These activation scripts will also be executed on dry-activate
+              activations with the environment variable
+              <literal>NIXOS_ACTION</literal> being set to <literal>dry-activate
+              </literal>.  it's important that these activation scripts  don't
+              modify anything about the system when the variable is set.
+            '';
+          };
+      };
+    in either str (submodule { options = scriptOptions; });
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    system.activationScripts = mkOption {
+      default = {};
+
+      example = literalExpression ''
+        { stdio.text =
+          '''
+            # Needed by some programs.
+            ln -sfn /proc/self/fd /dev/fd
+            ln -sfn /proc/self/fd/0 /dev/stdin
+            ln -sfn /proc/self/fd/1 /dev/stdout
+            ln -sfn /proc/self/fd/2 /dev/stderr
+          ''';
+        }
+      '';
+
+      description = ''
+        A set of shell script fragments that are executed when a NixOS
+        system configuration is activated.  Examples are updating
+        /etc, creating accounts, and so on.  Since these are executed
+        every time you boot the system or run
+        <command>nixos-rebuild</command>, it's important that they are
+        idempotent and fast.
+      '';
+
+      type = types.attrsOf (scriptType true);
+      apply = set: set // {
+        script = systemActivationScript set false;
+      };
+    };
+
+    system.dryActivationScript = mkOption {
+      description = "The shell script that is to be run when dry-activating a system.";
+      readOnly = true;
+      internal = true;
+      default = systemActivationScript (removeAttrs config.system.activationScripts [ "script" ]) true;
+      defaultText = literalDocBook "generated activation script";
+    };
+
+    system.userActivationScripts = mkOption {
+      default = {};
+
+      example = literalExpression ''
+        { plasmaSetup = {
+            text = '''
+              ''${pkgs.libsForQt5.kservice}/bin/kbuildsycoca5"
+            ''';
+            deps = [];
+          };
+        }
+      '';
+
+      description = ''
+        A set of shell script fragments that are executed by a systemd user
+        service when a NixOS system configuration is activated. Examples are
+        rebuilding the .desktop file cache for showing applications in the menu.
+        Since these are executed every time you run
+        <command>nixos-rebuild</command>, it's important that they are
+        idempotent and fast.
+      '';
+
+      type = with types; attrsOf (scriptType false);
+
+      apply = set: {
+        script = ''
+          unset PATH
+          for i in ${toString path}; do
+            PATH=$PATH:$i/bin:$i/sbin
+          done
+
+          _status=0
+          trap "_status=1 _localstatus=\$?" ERR
+
+          ${
+            let
+              set' = mapAttrs (n: v: if isString v then noDepEntry v else v) set;
+              withHeadlines = addAttributeName set';
+            in textClosureMap id (withHeadlines) (attrNames withHeadlines)
+          }
+
+          exit $_status
+        '';
+      };
+
+    };
+
+    environment.usrbinenv = mkOption {
+      default = "${pkgs.coreutils}/bin/env";
+      defaultText = literalExpression ''"''${pkgs.coreutils}/bin/env"'';
+      example = literalExpression ''"''${pkgs.busybox}/bin/env"'';
+      type = types.nullOr types.path;
+      visible = false;
+      description = ''
+        The env(1) executable that is linked system-wide to
+        <literal>/usr/bin/env</literal>.
+      '';
+    };
+  };
+
+
+  ###### implementation
+
+  config = {
+
+    system.activationScripts.stdio = ""; # obsolete
+
+    system.activationScripts.var =
+      ''
+        # Various log/runtime directories.
+
+        mkdir -m 1777 -p /var/tmp
+
+        # Empty, immutable home directory of many system accounts.
+        mkdir -p /var/empty
+        # Make sure it's really empty
+        ${pkgs.e2fsprogs}/bin/chattr -f -i /var/empty || true
+        find /var/empty -mindepth 1 -delete
+        chmod 0555 /var/empty
+        chown root:root /var/empty
+        ${pkgs.e2fsprogs}/bin/chattr -f +i /var/empty || true
+      '';
+
+    system.activationScripts.usrbinenv = if config.environment.usrbinenv != null
+      then ''
+        mkdir -m 0755 -p /usr/bin
+        ln -sfn ${config.environment.usrbinenv} /usr/bin/.env.tmp
+        mv /usr/bin/.env.tmp /usr/bin/env # atomically replace /usr/bin/env
+      ''
+      else ''
+        rm -f /usr/bin/env
+        rmdir --ignore-fail-on-non-empty /usr/bin /usr
+      '';
+
+    system.activationScripts.specialfs =
+      ''
+        specialMount() {
+          local device="$1"
+          local mountPoint="$2"
+          local options="$3"
+          local fsType="$4"
+
+          if mountpoint -q "$mountPoint"; then
+            local options="remount,$options"
+          else
+            mkdir -m 0755 -p "$mountPoint"
+          fi
+          mount -t "$fsType" -o "$options" "$device" "$mountPoint"
+        }
+        source ${config.system.build.earlyMountScript}
+      '';
+
+    systemd.user = {
+      services.nixos-activation = {
+        description = "Run user-specific NixOS activation";
+        script = config.system.userActivationScripts.script;
+        unitConfig.ConditionUser = "!@system";
+        serviceConfig.Type = "oneshot";
+        wantedBy = [ "default.target" ];
+      };
+    };
+  };
+
+}
diff --git a/nixos/modules/system/activation/no-clone.nix b/nixos/modules/system/activation/no-clone.nix
new file mode 100644
index 00000000000..912420347dc
--- /dev/null
+++ b/nixos/modules/system/activation/no-clone.nix
@@ -0,0 +1,8 @@
+{ lib, ... }:
+
+with lib;
+
+{
+  boot.loader.grub.device = mkOverride 0 "nodev";
+  specialisation = mkOverride 0 {};
+}
diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl
new file mode 100755
index 00000000000..459d09faa53
--- /dev/null
+++ b/nixos/modules/system/activation/switch-to-configuration.pl
@@ -0,0 +1,856 @@
+#! @perl@/bin/perl
+
+use strict;
+use warnings;
+use Config::IniFiles;
+use File::Path qw(make_path);
+use File::Basename;
+use File::Slurp qw(read_file write_file edit_file);
+use Net::DBus;
+use Sys::Syslog qw(:standard :macros);
+use Cwd 'abs_path';
+
+## no critic(CodeLayout::ProhibitParensWithBuiltins)
+
+my $out = "@out@";
+
+my $curSystemd = abs_path("/run/current-system/sw/bin");
+
+# To be robust against interruption, record what units need to be started etc.
+my $startListFile = "/run/nixos/start-list";
+my $restartListFile = "/run/nixos/restart-list";
+my $reloadListFile = "/run/nixos/reload-list";
+
+# Parse restart/reload requests by the activation script.
+# Activation scripts may write newline-separated units to the restart
+# file and switch-to-configuration will handle them. While
+# `stopIfChanged = true` is ignored, switch-to-configuration will
+# handle `restartIfChanged = false` and `reloadIfChanged = true`.
+# This is the same as specifying a restart trigger in the NixOS module.
+#
+# The reload file asks the script to reload a unit. This is the same as
+# specifying a reload trigger in the NixOS module and can be ignored if
+# the unit is restarted in this activation.
+my $restartByActivationFile = "/run/nixos/activation-restart-list";
+my $reloadByActivationFile = "/run/nixos/activation-reload-list";
+my $dryRestartByActivationFile = "/run/nixos/dry-activation-restart-list";
+my $dryReloadByActivationFile = "/run/nixos/dry-activation-reload-list";
+
+make_path("/run/nixos", { mode => oct(755) });
+
+my $action = shift(@ARGV);
+
+if ("@localeArchive@" ne "") {
+    $ENV{LOCALE_ARCHIVE} = "@localeArchive@";
+}
+
+if (!defined($action) || ($action ne "switch" && $action ne "boot" && $action ne "test" && $action ne "dry-activate")) {
+    print STDERR <<EOF;
+Usage: $0 [switch|boot|test]
+
+switch:       make the configuration the boot default and activate now
+boot:         make the configuration the boot default
+test:         activate the configuration, but don\'t make it the boot default
+dry-activate: show what would be done if this configuration were activated
+EOF
+    exit(1);
+}
+
+$ENV{NIXOS_ACTION} = $action;
+
+# This is a NixOS installation if it has /etc/NIXOS or a proper
+# /etc/os-release.
+die("This is not a NixOS installation!\n") unless
+    -f "/etc/NIXOS" || (read_file("/etc/os-release", err_mode => 'quiet') // "") =~ /ID="?nixos"?/s;
+
+openlog("nixos", "", LOG_USER);
+
+# Install or update the bootloader.
+if ($action eq "switch" || $action eq "boot") {
+    chomp(my $installBootLoader = <<'EOFBOOTLOADER');
+@installBootLoader@
+EOFBOOTLOADER
+    system("$installBootLoader $out") == 0 or exit 1;
+}
+
+# Just in case the new configuration hangs the system, do a sync now.
+system("@coreutils@/bin/sync", "-f", "/nix/store") unless ($ENV{"NIXOS_NO_SYNC"} // "") eq "1";
+
+exit(0) if $action eq "boot";
+
+# Check if we can activate the new configuration.
+my $oldVersion = read_file("/run/current-system/init-interface-version", err_mode => 'quiet') // "";
+my $newVersion = read_file("$out/init-interface-version");
+
+if ($newVersion ne $oldVersion) {
+    print STDERR <<EOF;
+Warning: the new NixOS configuration has an ‘init’ that is
+incompatible with the current configuration.  The new configuration
+won\'t take effect until you reboot the system.
+EOF
+    exit(100);
+}
+
+# Ignore SIGHUP so that we're not killed if we're running on (say)
+# virtual console 1 and we restart the "tty1" unit.
+$SIG{PIPE} = "IGNORE";
+
+sub getActiveUnits {
+    my $mgr = Net::DBus->system->get_service("org.freedesktop.systemd1")->get_object("/org/freedesktop/systemd1");
+    my $units = $mgr->ListUnitsByPatterns([], []);
+    my $res = {};
+    for my $item (@$units) {
+        my ($id, $description, $load_state, $active_state, $sub_state,
+            $following, $unit_path, $job_id, $job_type, $job_path) = @$item;
+        next unless $following eq '';
+        next if $job_id == 0 and $active_state eq 'inactive';
+        $res->{$id} = { load => $load_state, state => $active_state, substate => $sub_state };
+    }
+    return $res;
+}
+
+# Returns whether a systemd unit is active
+sub unit_is_active {
+    my ($unit_name) = @_;
+
+    my $mgr = Net::DBus->system->get_service('org.freedesktop.systemd1')->get_object('/org/freedesktop/systemd1');
+    my $units = $mgr->ListUnitsByNames([$unit_name]);
+    if (scalar(@{$units}) == 0) {
+        return 0;
+    }
+    my $active_state = $units->[0]->[3]; ## no critic (ValuesAndExpressions::ProhibitMagicNumbers)
+    return $active_state eq 'active' || $active_state eq 'activating';
+}
+
+sub parseFstab {
+    my ($filename) = @_;
+    my ($fss, $swaps);
+    foreach my $line (read_file($filename, err_mode => 'quiet')) {
+        chomp($line);
+        $line =~ s/^\s*#.*//;
+        next if $line =~ /^\s*$/;
+        my @xs = split(/ /, $line);
+        if ($xs[2] eq "swap") {
+            $swaps->{$xs[0]} = { options => $xs[3] // "" };
+        } else {
+            $fss->{$xs[1]} = { device => $xs[0], fsType => $xs[2], options => $xs[3] // "" };
+        }
+    }
+    return ($fss, $swaps);
+}
+
+# This subroutine takes a single ini file that specified systemd configuration
+# like unit configuration and parses it into a hash where the keys are the sections
+# of the unit file and the values are hashes themselves. These hashes have the unit file
+# keys as their keys (left side of =) and an array of all values that were set as their
+# values. If a value is empty (for example `ExecStart=`), then all current definitions are
+# removed.
+#
+# Instead of returning the hash, this subroutine takes a hashref to return the data in. This
+# allows calling the subroutine multiple times with the same hash to parse override files.
+sub parseSystemdIni {
+    my ($unitContents, $path) = @_;
+    # Tie the ini file to a hash for easier access
+    tie(my %fileContents, 'Config::IniFiles', (-file => $path, -allowempty => 1, -allowcontinue => 1)); ## no critic(Miscellanea::ProhibitTies)
+
+    # Copy over all sections
+    foreach my $sectionName (keys(%fileContents)) {
+        if ($sectionName eq "Install") {
+            # Skip the [Install] section because it has no relevant keys for us
+            next;
+        }
+        # Copy over all keys
+        foreach my $iniKey (keys(%{$fileContents{$sectionName}})) {
+            # Ensure the value is an array so it's easier to work with
+            my $iniValue = $fileContents{$sectionName}{$iniKey};
+            my @iniValues;
+            if (ref($iniValue) eq "ARRAY") {
+                @iniValues = @{$iniValue};
+            } else {
+                @iniValues = $iniValue;
+            }
+            # Go over all values
+            for my $iniValue (@iniValues) {
+                # If a value is empty, it's an override that tells us to clean the value
+                if ($iniValue eq "") {
+                    delete $unitContents->{$sectionName}->{$iniKey};
+                    next;
+                }
+                push(@{$unitContents->{$sectionName}->{$iniKey}}, $iniValue);
+            }
+        }
+    }
+    return;
+}
+
+# This subroutine takes the path to a systemd configuration file (like a unit configuration),
+# parses it, and returns a hash that contains the contents. The contents of this hash are
+# explained in the `parseSystemdIni` subroutine. Neither the sections nor the keys inside
+# the sections are consistently sorted.
+#
+# If a directory with the same basename ending in .d exists next to the unit file, it will be
+# assumed to contain override files which will be parsed as well and handled properly.
+sub parse_unit {
+    my ($unit_path) = @_;
+
+    # Parse the main unit and all overrides
+    my %unit_data;
+    # Replace \ with \\ so glob() still works with units that have a \ in them
+    # Valid characters in unit names are ASCII letters, digits, ":", "-", "_", ".", and "\"
+    $unit_path =~ s/\\/\\\\/gmsx;
+    foreach (glob("${unit_path}{,.d/*.conf}")) {
+        parseSystemdIni(\%unit_data, "$_")
+    }
+    return %unit_data;
+}
+
+# Checks whether a specified boolean in a systemd unit is true
+# or false, with a default that is applied when the value is not set.
+sub parseSystemdBool {
+    my ($unitConfig, $sectionName, $boolName, $default) = @_;
+
+    my @values = @{$unitConfig->{$sectionName}{$boolName} // []};
+    # Return default if value is not set
+    if (scalar(@values) lt 1 || not defined($values[-1])) {
+        return $default;
+    }
+    # If value is defined multiple times, use the last definition
+    my $last = $values[-1];
+    # These are valid values as of systemd.syntax(7)
+    return $last eq "1" || $last eq "yes" || $last eq "true" || $last eq "on";
+}
+
+sub recordUnit {
+    my ($fn, $unit) = @_;
+    write_file($fn, { append => 1 }, "$unit\n") if $action ne "dry-activate";
+}
+
+# The opposite of recordUnit, removes a unit name from a file
+sub unrecord_unit {
+    my ($fn, $unit) = @_;
+    edit_file(sub { s/^$unit\n//msx }, $fn) if $action ne "dry-activate";
+}
+
+# Compare the contents of two unit files and return whether the unit
+# needs to be restarted or reloaded. If the units differ, the service
+# is restarted unless the only difference is `X-Reload-Triggers` in the
+# `Unit` section. If this is the only modification, the unit is reloaded
+# instead of restarted.
+# Returns:
+# - 0 if the units are equal
+# - 1 if the units are different and a restart action is required
+# - 2 if the units are different and a reload action is required
+sub compare_units {
+    my ($old_unit, $new_unit) = @_;
+    my $ret = 0;
+    # Keys to ignore in the [Unit] section
+    my %unit_section_ignores = map { $_ => 1 } qw(
+        X-Reload-Triggers
+        Description Documentation
+        OnFailure OnSuccess OnFailureJobMode
+        IgnoreOnIsolate StopWhenUnneeded
+        RefuseManualStart RefuseManualStop
+        AllowIsolate CollectMode
+        SourcePath
+    );
+
+    my $comp_array = sub {
+      my ($a, $b) = @_;
+      return join("\0", @{$a}) eq join("\0", @{$b});
+    };
+
+    # Comparison hash for the sections
+    my %section_cmp = map { $_ => 1 } keys(%{$new_unit});
+    # Iterate over the sections
+    foreach my $section_name (keys(%{$old_unit})) {
+        # Missing section in the new unit?
+        if (not exists($section_cmp{$section_name})) {
+            # If the [Unit] section was removed, make sure that only keys
+            # were in it that are ignored
+            if ($section_name eq 'Unit') {
+                foreach my $ini_key (keys(%{$old_unit->{'Unit'}})) {
+                    if (not defined($unit_section_ignores{$ini_key})) {
+                        return 1;
+                    }
+                }
+                next; # check the next section
+            } else {
+                return 1;
+            }
+            if ($section_name eq 'Unit' and %{$old_unit->{'Unit'}} == 1 and defined(%{$old_unit->{'Unit'}}{'X-Reload-Triggers'})) {
+                # If a new [Unit] section was removed that only contained X-Reload-Triggers,
+                # do nothing.
+                next;
+            } else {
+                return 1;
+            }
+        }
+        delete $section_cmp{$section_name};
+        # Comparison hash for the section contents
+        my %ini_cmp = map { $_ => 1 } keys(%{$new_unit->{$section_name}});
+        # Iterate over the keys of the section
+        foreach my $ini_key (keys(%{$old_unit->{$section_name}})) {
+            delete $ini_cmp{$ini_key};
+            my @old_value = @{$old_unit->{$section_name}{$ini_key}};
+            # If the key is missing in the new unit, they are different...
+            if (not $new_unit->{$section_name}{$ini_key}) {
+                # ... unless the key that is now missing is one of the ignored keys
+                if ($section_name eq 'Unit' and defined($unit_section_ignores{$ini_key})) {
+                    next;
+                }
+                return 1;
+            }
+            my @new_value = @{$new_unit->{$section_name}{$ini_key}};
+            # If the contents are different, the units are different
+            if (not $comp_array->(\@old_value, \@new_value)) {
+                # Check if only the reload triggers changed or one of the ignored keys
+                if ($section_name eq 'Unit') {
+                    if ($ini_key eq 'X-Reload-Triggers') {
+                        $ret = 2;
+                        next;
+                    } elsif (defined($unit_section_ignores{$ini_key})) {
+                        next;
+                    }
+                }
+                return 1;
+            }
+        }
+        # A key was introduced that was missing in the old unit
+        if (%ini_cmp) {
+            if ($section_name eq 'Unit') {
+                foreach my $ini_key (keys(%ini_cmp)) {
+                    if ($ini_key eq 'X-Reload-Triggers') {
+                        $ret = 2;
+                    } elsif (defined($unit_section_ignores{$ini_key})) {
+                        next;
+                    } else {
+                        return 1;
+                    }
+                }
+            } else {
+                return 1;
+            }
+        };
+    }
+    # A section was introduced that was missing in the old unit
+    if (%section_cmp) {
+        if (%section_cmp == 1 and defined($section_cmp{'Unit'})) {
+            foreach my $ini_key (keys(%{$new_unit->{'Unit'}})) {
+                if (not defined($unit_section_ignores{$ini_key})) {
+                    return 1;
+                } elsif ($ini_key eq 'X-Reload-Triggers') {
+                    $ret = 2;
+                }
+            }
+        } else {
+            return 1;
+        }
+    }
+
+    return $ret;
+}
+
+sub handleModifiedUnit {
+    my ($unit, $baseName, $newUnitFile, $newUnitInfo, $activePrev, $unitsToStop, $unitsToStart, $unitsToReload, $unitsToRestart, $unitsToSkip) = @_;
+
+    if ($unit eq "sysinit.target" || $unit eq "basic.target" || $unit eq "multi-user.target" || $unit eq "graphical.target" || $unit =~ /\.path$/ || $unit =~ /\.slice$/) {
+        # Do nothing.  These cannot be restarted directly.
+
+        # Slices and Paths don't have to be restarted since
+        # properties (resource limits and inotify watches)
+        # seem to get applied on daemon-reload.
+    } elsif ($unit =~ /\.mount$/) {
+        # Reload the changed mount unit to force a remount.
+        # FIXME: only reload when Options= changed, restart otherwise
+        $unitsToReload->{$unit} = 1;
+        recordUnit($reloadListFile, $unit);
+    } elsif ($unit =~ /\.socket$/) {
+        # FIXME: do something?
+        # Attempt to fix this: https://github.com/NixOS/nixpkgs/pull/141192
+        # Revert of the attempt: https://github.com/NixOS/nixpkgs/pull/147609
+        # More details: https://github.com/NixOS/nixpkgs/issues/74899#issuecomment-981142430
+    } else {
+        my %unitInfo = $newUnitInfo ? %{$newUnitInfo} : parse_unit($newUnitFile);
+        if (parseSystemdBool(\%unitInfo, "Service", "X-ReloadIfChanged", 0) and not $unitsToRestart->{$unit} and not $unitsToStop->{$unit}) {
+            $unitsToReload->{$unit} = 1;
+            recordUnit($reloadListFile, $unit);
+        }
+        elsif (!parseSystemdBool(\%unitInfo, "Service", "X-RestartIfChanged", 1) || parseSystemdBool(\%unitInfo, "Unit", "RefuseManualStop", 0) || parseSystemdBool(\%unitInfo, "Unit", "X-OnlyManualStart", 0)) {
+            $unitsToSkip->{$unit} = 1;
+        } else {
+            # It doesn't make sense to stop and start non-services because
+            # they can't have ExecStop=
+            if (!parseSystemdBool(\%unitInfo, "Service", "X-StopIfChanged", 1) || $unit !~ /\.service$/) {
+                # This unit should be restarted instead of
+                # stopped and started.
+                $unitsToRestart->{$unit} = 1;
+                recordUnit($restartListFile, $unit);
+                # Remove from units to reload so we don't restart and reload
+                if ($unitsToReload->{$unit}) {
+                    delete $unitsToReload->{$unit};
+                    unrecord_unit($reloadListFile, $unit);
+                }
+            } else {
+                # If this unit is socket-activated, then stop the
+                # socket unit(s) as well, and restart the
+                # socket(s) instead of the service.
+                my $socket_activated = 0;
+                if ($unit =~ /\.service$/) {
+                    my @sockets = split(/ /, join(" ", @{$unitInfo{Service}{Sockets} // []}));
+                    if (scalar(@sockets) == 0) {
+                        @sockets = ("$baseName.socket");
+                    }
+                    foreach my $socket (@sockets) {
+                        if (defined($activePrev->{$socket})) {
+                            # We can now be sure this is a socket-activate unit
+
+                            $unitsToStop->{$socket} = 1;
+                            # Only restart sockets that actually
+                            # exist in new configuration:
+                            if (-e "$out/etc/systemd/system/$socket") {
+                                $unitsToStart->{$socket} = 1;
+                                if ($unitsToStart eq $unitsToRestart) {
+                                    recordUnit($restartListFile, $socket);
+                                } else {
+                                    recordUnit($startListFile, $socket);
+                                }
+                                $socket_activated = 1;
+                            }
+                            # Remove from units to reload so we don't restart and reload
+                            if ($unitsToReload->{$unit}) {
+                                delete $unitsToReload->{$unit};
+                                unrecord_unit($reloadListFile, $unit);
+                            }
+                        }
+                    }
+                }
+
+                # If the unit is not socket-activated, record
+                # that this unit needs to be started below.
+                # We write this to a file to ensure that the
+                # service gets restarted if we're interrupted.
+                if (!$socket_activated) {
+                    $unitsToStart->{$unit} = 1;
+                    if ($unitsToStart eq $unitsToRestart) {
+                        recordUnit($restartListFile, $unit);
+                    } else {
+                        recordUnit($startListFile, $unit);
+                    }
+                }
+
+                $unitsToStop->{$unit} = 1;
+                # Remove from units to reload so we don't restart and reload
+                if ($unitsToReload->{$unit}) {
+                    delete $unitsToReload->{$unit};
+                    unrecord_unit($reloadListFile, $unit);
+                }
+            }
+        }
+    }
+}
+
+# Figure out what units need to be stopped, started, restarted or reloaded.
+my (%unitsToStop, %unitsToSkip, %unitsToStart, %unitsToRestart, %unitsToReload);
+
+my %unitsToFilter; # units not shown
+
+$unitsToStart{$_} = 1 foreach
+    split('\n', read_file($startListFile, err_mode => 'quiet') // "");
+
+$unitsToRestart{$_} = 1 foreach
+    split('\n', read_file($restartListFile, err_mode => 'quiet') // "");
+
+$unitsToReload{$_} = 1 foreach
+    split('\n', read_file($reloadListFile, err_mode => 'quiet') // "");
+
+my $activePrev = getActiveUnits();
+while (my ($unit, $state) = each(%{$activePrev})) {
+    my $baseUnit = $unit;
+
+    my $prevUnitFile = "/etc/systemd/system/$baseUnit";
+    my $newUnitFile = "$out/etc/systemd/system/$baseUnit";
+
+    # Detect template instances.
+    if (!-e $prevUnitFile && !-e $newUnitFile && $unit =~ /^(.*)@[^\.]*\.(.*)$/) {
+      $baseUnit = "$1\@.$2";
+      $prevUnitFile = "/etc/systemd/system/$baseUnit";
+      $newUnitFile = "$out/etc/systemd/system/$baseUnit";
+    }
+
+    my $baseName = $baseUnit;
+    $baseName =~ s/\.[a-z]*$//;
+
+    if (-e $prevUnitFile && ($state->{state} eq "active" || $state->{state} eq "activating")) {
+        if (! -e $newUnitFile || abs_path($newUnitFile) eq "/dev/null") {
+            my %unitInfo = parse_unit($prevUnitFile);
+            $unitsToStop{$unit} = 1 if parseSystemdBool(\%unitInfo, "Unit", "X-StopOnRemoval", 1);
+        }
+
+        elsif ($unit =~ /\.target$/) {
+            my %unitInfo = parse_unit($newUnitFile);
+
+            # Cause all active target units to be restarted below.
+            # This should start most changed units we stop here as
+            # well as any new dependencies (including new mounts and
+            # swap devices).  FIXME: the suspend target is sometimes
+            # active after the system has resumed, which probably
+            # should not be the case.  Just ignore it.
+            if ($unit ne "suspend.target" && $unit ne "hibernate.target" && $unit ne "hybrid-sleep.target") {
+                unless (parseSystemdBool(\%unitInfo, "Unit", "RefuseManualStart", 0) || parseSystemdBool(\%unitInfo, "Unit", "X-OnlyManualStart", 0)) {
+                    $unitsToStart{$unit} = 1;
+                    recordUnit($startListFile, $unit);
+                    # Don't spam the user with target units that always get started.
+                    $unitsToFilter{$unit} = 1;
+                }
+            }
+
+            # Stop targets that have X-StopOnReconfiguration set.
+            # This is necessary to respect dependency orderings
+            # involving targets: if unit X starts after target Y and
+            # target Y starts after unit Z, then if X and Z have both
+            # changed, then X should be restarted after Z.  However,
+            # if target Y is in the "active" state, X and Z will be
+            # restarted at the same time because X's dependency on Y
+            # is already satisfied.  Thus, we need to stop Y first.
+            # Stopping a target generally has no effect on other units
+            # (unless there is a PartOf dependency), so this is just a
+            # bookkeeping thing to get systemd to do the right thing.
+            if (parseSystemdBool(\%unitInfo, "Unit", "X-StopOnReconfiguration", 0)) {
+                $unitsToStop{$unit} = 1;
+            }
+        }
+
+        else {
+            my %old_unit_info = parse_unit($prevUnitFile);
+            my %new_unit_info = parse_unit($newUnitFile);
+            my $diff = compare_units(\%old_unit_info, \%new_unit_info);
+            if ($diff == 1) {
+                handleModifiedUnit($unit, $baseName, $newUnitFile, \%new_unit_info, $activePrev, \%unitsToStop, \%unitsToStart, \%unitsToReload, \%unitsToRestart, \%unitsToSkip);
+            } elsif ($diff == 2 and not $unitsToRestart{$unit}) {
+                $unitsToReload{$unit} = 1;
+                recordUnit($reloadListFile, $unit);
+            }
+        }
+    }
+}
+
+sub pathToUnitName {
+    my ($path) = @_;
+    # Use current version of systemctl binary before daemon is reexeced.
+    open(my $cmd, "-|", "$curSystemd/systemd-escape", "--suffix=mount", "-p", $path)
+        or die "Unable to escape $path!\n";
+    my $escaped = join("", <$cmd>);
+    chomp($escaped);
+    close($cmd) or die('Unable to close systemd-escape pipe');
+    return $escaped;
+}
+
+# Compare the previous and new fstab to figure out which filesystems
+# need a remount or need to be unmounted.  New filesystems are mounted
+# automatically by starting local-fs.target.  FIXME: might be nicer if
+# we generated units for all mounts; then we could unify this with the
+# unit checking code above.
+my ($prevFss, $prevSwaps) = parseFstab("/etc/fstab");
+my ($newFss, $newSwaps) = parseFstab("$out/etc/fstab");
+foreach my $mountPoint (keys(%$prevFss)) {
+    my $prev = $prevFss->{$mountPoint};
+    my $new = $newFss->{$mountPoint};
+    my $unit = pathToUnitName($mountPoint);
+    if (!defined($new)) {
+        # Filesystem entry disappeared, so unmount it.
+        $unitsToStop{$unit} = 1;
+    } elsif ($prev->{fsType} ne $new->{fsType} || $prev->{device} ne $new->{device}) {
+        # Filesystem type or device changed, so unmount and mount it.
+        $unitsToStop{$unit} = 1;
+        $unitsToStart{$unit} = 1;
+        recordUnit($startListFile, $unit);
+    } elsif ($prev->{options} ne $new->{options}) {
+        # Mount options changes, so remount it.
+        $unitsToReload{$unit} = 1;
+        recordUnit($reloadListFile, $unit);
+    }
+}
+
+# Also handles swap devices.
+foreach my $device (keys(%$prevSwaps)) {
+    my $prev = $prevSwaps->{$device};
+    my $new = $newSwaps->{$device};
+    if (!defined($new)) {
+        # Swap entry disappeared, so turn it off.  Can't use
+        # "systemctl stop" here because systemd has lots of alias
+        # units that prevent a stop from actually calling
+        # "swapoff".
+        if ($action ne "dry-activate") {
+            print STDERR "would stop swap device: $device\n";
+        } else {
+            print STDERR "stopping swap device: $device\n";
+            system("@utillinux@/sbin/swapoff", $device);
+        }
+    }
+    # FIXME: update swap options (i.e. its priority).
+}
+
+
+# Should we have systemd re-exec itself?
+my $prevSystemd = abs_path("/proc/1/exe") // "/unknown";
+my $prevSystemdSystemConfig = abs_path("/etc/systemd/system.conf") // "/unknown";
+my $newSystemd = abs_path("@systemd@/lib/systemd/systemd") or die;
+my $newSystemdSystemConfig = abs_path("$out/etc/systemd/system.conf") // "/unknown";
+
+my $restartSystemd = $prevSystemd ne $newSystemd;
+if ($prevSystemdSystemConfig ne $newSystemdSystemConfig) {
+    $restartSystemd = 1;
+}
+
+
+sub filterUnits {
+    my ($units) = @_;
+    my @res;
+    foreach my $unit (sort(keys(%{$units}))) {
+        push(@res, $unit) if !defined($unitsToFilter{$unit});
+    }
+    return @res;
+}
+
+my @unitsToStopFiltered = filterUnits(\%unitsToStop);
+
+
+# Show dry-run actions.
+if ($action eq "dry-activate") {
+    print STDERR "would stop the following units: ", join(", ", @unitsToStopFiltered), "\n"
+        if scalar(@unitsToStopFiltered) > 0;
+    print STDERR "would NOT stop the following changed units: ", join(", ", sort(keys(%unitsToSkip))), "\n"
+        if scalar(keys(%unitsToSkip)) > 0;
+
+    print STDERR "would activate the configuration...\n";
+    system("$out/dry-activate", "$out");
+
+    # Handle the activation script requesting the restart or reload of a unit.
+    foreach (split('\n', read_file($dryRestartByActivationFile, err_mode => 'quiet') // "")) {
+        my $unit = $_;
+        my $baseUnit = $unit;
+        my $newUnitFile = "$out/etc/systemd/system/$baseUnit";
+
+        # Detect template instances.
+        if (!-e $newUnitFile && $unit =~ /^(.*)@[^\.]*\.(.*)$/) {
+          $baseUnit = "$1\@.$2";
+          $newUnitFile = "$out/etc/systemd/system/$baseUnit";
+        }
+
+        my $baseName = $baseUnit;
+        $baseName =~ s/\.[a-z]*$//;
+
+        # Start units if they were not active previously
+        if (not defined($activePrev->{$unit})) {
+            $unitsToStart{$unit} = 1;
+            next;
+        }
+
+        handleModifiedUnit($unit, $baseName, $newUnitFile, undef, $activePrev, \%unitsToRestart, \%unitsToRestart, \%unitsToReload, \%unitsToRestart, \%unitsToSkip);
+    }
+    unlink($dryRestartByActivationFile);
+
+    foreach (split('\n', read_file($dryReloadByActivationFile, err_mode => 'quiet') // "")) {
+        my $unit = $_;
+
+        if (defined($activePrev->{$unit}) and not $unitsToRestart{$unit} and not $unitsToStop{$unit}) {
+            $unitsToReload{$unit} = 1;
+            recordUnit($reloadListFile, $unit);
+        }
+    }
+    unlink($dryReloadByActivationFile);
+
+    print STDERR "would restart systemd\n" if $restartSystemd;
+    print STDERR "would reload the following units: ", join(", ", sort(keys(%unitsToReload))), "\n"
+        if scalar(keys(%unitsToReload)) > 0;
+    print STDERR "would restart the following units: ", join(", ", sort(keys(%unitsToRestart))), "\n"
+        if scalar(keys(%unitsToRestart)) > 0;
+    my @unitsToStartFiltered = filterUnits(\%unitsToStart);
+    print STDERR "would start the following units: ", join(", ", @unitsToStartFiltered), "\n"
+        if scalar(@unitsToStartFiltered);
+    exit 0;
+}
+
+
+syslog(LOG_NOTICE, "switching to system configuration $out");
+
+if (scalar(keys(%unitsToStop)) > 0) {
+    print STDERR "stopping the following units: ", join(", ", @unitsToStopFiltered), "\n"
+        if scalar(@unitsToStopFiltered);
+    # Use current version of systemctl binary before daemon is reexeced.
+    system("$curSystemd/systemctl", "stop", "--", sort(keys(%unitsToStop)));
+}
+
+print STDERR "NOT restarting the following changed units: ", join(", ", sort(keys(%unitsToSkip))), "\n"
+    if scalar(keys(%unitsToSkip)) > 0;
+
+# Activate the new configuration (i.e., update /etc, make accounts,
+# and so on).
+my $res = 0;
+print STDERR "activating the configuration...\n";
+system("$out/activate", "$out") == 0 or $res = 2;
+
+# Handle the activation script requesting the restart or reload of a unit.
+foreach (split('\n', read_file($restartByActivationFile, err_mode => 'quiet') // "")) {
+    my $unit = $_;
+    my $baseUnit = $unit;
+    my $newUnitFile = "$out/etc/systemd/system/$baseUnit";
+
+    # Detect template instances.
+    if (!-e $newUnitFile && $unit =~ /^(.*)@[^\.]*\.(.*)$/) {
+      $baseUnit = "$1\@.$2";
+      $newUnitFile = "$out/etc/systemd/system/$baseUnit";
+    }
+
+    my $baseName = $baseUnit;
+    $baseName =~ s/\.[a-z]*$//;
+
+    # Start units if they were not active previously
+    if (not defined($activePrev->{$unit})) {
+        $unitsToStart{$unit} = 1;
+        recordUnit($startListFile, $unit);
+        next;
+    }
+
+    handleModifiedUnit($unit, $baseName, $newUnitFile, undef, $activePrev, \%unitsToRestart, \%unitsToRestart, \%unitsToReload, \%unitsToRestart, \%unitsToSkip);
+}
+# We can remove the file now because it has been propagated to the other restart/reload files
+unlink($restartByActivationFile);
+
+foreach (split('\n', read_file($reloadByActivationFile, err_mode => 'quiet') // "")) {
+    my $unit = $_;
+
+    if (defined($activePrev->{$unit}) and not $unitsToRestart{$unit} and not $unitsToStop{$unit}) {
+        $unitsToReload{$unit} = 1;
+        recordUnit($reloadListFile, $unit);
+    }
+}
+# We can remove the file now because it has been propagated to the other reload file
+unlink($reloadByActivationFile);
+
+# Restart systemd if necessary. Note that this is done using the
+# current version of systemd, just in case the new one has trouble
+# communicating with the running pid 1.
+if ($restartSystemd) {
+    print STDERR "restarting systemd...\n";
+    system("$curSystemd/systemctl", "daemon-reexec") == 0 or $res = 2;
+}
+
+# Forget about previously failed services.
+system("@systemd@/bin/systemctl", "reset-failed");
+
+# Make systemd reload its units.
+system("@systemd@/bin/systemctl", "daemon-reload") == 0 or $res = 3;
+
+# Reload user units
+open(my $listActiveUsers, '-|', '@systemd@/bin/loginctl', 'list-users', '--no-legend');
+while (my $f = <$listActiveUsers>) {
+    next unless $f =~ /^\s*(?<uid>\d+)\s+(?<user>\S+)/;
+    my ($uid, $name) = ($+{uid}, $+{user});
+    print STDERR "reloading user units for $name...\n";
+
+    system("@su@", "-s", "@shell@", "-l", $name, "-c",
+           "export XDG_RUNTIME_DIR=/run/user/$uid; " .
+           "$curSystemd/systemctl --user daemon-reexec; " .
+           "@systemd@/bin/systemctl --user start nixos-activation.service");
+}
+
+close($listActiveUsers);
+
+# Set the new tmpfiles
+print STDERR "setting up tmpfiles\n";
+system("@systemd@/bin/systemd-tmpfiles", "--create", "--remove", "--exclude-prefix=/dev") == 0 or $res = 3;
+
+# Before reloading we need to ensure that the units are still active. They may have been
+# deactivated because one of their requirements got stopped. If they are inactive
+# but should have been reloaded, the user probably expects them to be started.
+if (scalar(keys(%unitsToReload)) > 0) {
+    for my $unit (keys(%unitsToReload)) {
+        if (!unit_is_active($unit)) {
+            # Figure out if we need to start the unit
+            my %unit_info = parse_unit("$out/etc/systemd/system/$unit");
+            if (!(parseSystemdBool(\%unit_info, 'Unit', 'RefuseManualStart', 0) || parseSystemdBool(\%unit_info, 'Unit', 'X-OnlyManualStart', 0))) {
+                $unitsToStart{$unit} = 1;
+                recordUnit($startListFile, $unit);
+            }
+            # Don't reload the unit, reloading would fail
+            delete %unitsToReload{$unit};
+            unrecord_unit($reloadListFile, $unit);
+        }
+    }
+}
+# Reload units that need it. This includes remounting changed mount
+# units.
+if (scalar(keys(%unitsToReload)) > 0) {
+    print STDERR "reloading the following units: ", join(", ", sort(keys(%unitsToReload))), "\n";
+    system("@systemd@/bin/systemctl", "reload", "--", sort(keys(%unitsToReload))) == 0 or $res = 4;
+    unlink($reloadListFile);
+}
+
+# Restart changed services (those that have to be restarted rather
+# than stopped and started).
+if (scalar(keys(%unitsToRestart)) > 0) {
+    print STDERR "restarting the following units: ", join(", ", sort(keys(%unitsToRestart))), "\n";
+    system("@systemd@/bin/systemctl", "restart", "--", sort(keys(%unitsToRestart))) == 0 or $res = 4;
+    unlink($restartListFile);
+}
+
+# Start all active targets, as well as changed units we stopped above.
+# The latter is necessary because some may not be dependencies of the
+# targets (i.e., they were manually started).  FIXME: detect units
+# that are symlinks to other units.  We shouldn't start both at the
+# same time because we'll get a "Failed to add path to set" error from
+# systemd.
+my @unitsToStartFiltered = filterUnits(\%unitsToStart);
+print STDERR "starting the following units: ", join(", ", @unitsToStartFiltered), "\n"
+    if scalar(@unitsToStartFiltered);
+system("@systemd@/bin/systemctl", "start", "--", sort(keys(%unitsToStart))) == 0 or $res = 4;
+unlink($startListFile);
+
+
+# Print failed and new units.
+my (@failed, @new);
+my $activeNew = getActiveUnits();
+while (my ($unit, $state) = each(%{$activeNew})) {
+    if ($state->{state} eq "failed") {
+        push(@failed, $unit);
+        next;
+    }
+
+    if ($state->{substate} eq "auto-restart") {
+        # A unit in auto-restart substate is a failure *if* it previously failed to start
+        my $main_status = `@systemd@/bin/systemctl show --value --property=ExecMainStatus '$unit'`;
+        chomp($main_status);
+
+        if ($main_status ne "0") {
+            push(@failed, $unit);
+            next;
+        }
+    }
+
+    # Ignore scopes since they are not managed by this script but rather
+    # created and managed by third-party services via the systemd dbus API.
+    # This only lists units that are not failed (including ones that are in auto-restart but have not failed previously)
+    if ($state->{state} ne "failed" && !defined($activePrev->{$unit}) && $unit !~ /\.scope$/msx) {
+        push(@new, $unit);
+    }
+}
+
+if (scalar(@new) > 0) {
+    print STDERR "the following new units were started: ", join(", ", sort(@new)), "\n"
+}
+
+if (scalar(@failed) > 0) {
+    my @failed_sorted = sort(@failed);
+    print STDERR "warning: the following units failed: ", join(", ", @failed_sorted), "\n\n";
+    system("@systemd@/bin/systemctl status --no-pager --full '" . join("' '", @failed_sorted) . "' >&2");
+    $res = 4;
+}
+
+if ($res == 0) {
+    syslog(LOG_NOTICE, "finished switching to system configuration $out");
+} else {
+    syslog(LOG_ERR, "switching to system configuration $out failed (status $res)");
+}
+
+exit($res);
diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix
new file mode 100644
index 00000000000..b8aeee8c11b
--- /dev/null
+++ b/nixos/modules/system/activation/top-level.nix
@@ -0,0 +1,355 @@
+{ config, lib, pkgs, extendModules, noUserModules, ... }:
+
+with lib;
+
+let
+
+
+  # This attribute is responsible for creating boot entries for
+  # child configuration. They are only (directly) accessible
+  # when the parent configuration is boot default. For example,
+  # you can provide an easy way to boot the same configuration
+  # as you use, but with another kernel
+  # !!! fix this
+  children =
+    mapAttrs
+      (childName: childConfig: childConfig.configuration.system.build.toplevel)
+      config.specialisation;
+
+  systemBuilder =
+    let
+      kernelPath = "${config.boot.kernelPackages.kernel}/" +
+        "${config.system.boot.loader.kernelFile}";
+      initrdPath = "${config.system.build.initialRamdisk}/" +
+        "${config.system.boot.loader.initrdFile}";
+    in ''
+      mkdir $out
+
+      # Containers don't have their own kernel or initrd.  They boot
+      # directly into stage 2.
+      ${optionalString (!config.boot.isContainer) ''
+        if [ ! -f ${kernelPath} ]; then
+          echo "The bootloader cannot find the proper kernel image."
+          echo "(Expecting ${kernelPath})"
+          false
+        fi
+
+        ln -s ${kernelPath} $out/kernel
+        ln -s ${config.system.modulesTree} $out/kernel-modules
+        ${optionalString (config.hardware.deviceTree.package != null) ''
+          ln -s ${config.hardware.deviceTree.package} $out/dtbs
+        ''}
+
+        echo -n "$kernelParams" > $out/kernel-params
+
+        ln -s ${initrdPath} $out/initrd
+
+        ln -s ${config.system.build.initialRamdiskSecretAppender}/bin/append-initrd-secrets $out
+
+        ln -s ${config.hardware.firmware}/lib/firmware $out/firmware
+      ''}
+
+      echo "$activationScript" > $out/activate
+      echo "$dryActivationScript" > $out/dry-activate
+      substituteInPlace $out/activate --subst-var out
+      substituteInPlace $out/dry-activate --subst-var out
+      chmod u+x $out/activate $out/dry-activate
+      unset activationScript dryActivationScript
+      ${pkgs.stdenv.shellDryRun} $out/activate
+      ${pkgs.stdenv.shellDryRun} $out/dry-activate
+
+      cp ${config.system.build.bootStage2} $out/init
+      substituteInPlace $out/init --subst-var-by systemConfig $out
+
+      ln -s ${config.system.build.etc}/etc $out/etc
+      ln -s ${config.system.path} $out/sw
+      ln -s "$systemd" $out/systemd
+
+      echo -n "$configurationName" > $out/configuration-name
+      echo -n "systemd ${toString config.systemd.package.interfaceVersion}" > $out/init-interface-version
+      echo -n "$nixosLabel" > $out/nixos-version
+      echo -n "${config.boot.kernelPackages.stdenv.hostPlatform.system}" > $out/system
+
+      mkdir $out/specialisation
+      ${concatStringsSep "\n"
+      (mapAttrsToList (name: path: "ln -s ${path} $out/specialisation/${name}") children)}
+
+      mkdir $out/bin
+      export localeArchive="${config.i18n.glibcLocales}/lib/locale/locale-archive"
+      substituteAll ${./switch-to-configuration.pl} $out/bin/switch-to-configuration
+      chmod +x $out/bin/switch-to-configuration
+      ${optionalString (pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) ''
+        if ! output=$($perl/bin/perl -c $out/bin/switch-to-configuration 2>&1); then
+          echo "switch-to-configuration syntax is not valid:"
+          echo "$output"
+          exit 1
+        fi
+      ''}
+
+      echo -n "${toString config.system.extraDependencies}" > $out/extra-dependencies
+
+      ${config.system.extraSystemBuilderCmds}
+    '';
+
+  # Putting it all together.  This builds a store path containing
+  # symlinks to the various parts of the built configuration (the
+  # kernel, systemd units, init scripts, etc.) as well as a script
+  # `switch-to-configuration' that activates the configuration and
+  # makes it bootable.
+  baseSystem = pkgs.stdenvNoCC.mkDerivation {
+    name = "nixos-system-${config.system.name}-${config.system.nixos.label}";
+    preferLocalBuild = true;
+    allowSubstitutes = false;
+    buildCommand = systemBuilder;
+
+    inherit (pkgs) coreutils;
+    systemd = config.systemd.package;
+    shell = "${pkgs.bash}/bin/sh";
+    su = "${pkgs.shadow.su}/bin/su";
+    utillinux = pkgs.util-linux;
+
+    kernelParams = config.boot.kernelParams;
+    installBootLoader = config.system.build.installBootLoader;
+    activationScript = config.system.activationScripts.script;
+    dryActivationScript = config.system.dryActivationScript;
+    nixosLabel = config.system.nixos.label;
+
+    configurationName = config.boot.loader.grub.configurationName;
+
+    # Needed by switch-to-configuration.
+    perl = pkgs.perl.withPackages (p: with p; [ ConfigIniFiles FileSlurp NetDBus ]);
+  };
+
+  # Handle assertions and warnings
+
+  failedAssertions = map (x: x.message) (filter (x: !x.assertion) config.assertions);
+
+  baseSystemAssertWarn = if failedAssertions != []
+    then throw "\nFailed assertions:\n${concatStringsSep "\n" (map (x: "- ${x}") failedAssertions)}"
+    else showWarnings config.warnings baseSystem;
+
+  # Replace runtime dependencies
+  system = foldr ({ oldDependency, newDependency }: drv:
+      pkgs.replaceDependency { inherit oldDependency newDependency drv; }
+    ) baseSystemAssertWarn config.system.replaceRuntimeDependencies;
+
+  /* Workaround until https://github.com/NixOS/nixpkgs/pull/156533
+     Call can be replaced by argument when that's merged.
+  */
+  tmpFixupSubmoduleBoundary = subopts:
+    lib.mkOption {
+      type = lib.types.submoduleWith {
+        modules = [ { options = subopts; } ];
+      };
+    };
+
+in
+
+{
+  imports = [
+    ../build.nix
+    (mkRemovedOptionModule [ "nesting" "clone" ] "Use `specialisation.«name» = { inheritParentConfig = true; configuration = { ... }; }` instead.")
+    (mkRemovedOptionModule [ "nesting" "children" ] "Use `specialisation.«name».configuration = { ... }` instead.")
+  ];
+
+  options = {
+
+    specialisation = mkOption {
+      default = {};
+      example = lib.literalExpression "{ fewJobsManyCores.configuration = { nix.settings = { core = 0; max-jobs = 1; }; }";
+      description = ''
+        Additional configurations to build. If
+        <literal>inheritParentConfig</literal> is true, the system
+        will be based on the overall system configuration.
+
+        To switch to a specialised configuration
+        (e.g. <literal>fewJobsManyCores</literal>) at runtime, run:
+
+        <screen>
+        <prompt># </prompt>sudo /run/current-system/specialisation/fewJobsManyCores/bin/switch-to-configuration test
+        </screen>
+      '';
+      type = types.attrsOf (types.submodule (
+        local@{ ... }: let
+          extend = if local.config.inheritParentConfig
+            then extendModules
+            else noUserModules.extendModules;
+        in {
+          options.inheritParentConfig = mkOption {
+            type = types.bool;
+            default = true;
+            description = "Include the entire system's configuration. Set to false to make a completely differently configured system.";
+          };
+
+          options.configuration = mkOption {
+            default = {};
+            description = ''
+              Arbitrary NixOS configuration.
+
+              Anything you can add to a normal NixOS configuration, you can add
+              here, including imports and config values, although nested
+              specialisations will be ignored.
+            '';
+            visible = "shallow";
+            inherit (extend { modules = [ ./no-clone.nix ]; }) type;
+          };
+        })
+      );
+    };
+
+    system.boot.loader.id = mkOption {
+      internal = true;
+      default = "";
+      description = ''
+        Id string of the used bootloader.
+      '';
+    };
+
+    system.boot.loader.kernelFile = mkOption {
+      internal = true;
+      default = pkgs.stdenv.hostPlatform.linux-kernel.target;
+      defaultText = literalExpression "pkgs.stdenv.hostPlatform.linux-kernel.target";
+      type = types.str;
+      description = ''
+        Name of the kernel file to be passed to the bootloader.
+      '';
+    };
+
+    system.boot.loader.initrdFile = mkOption {
+      internal = true;
+      default = "initrd";
+      type = types.str;
+      description = ''
+        Name of the initrd file to be passed to the bootloader.
+      '';
+    };
+
+    system.build = tmpFixupSubmoduleBoundary {
+      installBootLoader = mkOption {
+        internal = true;
+        # "; true" => make the `$out` argument from switch-to-configuration.pl
+        #             go to `true` instead of `echo`, hiding the useless path
+        #             from the log.
+        default = "echo 'Warning: do not know how to make this configuration bootable; please enable a boot loader.' 1>&2; true";
+        description = ''
+          A program that writes a bootloader installation script to the path passed in the first command line argument.
+
+          See <literal>nixos/modules/system/activation/switch-to-configuration.pl</literal>.
+        '';
+        type = types.unique {
+          message = ''
+            Only one bootloader can be enabled at a time. This requirement has not
+            been checked until NixOS 22.05. Earlier versions defaulted to the last
+            definition. Change your configuration to enable only one bootloader.
+          '';
+        } (types.either types.str types.package);
+      };
+
+      toplevel = mkOption {
+        type = types.package;
+        readOnly = true;
+        description = ''
+          This option contains the store path that typically represents a NixOS system.
+
+          You can read this path in a custom deployment tool for example.
+        '';
+      };
+    };
+
+
+    system.copySystemConfiguration = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        If enabled, copies the NixOS configuration file
+        (usually <filename>/etc/nixos/configuration.nix</filename>)
+        and links it from the resulting system
+        (getting to <filename>/run/current-system/configuration.nix</filename>).
+        Note that only this single file is copied, even if it imports others.
+      '';
+    };
+
+    system.extraSystemBuilderCmds = mkOption {
+      type = types.lines;
+      internal = true;
+      default = "";
+      description = ''
+        This code will be added to the builder creating the system store path.
+      '';
+    };
+
+    system.extraDependencies = mkOption {
+      type = types.listOf types.package;
+      default = [];
+      description = ''
+        A list of packages that should be included in the system
+        closure but not otherwise made available to users. This is
+        primarily used by the installation tests.
+      '';
+    };
+
+    system.replaceRuntimeDependencies = mkOption {
+      default = [];
+      example = lib.literalExpression "[ ({ original = pkgs.openssl; replacement = pkgs.callPackage /path/to/openssl { }; }) ]";
+      type = types.listOf (types.submodule (
+        { ... }: {
+          options.original = mkOption {
+            type = types.package;
+            description = "The original package to override.";
+          };
+
+          options.replacement = mkOption {
+            type = types.package;
+            description = "The replacement package.";
+          };
+        })
+      );
+      apply = map ({ original, replacement, ... }: {
+        oldDependency = original;
+        newDependency = replacement;
+      });
+      description = ''
+        List of packages to override without doing a full rebuild.
+        The original derivation and replacement derivation must have the same
+        name length, and ideally should have close-to-identical directory layout.
+      '';
+    };
+
+    system.name = mkOption {
+      type = types.str;
+      default =
+        if config.networking.hostName == ""
+        then "unnamed"
+        else config.networking.hostName;
+      defaultText = literalExpression ''
+        if config.networking.hostName == ""
+        then "unnamed"
+        else config.networking.hostName;
+      '';
+      description = ''
+        The name of the system used in the <option>system.build.toplevel</option> derivation.
+        </para><para>
+        That derivation has the following name:
+        <literal>"nixos-system-''${config.system.name}-''${config.system.nixos.label}"</literal>
+      '';
+    };
+
+  };
+
+
+  config = {
+
+    system.extraSystemBuilderCmds =
+      optionalString
+        config.system.copySystemConfiguration
+        ''ln -s '${import ../../../lib/from-env.nix "NIXOS_CONFIG" <nixos-config>}' \
+            "$out/configuration.nix"
+        '';
+
+    system.build.toplevel = system;
+
+  };
+
+  # uses extendModules to generate a type
+  meta.buildDocsInSandbox = false;
+}
diff --git a/nixos/modules/system/boot/binfmt.nix b/nixos/modules/system/boot/binfmt.nix
new file mode 100644
index 00000000000..33748358e45
--- /dev/null
+++ b/nixos/modules/system/boot/binfmt.nix
@@ -0,0 +1,325 @@
+{ config, lib, pkgs, ... }:
+let
+  inherit (lib) mkOption types optionalString stringAfter;
+
+  cfg = config.boot.binfmt;
+
+  makeBinfmtLine = name: { recognitionType, offset, magicOrExtension
+                         , mask, preserveArgvZero, openBinary
+                         , matchCredentials, fixBinary, ...
+                         }: let
+    type = if recognitionType == "magic" then "M" else "E";
+    offset' = toString offset;
+    mask' = toString mask;
+    interpreter = "/run/binfmt/${name}";
+    flags = if !(matchCredentials -> openBinary)
+              then throw "boot.binfmt.registrations.${name}: you can't specify openBinary = false when matchCredentials = true."
+            else optionalString preserveArgvZero "P" +
+                 optionalString (openBinary && !matchCredentials) "O" +
+                 optionalString matchCredentials "C" +
+                 optionalString fixBinary "F";
+  in ":${name}:${type}:${offset'}:${magicOrExtension}:${mask'}:${interpreter}:${flags}";
+
+  activationSnippet = name: { interpreter, wrapInterpreterInShell, ... }: if wrapInterpreterInShell then ''
+    rm -f /run/binfmt/${name}
+    cat > /run/binfmt/${name} << 'EOF'
+    #!${pkgs.bash}/bin/sh
+    exec -- ${interpreter} "$@"
+    EOF
+    chmod +x /run/binfmt/${name}
+  '' else ''
+    rm -f /run/binfmt/${name}
+    ln -s ${interpreter} /run/binfmt/${name}
+  '';
+
+  getEmulator = system: (lib.systems.elaborate { inherit system; }).emulator pkgs;
+  getQemuArch = system: (lib.systems.elaborate { inherit system; }).qemuArch;
+
+  # Mapping of systems to “magicOrExtension” and “mask”. Mostly taken from:
+  # - https://github.com/cleverca22/nixos-configs/blob/master/qemu.nix
+  # and
+  # - https://github.com/qemu/qemu/blob/master/scripts/qemu-binfmt-conf.sh
+  # TODO: maybe put these in a JSON file?
+  magics = {
+    armv6l-linux = {
+      magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\x00\xff\xfe\xff\xff\xff'';
+    };
+    armv7l-linux = {
+      magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\x00\xff\xfe\xff\xff\xff'';
+    };
+    aarch64-linux = {
+      magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\x00\xff\xfe\xff\xff\xff'';
+    };
+    aarch64_be-linux = {
+      magicOrExtension = ''\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
+    };
+    i386-linux = {
+      magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x03\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
+    i486-linux = {
+      magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
+    i586-linux = {
+      magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
+    i686-linux = {
+      magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
+    x86_64-linux = {
+      magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3e\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
+    alpha-linux = {
+      magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x26\x90'';
+      mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
+    sparc64-linux = {
+      magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x02'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
+    };
+    sparc-linux = {
+      magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x12'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
+    };
+    powerpc-linux = {
+      magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
+    };
+    powerpc64-linux = {
+      magicOrExtension = ''\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x15'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
+    };
+    powerpc64le-linux = {
+      magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x15\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\x00'';
+    };
+    mips-linux = {
+      magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
+    };
+    mipsel-linux = {
+      magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
+    mips64-linux = {
+      magicOrExtension = ''\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
+    };
+    mips64el-linux = {
+      magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
+    riscv32-linux = {
+      magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
+    riscv64-linux = {
+      magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
+    wasm32-wasi = {
+      magicOrExtension = ''\x00asm'';
+      mask = ''\xff\xff\xff\xff'';
+    };
+    wasm64-wasi = {
+      magicOrExtension = ''\x00asm'';
+      mask = ''\xff\xff\xff\xff'';
+    };
+    x86_64-windows = {
+      magicOrExtension = ".exe";
+      recognitionType = "extension";
+    };
+    i686-windows = {
+      magicOrExtension = ".exe";
+      recognitionType = "extension";
+    };
+  };
+
+in {
+  imports = [
+    (lib.mkRenamedOptionModule [ "boot" "binfmtMiscRegistrations" ] [ "boot" "binfmt" "registrations" ])
+  ];
+
+  options = {
+    boot.binfmt = {
+      registrations = mkOption {
+        default = {};
+
+        description = ''
+          Extra binary formats to register with the kernel.
+          See https://www.kernel.org/doc/html/latest/admin-guide/binfmt-misc.html for more details.
+        '';
+
+        type = types.attrsOf (types.submodule ({ config, ... }: {
+          options = {
+            recognitionType = mkOption {
+              default = "magic";
+              description = "Whether to recognize executables by magic number or extension.";
+              type = types.enum [ "magic" "extension" ];
+            };
+
+            offset = mkOption {
+              default = null;
+              description = "The byte offset of the magic number used for recognition.";
+              type = types.nullOr types.int;
+            };
+
+            magicOrExtension = mkOption {
+              description = "The magic number or extension to match on.";
+              type = types.str;
+            };
+
+            mask = mkOption {
+              default = null;
+              description =
+                "A mask to be ANDed with the byte sequence of the file before matching";
+              type = types.nullOr types.str;
+            };
+
+            interpreter = mkOption {
+              description = ''
+                The interpreter to invoke to run the program.
+
+                Note that the actual registration will point to
+                /run/binfmt/''${name}, so the kernel interpreter length
+                limit doesn't apply.
+              '';
+              type = types.path;
+            };
+
+            preserveArgvZero = mkOption {
+              default = false;
+              description = ''
+                Whether to pass the original argv[0] to the interpreter.
+
+                See the description of the 'P' flag in the kernel docs
+                for more details;
+              '';
+              type = types.bool;
+            };
+
+            openBinary = mkOption {
+              default = config.matchCredentials;
+              description = ''
+                Whether to pass the binary to the interpreter as an open
+                file descriptor, instead of a path.
+              '';
+              type = types.bool;
+            };
+
+            matchCredentials = mkOption {
+              default = false;
+              description = ''
+                Whether to launch with the credentials and security
+                token of the binary, not the interpreter (e.g. setuid
+                bit).
+
+                See the description of the 'C' flag in the kernel docs
+                for more details.
+
+                Implies/requires openBinary = true.
+              '';
+              type = types.bool;
+            };
+
+            fixBinary = mkOption {
+              default = false;
+              description = ''
+                Whether to open the interpreter file as soon as the
+                registration is loaded, rather than waiting for a
+                relevant file to be invoked.
+
+                See the description of the 'F' flag in the kernel docs
+                for more details.
+              '';
+              type = types.bool;
+            };
+
+            wrapInterpreterInShell = mkOption {
+              default = true;
+              description = ''
+                Whether to wrap the interpreter in a shell script.
+
+                This allows a shell command to be set as the interpreter.
+              '';
+              type = types.bool;
+            };
+
+            interpreterSandboxPath = mkOption {
+              internal = true;
+              default = null;
+              description = ''
+                Path of the interpreter to expose in the build sandbox.
+              '';
+              type = types.nullOr types.path;
+            };
+          };
+        }));
+      };
+
+      emulatedSystems = mkOption {
+        default = [];
+        example = [ "wasm32-wasi" "x86_64-windows" "aarch64-linux" ];
+        description = ''
+          List of systems to emulate. Will also configure Nix to
+          support your new systems.
+          Warning: the builder can execute all emulated systems within the same build, which introduces impurities in the case of cross compilation.
+        '';
+        type = types.listOf types.str;
+      };
+    };
+  };
+
+  config = {
+    boot.binfmt.registrations = builtins.listToAttrs (map (system: {
+      name = system;
+      value = let
+        interpreter = getEmulator system;
+        qemuArch = getQemuArch system;
+
+        preserveArgvZero = "qemu-${qemuArch}" == baseNameOf interpreter;
+        interpreterReg = let
+          wrapperName = "qemu-${qemuArch}-binfmt-P";
+          wrapper = pkgs.wrapQemuBinfmtP wrapperName interpreter;
+        in
+          if preserveArgvZero then "${wrapper}/bin/${wrapperName}"
+          else interpreter;
+      in {
+        inherit preserveArgvZero;
+
+        interpreter = interpreterReg;
+        wrapInterpreterInShell = !preserveArgvZero;
+        interpreterSandboxPath = dirOf (dirOf interpreterReg);
+      } // (magics.${system} or (throw "Cannot create binfmt registration for system ${system}"));
+    }) cfg.emulatedSystems);
+    nix.settings = lib.mkIf (cfg.emulatedSystems != []) {
+      extra-platforms = cfg.emulatedSystems ++ lib.optional pkgs.stdenv.hostPlatform.isx86_64 "i686-linux";
+      extra-sandbox-paths = let
+        ruleFor = system: cfg.registrations.${system};
+        hasWrappedRule = lib.any (system: (ruleFor system).wrapInterpreterInShell) cfg.emulatedSystems;
+      in [ "/run/binfmt" ]
+        ++ lib.optional hasWrappedRule "${pkgs.bash}"
+        ++ (map (system: (ruleFor system).interpreterSandboxPath) cfg.emulatedSystems);
+    };
+
+    environment.etc."binfmt.d/nixos.conf".source = builtins.toFile "binfmt_nixos.conf"
+      (lib.concatStringsSep "\n" (lib.mapAttrsToList makeBinfmtLine config.boot.binfmt.registrations));
+    system.activationScripts.binfmt = stringAfter [ "specialfs" ] ''
+      mkdir -p -m 0755 /run/binfmt
+      ${lib.concatStringsSep "\n" (lib.mapAttrsToList activationSnippet config.boot.binfmt.registrations)}
+    '';
+    systemd.additionalUpstreamSystemUnits = lib.mkIf (config.boot.binfmt.registrations != {}) [
+      "proc-sys-fs-binfmt_misc.automount"
+      "proc-sys-fs-binfmt_misc.mount"
+      "systemd-binfmt.service"
+    ];
+  };
+}
diff --git a/nixos/modules/system/boot/emergency-mode.nix b/nixos/modules/system/boot/emergency-mode.nix
new file mode 100644
index 00000000000..ec697bcee26
--- /dev/null
+++ b/nixos/modules/system/boot/emergency-mode.nix
@@ -0,0 +1,37 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+
+  ###### interface
+
+  options = {
+
+    systemd.enableEmergencyMode = mkOption {
+      default = true;
+      type = types.bool;
+      description = ''
+        Whether to enable emergency mode, which is an
+        <command>sulogin</command> shell started on the console if
+        mounting a filesystem fails.  Since some machines (like EC2
+        instances) have no console of any kind, emergency mode doesn't
+        make sense, and it's better to continue with the boot insofar
+        as possible.
+      '';
+    };
+
+  };
+
+  ###### implementation
+
+  config = {
+
+    systemd.additionalUpstreamSystemUnits = optionals
+      config.systemd.enableEmergencyMode [
+        "emergency.target" "emergency.service"
+      ];
+
+  };
+
+}
diff --git a/nixos/modules/system/boot/grow-partition.nix b/nixos/modules/system/boot/grow-partition.nix
new file mode 100644
index 00000000000..87c981b24ce
--- /dev/null
+++ b/nixos/modules/system/boot/grow-partition.nix
@@ -0,0 +1,53 @@
+# This module automatically grows the root partition.
+# This allows an instance to be created with a bigger root filesystem
+# than provided by the machine image.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  imports = [
+    (mkRenamedOptionModule [ "virtualisation" "growPartition" ] [ "boot" "growPartition" ])
+  ];
+
+  options = {
+    boot.growPartition = mkEnableOption "grow the root partition on boot";
+  };
+
+  config = mkIf config.boot.growPartition {
+
+    boot.initrd.extraUtilsCommands = ''
+      copy_bin_and_libs ${pkgs.gawk}/bin/gawk
+      copy_bin_and_libs ${pkgs.gnused}/bin/sed
+      copy_bin_and_libs ${pkgs.util-linux}/sbin/sfdisk
+      copy_bin_and_libs ${pkgs.util-linux}/sbin/lsblk
+
+      substitute "${pkgs.cloud-utils.guest}/bin/.growpart-wrapped" "$out/bin/growpart" \
+        --replace "${pkgs.bash}/bin/sh" "/bin/sh" \
+        --replace "awk" "gawk" \
+        --replace "sed" "gnused"
+
+      ln -s sed $out/bin/gnused
+    '';
+
+    boot.initrd.postDeviceCommands = ''
+      rootDevice="${config.fileSystems."/".device}"
+      if waitDevice "$rootDevice"; then
+        rootDevice="$(readlink -f "$rootDevice")"
+        parentDevice="$rootDevice"
+        while [ "''${parentDevice%[0-9]}" != "''${parentDevice}" ]; do
+          parentDevice="''${parentDevice%[0-9]}";
+        done
+        partNum="''${rootDevice#''${parentDevice}}"
+        if [ "''${parentDevice%[0-9]p}" != "''${parentDevice}" ] && [ -b "''${parentDevice%p}" ]; then
+          parentDevice="''${parentDevice%p}"
+        fi
+        TMPDIR=/run sh $(type -P growpart) "$parentDevice" "$partNum"
+        udevadm settle
+      fi
+    '';
+
+  };
+
+}
diff --git a/nixos/modules/system/boot/initrd-network.nix b/nixos/modules/system/boot/initrd-network.nix
new file mode 100644
index 00000000000..2a7417ed371
--- /dev/null
+++ b/nixos/modules/system/boot/initrd-network.nix
@@ -0,0 +1,148 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.boot.initrd.network;
+
+  dhcpInterfaces = lib.attrNames (lib.filterAttrs (iface: v: v.useDHCP == true) (config.networking.interfaces or {}));
+  doDhcp = config.networking.useDHCP || dhcpInterfaces != [];
+  dhcpIfShellExpr = if config.networking.useDHCP
+                      then "$(ls /sys/class/net/ | grep -v ^lo$)"
+                      else lib.concatMapStringsSep " " lib.escapeShellArg dhcpInterfaces;
+
+  udhcpcScript = pkgs.writeScript "udhcp-script"
+    ''
+      #! /bin/sh
+      if [ "$1" = bound ]; then
+        ip address add "$ip/$mask" dev "$interface"
+        if [ -n "$mtu" ]; then
+          ip link set mtu "$mtu" dev "$interface"
+        fi
+        if [ -n "$staticroutes" ]; then
+          echo "$staticroutes" \
+            | sed -r "s@(\S+) (\S+)@ ip route add \"\1\" via \"\2\" dev \"$interface\" ; @g" \
+            | sed -r "s@ via \"0\.0\.0\.0\"@@g" \
+            | /bin/sh
+        fi
+        if [ -n "$router" ]; then
+          ip route add "$router" dev "$interface" # just in case if "$router" is not within "$ip/$mask" (e.g. Hetzner Cloud)
+          ip route add default via "$router" dev "$interface"
+        fi
+        if [ -n "$dns" ]; then
+          rm -f /etc/resolv.conf
+          for server in $dns; do
+            echo "nameserver $server" >> /etc/resolv.conf
+          done
+        fi
+      fi
+    '';
+
+  udhcpcArgs = toString cfg.udhcpc.extraArgs;
+
+in
+
+{
+
+  options = {
+
+    boot.initrd.network.enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Add network connectivity support to initrd. The network may be
+        configured using the <literal>ip</literal> kernel parameter,
+        as described in <link
+        xlink:href="https://www.kernel.org/doc/Documentation/filesystems/nfs/nfsroot.txt">the
+        kernel documentation</link>.  Otherwise, if
+        <option>networking.useDHCP</option> is enabled, an IP address
+        is acquired using DHCP.
+
+        You should add the module(s) required for your network card to
+        boot.initrd.availableKernelModules.
+        <literal>lspci -v | grep -iA8 'network\|ethernet'</literal>
+        will tell you which.
+      '';
+    };
+
+    boot.initrd.network.flushBeforeStage2 = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether to clear the configuration of the interfaces that were set up in
+        the initrd right before stage 2 takes over. Stage 2 will do the regular network
+        configuration based on the NixOS networking options.
+      '';
+    };
+
+    boot.initrd.network.udhcpc.extraArgs = mkOption {
+      default = [];
+      type = types.listOf types.str;
+      description = ''
+        Additional command-line arguments passed verbatim to udhcpc if
+        <option>boot.initrd.network.enable</option> and <option>networking.useDHCP</option>
+        are enabled.
+      '';
+    };
+
+    boot.initrd.network.postCommands = mkOption {
+      default = "";
+      type = types.lines;
+      description = ''
+        Shell commands to be executed after stage 1 of the
+        boot has initialised the network.
+      '';
+    };
+
+
+  };
+
+  config = mkIf cfg.enable {
+
+    boot.initrd.kernelModules = [ "af_packet" ];
+
+    boot.initrd.extraUtilsCommands = ''
+      copy_bin_and_libs ${pkgs.klibc}/lib/klibc/bin.static/ipconfig
+    '';
+
+    boot.initrd.preLVMCommands = mkBefore (
+      # Search for interface definitions in command line.
+      ''
+        ifaces=""
+        for o in $(cat /proc/cmdline); do
+          case $o in
+            ip=*)
+              ipconfig $o && ifaces="$ifaces $(echo $o | cut -d: -f6)"
+              ;;
+          esac
+        done
+      ''
+
+      # Otherwise, use DHCP.
+      + optionalString doDhcp ''
+        # Bring up all interfaces.
+        for iface in ${dhcpIfShellExpr}; do
+          echo "bringing up network interface $iface..."
+          ip link set "$iface" up && ifaces="$ifaces $iface"
+        done
+
+        # Acquire DHCP leases.
+        for iface in ${dhcpIfShellExpr}; do
+          echo "acquiring IP address via DHCP on $iface..."
+          udhcpc --quit --now -i $iface -O staticroutes --script ${udhcpcScript} ${udhcpcArgs}
+        done
+      ''
+
+      + cfg.postCommands);
+
+    boot.initrd.postMountCommands = mkIf cfg.flushBeforeStage2 ''
+      for iface in $ifaces; do
+        ip address flush "$iface"
+        ip link set "$iface" down
+      done
+    '';
+
+  };
+
+}
diff --git a/nixos/modules/system/boot/initrd-openvpn.nix b/nixos/modules/system/boot/initrd-openvpn.nix
new file mode 100644
index 00000000000..9b52d4bbdb1
--- /dev/null
+++ b/nixos/modules/system/boot/initrd-openvpn.nix
@@ -0,0 +1,81 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.boot.initrd.network.openvpn;
+
+in
+
+{
+
+  options = {
+
+    boot.initrd.network.openvpn.enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Starts an OpenVPN client during initrd boot. It can be used to e.g.
+        remotely accessing the SSH service controlled by
+        <option>boot.initrd.network.ssh</option> or other network services
+        included. Service is killed when stage-1 boot is finished.
+      '';
+    };
+
+    boot.initrd.network.openvpn.configuration = mkOption {
+      type = types.path; # Same type as boot.initrd.secrets
+      description = ''
+        The configuration file for OpenVPN.
+
+        <warning>
+          <para>
+            Unless your bootloader supports initrd secrets, this configuration
+            is stored insecurely in the global Nix store.
+          </para>
+        </warning>
+      '';
+      example = literalExpression "./configuration.ovpn";
+    };
+
+  };
+
+  config = mkIf (config.boot.initrd.network.enable && cfg.enable) {
+    assertions = [
+      {
+        assertion = cfg.configuration != null;
+        message = "You should specify a configuration for initrd OpenVPN";
+      }
+    ];
+
+    # Add kernel modules needed for OpenVPN
+    boot.initrd.kernelModules = [ "tun" "tap" ];
+
+    # Add openvpn and ip binaries to the initrd
+    # The shared libraries are required for DNS resolution
+    boot.initrd.extraUtilsCommands = ''
+      copy_bin_and_libs ${pkgs.openvpn}/bin/openvpn
+      copy_bin_and_libs ${pkgs.iproute2}/bin/ip
+
+      cp -pv ${pkgs.glibc}/lib/libresolv.so.2 $out/lib
+      cp -pv ${pkgs.glibc}/lib/libnss_dns.so.2 $out/lib
+    '';
+
+    boot.initrd.secrets = {
+      "/etc/initrd.ovpn" = cfg.configuration;
+    };
+
+    # openvpn --version would exit with 1 instead of 0
+    boot.initrd.extraUtilsCommandsTest = ''
+      $out/bin/openvpn --show-gateway
+    '';
+
+    # Add `iproute /bin/ip` to the config, to ensure that openvpn
+    # is able to set the routes
+    boot.initrd.network.postCommands = ''
+      (cat /etc/initrd.ovpn; echo -e '\niproute /bin/ip') | \
+        openvpn /dev/stdin &
+    '';
+  };
+
+}
diff --git a/nixos/modules/system/boot/initrd-ssh.nix b/nixos/modules/system/boot/initrd-ssh.nix
new file mode 100644
index 00000000000..0999142de86
--- /dev/null
+++ b/nixos/modules/system/boot/initrd-ssh.nix
@@ -0,0 +1,215 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.boot.initrd.network.ssh;
+
+in
+
+{
+
+  options.boot.initrd.network.ssh = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Start SSH service during initrd boot. It can be used to debug failing
+        boot on a remote server, enter pasphrase for an encrypted partition etc.
+        Service is killed when stage-1 boot is finished.
+
+        The sshd configuration is largely inherited from
+        <option>services.openssh</option>.
+      '';
+    };
+
+    port = mkOption {
+      type = types.int;
+      default = 22;
+      description = ''
+        Port on which SSH initrd service should listen.
+      '';
+    };
+
+    shell = mkOption {
+      type = types.str;
+      default = "/bin/ash";
+      description = ''
+        Login shell of the remote user. Can be used to limit actions user can do.
+      '';
+    };
+
+    hostKeys = mkOption {
+      type = types.listOf (types.either types.str types.path);
+      default = [];
+      example = [
+        "/etc/secrets/initrd/ssh_host_rsa_key"
+        "/etc/secrets/initrd/ssh_host_ed25519_key"
+      ];
+      description = ''
+        Specify SSH host keys to import into the initrd.
+
+        To generate keys, use
+        <citerefentry><refentrytitle>ssh-keygen</refentrytitle><manvolnum>1</manvolnum></citerefentry>:
+
+        <screen>
+        <prompt># </prompt>ssh-keygen -t rsa -N "" -f /etc/secrets/initrd/ssh_host_rsa_key
+        <prompt># </prompt>ssh-keygen -t ed25519 -N "" -f /etc/secrets/initrd/ssh_host_ed25519_key
+        </screen>
+
+        <warning>
+          <para>
+            Unless your bootloader supports initrd secrets, these keys
+            are stored insecurely in the global Nix store. Do NOT use
+            your regular SSH host private keys for this purpose or
+            you'll expose them to regular users!
+          </para>
+          <para>
+            Additionally, even if your initrd supports secrets, if
+            you're using initrd SSH to unlock an encrypted disk then
+            using your regular host keys exposes the private keys on
+            your unencrypted boot partition.
+          </para>
+        </warning>
+      '';
+    };
+
+    authorizedKeys = mkOption {
+      type = types.listOf types.str;
+      default = config.users.users.root.openssh.authorizedKeys.keys;
+      defaultText = literalExpression "config.users.users.root.openssh.authorizedKeys.keys";
+      description = ''
+        Authorized keys for the root user on initrd.
+      '';
+    };
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = "Verbatim contents of <filename>sshd_config</filename>.";
+    };
+  };
+
+  imports =
+    map (opt: mkRemovedOptionModule ([ "boot" "initrd" "network" "ssh" ] ++ [ opt ]) ''
+      The initrd SSH functionality now uses OpenSSH rather than Dropbear.
+
+      If you want to keep your existing initrd SSH host keys, convert them with
+        $ dropbearconvert dropbear openssh dropbear_host_$type_key ssh_host_$type_key
+      and then set options.boot.initrd.network.ssh.hostKeys.
+    '') [ "hostRSAKey" "hostDSSKey" "hostECDSAKey" ];
+
+  config = let
+    # Nix complains if you include a store hash in initrd path names, so
+    # as an awful hack we drop the first character of the hash.
+    initrdKeyPath = path: if isString path
+      then path
+      else let name = builtins.baseNameOf path; in
+        builtins.unsafeDiscardStringContext ("/etc/ssh/" +
+          substring 1 (stringLength name) name);
+
+    sshdCfg = config.services.openssh;
+
+    sshdConfig = ''
+      Port ${toString cfg.port}
+
+      PasswordAuthentication no
+      ChallengeResponseAuthentication no
+
+      ${flip concatMapStrings cfg.hostKeys (path: ''
+        HostKey ${initrdKeyPath path}
+      '')}
+
+      KexAlgorithms ${concatStringsSep "," sshdCfg.kexAlgorithms}
+      Ciphers ${concatStringsSep "," sshdCfg.ciphers}
+      MACs ${concatStringsSep "," sshdCfg.macs}
+
+      LogLevel ${sshdCfg.logLevel}
+
+      ${if sshdCfg.useDns then ''
+        UseDNS yes
+      '' else ''
+        UseDNS no
+      ''}
+
+      ${cfg.extraConfig}
+    '';
+  in mkIf (config.boot.initrd.network.enable && cfg.enable) {
+    assertions = [
+      {
+        assertion = cfg.authorizedKeys != [];
+        message = "You should specify at least one authorized key for initrd SSH";
+      }
+
+      {
+        assertion = cfg.hostKeys != [];
+        message = ''
+          You must now pre-generate the host keys for initrd SSH.
+          See the boot.initrd.network.ssh.hostKeys documentation
+          for instructions.
+        '';
+      }
+    ];
+
+    boot.initrd.extraUtilsCommands = ''
+      copy_bin_and_libs ${pkgs.openssh}/bin/sshd
+      cp -pv ${pkgs.glibc.out}/lib/libnss_files.so.* $out/lib
+    '';
+
+    boot.initrd.extraUtilsCommandsTest = ''
+      # sshd requires a host key to check config, so we pass in the test's
+      tmpkey="$(mktemp initrd-ssh-testkey.XXXXXXXXXX)"
+      cp "${../../../tests/initrd-network-ssh/ssh_host_ed25519_key}" "$tmpkey"
+      # keys from Nix store are world-readable, which sshd doesn't like
+      chmod 600 "$tmpkey"
+      echo -n ${escapeShellArg sshdConfig} |
+        $out/bin/sshd -t -f /dev/stdin \
+        -h "$tmpkey"
+      rm "$tmpkey"
+    '';
+
+    boot.initrd.network.postCommands = ''
+      echo '${cfg.shell}' > /etc/shells
+      echo 'root:x:0:0:root:/root:${cfg.shell}' > /etc/passwd
+      echo 'sshd:x:1:1:sshd:/var/empty:/bin/nologin' >> /etc/passwd
+      echo 'passwd: files' > /etc/nsswitch.conf
+
+      mkdir -p /var/log /var/empty
+      touch /var/log/lastlog
+
+      mkdir -p /etc/ssh
+      echo -n ${escapeShellArg sshdConfig} > /etc/ssh/sshd_config
+
+      echo "export PATH=$PATH" >> /etc/profile
+      echo "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH" >> /etc/profile
+
+      mkdir -p /root/.ssh
+      ${concatStrings (map (key: ''
+        echo ${escapeShellArg key} >> /root/.ssh/authorized_keys
+      '') cfg.authorizedKeys)}
+
+      ${flip concatMapStrings cfg.hostKeys (path: ''
+        # keys from Nix store are world-readable, which sshd doesn't like
+        chmod 0600 "${initrdKeyPath path}"
+      '')}
+
+      /bin/sshd -e
+    '';
+
+    boot.initrd.postMountCommands = ''
+      # Stop sshd cleanly before stage 2.
+      #
+      # If you want to keep it around to debug post-mount SSH issues,
+      # run `touch /.keep_sshd` (either from an SSH session or in
+      # another initrd hook like preDeviceCommands).
+      if ! [ -e /.keep_sshd ]; then
+        pkill -x sshd
+      fi
+    '';
+
+    boot.initrd.secrets = listToAttrs
+      (map (path: nameValuePair (initrdKeyPath path) path) cfg.hostKeys);
+  };
+
+}
diff --git a/nixos/modules/system/boot/kernel.nix b/nixos/modules/system/boot/kernel.nix
new file mode 100644
index 00000000000..db00244ca0a
--- /dev/null
+++ b/nixos/modules/system/boot/kernel.nix
@@ -0,0 +1,350 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  inherit (config.boot) kernelPatches;
+  inherit (config.boot.kernel) features randstructSeed;
+  inherit (config.boot.kernelPackages) kernel;
+
+  kernelModulesConf = pkgs.writeText "nixos.conf"
+    ''
+      ${concatStringsSep "\n" config.boot.kernelModules}
+    '';
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    boot.kernel.features = mkOption {
+      default = {};
+      example = literalExpression "{ debug = true; }";
+      internal = true;
+      description = ''
+        This option allows to enable or disable certain kernel features.
+        It's not API, because it's about kernel feature sets, that
+        make sense for specific use cases. Mostly along with programs,
+        which would have separate nixos options.
+        `grep features pkgs/os-specific/linux/kernel/common-config.nix`
+      '';
+    };
+
+    boot.kernelPackages = mkOption {
+      default = pkgs.linuxPackages;
+      type = types.raw;
+      apply = kernelPackages: kernelPackages.extend (self: super: {
+        kernel = super.kernel.override (originalArgs: {
+          inherit randstructSeed;
+          kernelPatches = (originalArgs.kernelPatches or []) ++ kernelPatches;
+          features = lib.recursiveUpdate super.kernel.features features;
+        });
+      });
+      # We don't want to evaluate all of linuxPackages for the manual
+      # - some of it might not even evaluate correctly.
+      defaultText = literalExpression "pkgs.linuxPackages";
+      example = literalExpression "pkgs.linuxKernel.packages.linux_5_10";
+      description = ''
+        This option allows you to override the Linux kernel used by
+        NixOS.  Since things like external kernel module packages are
+        tied to the kernel you're using, it also overrides those.
+        This option is a function that takes Nixpkgs as an argument
+        (as a convenience), and returns an attribute set containing at
+        the very least an attribute <varname>kernel</varname>.
+        Additional attributes may be needed depending on your
+        configuration.  For instance, if you use the NVIDIA X driver,
+        then it also needs to contain an attribute
+        <varname>nvidia_x11</varname>.
+      '';
+    };
+
+    boot.kernelPatches = mkOption {
+      type = types.listOf types.attrs;
+      default = [];
+      example = literalExpression "[ pkgs.kernelPatches.ubuntu_fan_4_4 ]";
+      description = "A list of additional patches to apply to the kernel.";
+    };
+
+    boot.kernel.randstructSeed = mkOption {
+      type = types.str;
+      default = "";
+      example = "my secret seed";
+      description = ''
+        Provides a custom seed for the <varname>RANDSTRUCT</varname> security
+        option of the Linux kernel. Note that <varname>RANDSTRUCT</varname> is
+        only enabled in NixOS hardened kernels. Using a custom seed requires
+        building the kernel and dependent packages locally, since this
+        customization happens at build time.
+      '';
+    };
+
+    boot.kernelParams = mkOption {
+      type = types.listOf (types.strMatching ''([^"[:space:]]|"[^"]*")+'' // {
+        name = "kernelParam";
+        description = "string, with spaces inside double quotes";
+      });
+      default = [ ];
+      description = "Parameters added to the kernel command line.";
+    };
+
+    boot.consoleLogLevel = mkOption {
+      type = types.int;
+      default = 4;
+      description = ''
+        The kernel console <literal>loglevel</literal>. All Kernel Messages with a log level smaller
+        than this setting will be printed to the console.
+      '';
+    };
+
+    boot.vesa = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        (Deprecated) This option, if set, activates the VESA 800x600 video
+        mode on boot and disables kernel modesetting. It is equivalent to
+        specifying <literal>[ "vga=0x317" "nomodeset" ]</literal> in the
+        <option>boot.kernelParams</option> option. This option is
+        deprecated as of 2020: Xorg now works better with modesetting, and
+        you might want a different VESA vga setting, anyway.
+      '';
+    };
+
+    boot.extraModulePackages = mkOption {
+      type = types.listOf types.package;
+      default = [];
+      example = literalExpression "[ config.boot.kernelPackages.nvidia_x11 ]";
+      description = "A list of additional packages supplying kernel modules.";
+    };
+
+    boot.kernelModules = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = ''
+        The set of kernel modules to be loaded in the second stage of
+        the boot process.  Note that modules that are needed to
+        mount the root file system should be added to
+        <option>boot.initrd.availableKernelModules</option> or
+        <option>boot.initrd.kernelModules</option>.
+      '';
+    };
+
+    boot.initrd.availableKernelModules = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = [ "sata_nv" "ext3" ];
+      description = ''
+        The set of kernel modules in the initial ramdisk used during the
+        boot process.  This set must include all modules necessary for
+        mounting the root device.  That is, it should include modules
+        for the physical device (e.g., SCSI drivers) and for the file
+        system (e.g., ext3).  The set specified here is automatically
+        closed under the module dependency relation, i.e., all
+        dependencies of the modules list here are included
+        automatically.  The modules listed here are available in the
+        initrd, but are only loaded on demand (e.g., the ext3 module is
+        loaded automatically when an ext3 filesystem is mounted, and
+        modules for PCI devices are loaded when they match the PCI ID
+        of a device in your system).  To force a module to be loaded,
+        include it in <option>boot.initrd.kernelModules</option>.
+      '';
+    };
+
+    boot.initrd.kernelModules = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = "List of modules that are always loaded by the initrd.";
+    };
+
+    boot.initrd.includeDefaultModules = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        This option, if set, adds a collection of default kernel modules
+        to <option>boot.initrd.availableKernelModules</option> and
+        <option>boot.initrd.kernelModules</option>.
+      '';
+    };
+
+    system.modulesTree = mkOption {
+      type = types.listOf types.path;
+      internal = true;
+      default = [];
+      description = ''
+        Tree of kernel modules.  This includes the kernel, plus modules
+        built outside of the kernel.  Combine these into a single tree of
+        symlinks because modprobe only supports one directory.
+      '';
+      # Convert the list of path to only one path.
+      apply = pkgs.aggregateModules;
+    };
+
+    system.requiredKernelConfig = mkOption {
+      default = [];
+      example = literalExpression ''
+        with config.lib.kernelConfig; [
+          (isYes "MODULES")
+          (isEnabled "FB_CON_DECOR")
+          (isEnabled "BLK_DEV_INITRD")
+        ]
+      '';
+      internal = true;
+      type = types.listOf types.attrs;
+      description = ''
+        This option allows modules to specify the kernel config options that
+        must be set (or unset) for the module to work. Please use the
+        lib.kernelConfig functions to build list elements.
+      '';
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkMerge
+    [ (mkIf config.boot.initrd.enable {
+        boot.initrd.availableKernelModules =
+          optionals config.boot.initrd.includeDefaultModules ([
+            # Note: most of these (especially the SATA/PATA modules)
+            # shouldn't be included by default since nixos-generate-config
+            # detects them, but I'm keeping them for now for backwards
+            # compatibility.
+
+            # Some SATA/PATA stuff.
+            "ahci"
+            "sata_nv"
+            "sata_via"
+            "sata_sis"
+            "sata_uli"
+            "ata_piix"
+            "pata_marvell"
+
+            # Standard SCSI stuff.
+            "sd_mod"
+            "sr_mod"
+
+            # SD cards and internal eMMC drives.
+            "mmc_block"
+
+            # Support USB keyboards, in case the boot fails and we only have
+            # a USB keyboard, or for LUKS passphrase prompt.
+            "uhci_hcd"
+            "ehci_hcd"
+            "ehci_pci"
+            "ohci_hcd"
+            "ohci_pci"
+            "xhci_hcd"
+            "xhci_pci"
+            "usbhid"
+            "hid_generic" "hid_lenovo" "hid_apple" "hid_roccat"
+            "hid_logitech_hidpp" "hid_logitech_dj" "hid_microsoft"
+
+          ] ++ optionals pkgs.stdenv.hostPlatform.isx86 [
+            # Misc. x86 keyboard stuff.
+            "pcips2" "atkbd" "i8042"
+
+            # x86 RTC needed by the stage 2 init script.
+            "rtc_cmos"
+          ]);
+
+        boot.initrd.kernelModules =
+          optionals config.boot.initrd.includeDefaultModules [
+            # For LVM.
+            "dm_mod"
+          ];
+      })
+
+      (mkIf (!config.boot.isContainer) {
+        system.build = { inherit kernel; };
+
+        system.modulesTree = [ kernel ] ++ config.boot.extraModulePackages;
+
+        # Implement consoleLogLevel both in early boot and using sysctl
+        # (so you don't need to reboot to have changes take effect).
+        boot.kernelParams =
+          [ "loglevel=${toString config.boot.consoleLogLevel}" ] ++
+          optionals config.boot.vesa [ "vga=0x317" "nomodeset" ];
+
+        boot.kernel.sysctl."kernel.printk" = mkDefault config.boot.consoleLogLevel;
+
+        boot.kernelModules = [ "loop" "atkbd" ];
+
+        # The Linux kernel >= 2.6.27 provides firmware.
+        hardware.firmware = [ kernel ];
+
+        # Create /etc/modules-load.d/nixos.conf, which is read by
+        # systemd-modules-load.service to load required kernel modules.
+        environment.etc =
+          { "modules-load.d/nixos.conf".source = kernelModulesConf;
+          };
+
+        systemd.services.systemd-modules-load =
+          { wantedBy = [ "multi-user.target" ];
+            restartTriggers = [ kernelModulesConf ];
+            serviceConfig =
+              { # Ignore failed module loads.  Typically some of the
+                # modules in ‘boot.kernelModules’ are "nice to have but
+                # not required" (e.g. acpi-cpufreq), so we don't want to
+                # barf on those.
+                SuccessExitStatus = "0 1";
+              };
+          };
+
+        lib.kernelConfig = {
+          isYes = option: {
+            assertion = config: config.isYes option;
+            message = "CONFIG_${option} is not yes!";
+            configLine = "CONFIG_${option}=y";
+          };
+
+          isNo = option: {
+            assertion = config: config.isNo option;
+            message = "CONFIG_${option} is not no!";
+            configLine = "CONFIG_${option}=n";
+          };
+
+          isModule = option: {
+            assertion = config: config.isModule option;
+            message = "CONFIG_${option} is not built as a module!";
+            configLine = "CONFIG_${option}=m";
+          };
+
+          ### Usually you will just want to use these two
+          # True if yes or module
+          isEnabled = option: {
+            assertion = config: config.isEnabled option;
+            message = "CONFIG_${option} is not enabled!";
+            configLine = "CONFIG_${option}=y";
+          };
+
+          # True if no or omitted
+          isDisabled = option: {
+            assertion = config: config.isDisabled option;
+            message = "CONFIG_${option} is not disabled!";
+            configLine = "CONFIG_${option}=n";
+          };
+        };
+
+        # The config options that all modules can depend upon
+        system.requiredKernelConfig = with config.lib.kernelConfig;
+          [
+            # !!! Should this really be needed?
+            (isYes "MODULES")
+            (isYes "BINFMT_ELF")
+          ] ++ (optional (randstructSeed != "") (isYes "GCC_PLUGIN_RANDSTRUCT"));
+
+        # nixpkgs kernels are assumed to have all required features
+        assertions = if config.boot.kernelPackages.kernel ? features then [] else
+          let cfg = config.boot.kernelPackages.kernel.config; in map (attrs:
+            { assertion = attrs.assertion cfg; inherit (attrs) message; }
+          ) config.system.requiredKernelConfig;
+
+      })
+
+    ];
+
+}
diff --git a/nixos/modules/system/boot/kernel_config.nix b/nixos/modules/system/boot/kernel_config.nix
new file mode 100644
index 00000000000..495fe74bc21
--- /dev/null
+++ b/nixos/modules/system/boot/kernel_config.nix
@@ -0,0 +1,117 @@
+{ lib, config, ... }:
+
+with lib;
+let
+  mergeFalseByDefault = locs: defs:
+    if defs == [] then abort "This case should never happen."
+    else if any (x: x == false) (getValues defs) then false
+    else true;
+
+  kernelItem = types.submodule {
+    options = {
+      tristate = mkOption {
+        type = types.enum [ "y" "m" "n" null ];
+        default = null;
+        internal = true;
+        visible = true;
+        description = ''
+          Use this field for tristate kernel options expecting a "y" or "m" or "n".
+        '';
+      };
+
+      freeform = mkOption {
+        type = types.nullOr types.str // {
+          merge = mergeEqualOption;
+        };
+        default = null;
+        example = ''MMC_BLOCK_MINORS.freeform = "32";'';
+        description = ''
+          Freeform description of a kernel configuration item value.
+        '';
+      };
+
+      optional = mkOption {
+        type = types.bool // { merge = mergeFalseByDefault; };
+        default = false;
+        description = ''
+          Whether option should generate a failure when unused.
+          Upon merging values, mandatory wins over optional.
+        '';
+      };
+    };
+  };
+
+  mkValue = with lib; val:
+  let
+    isNumber = c: elem c ["0" "1" "2" "3" "4" "5" "6" "7" "8" "9"];
+
+  in
+    if (val == "") then "\"\""
+    else if val == "y" || val == "m" || val == "n" then val
+    else if all isNumber (stringToCharacters val) then val
+    else if substring 0 2 val == "0x" then val
+    else val; # FIXME: fix quoting one day
+
+
+  # generate nix intermediate kernel config file of the form
+  #
+  #       VIRTIO_MMIO m
+  #       VIRTIO_BLK y
+  #       VIRTIO_CONSOLE n
+  #       NET_9P_VIRTIO? y
+  #
+  # Borrowed from copumpkin https://github.com/NixOS/nixpkgs/pull/12158
+  # returns a string, expr should be an attribute set
+  # Use mkValuePreprocess to preprocess option values, aka mark 'modules' as 'yes' or vice-versa
+  # use the identity if you don't want to override the configured values
+  generateNixKConf = exprs:
+  let
+    mkConfigLine = key: item:
+      let
+        val = if item.freeform != null then item.freeform else item.tristate;
+      in
+        if val == null
+          then ""
+          else if (item.optional)
+            then "${key}? ${mkValue val}\n"
+            else "${key} ${mkValue val}\n";
+
+    mkConf = cfg: concatStrings (mapAttrsToList mkConfigLine cfg);
+  in mkConf exprs;
+
+in
+{
+
+  options = {
+
+    intermediateNixConfig = mkOption {
+      readOnly = true;
+      type = types.lines;
+      example = ''
+        USB? y
+        DEBUG n
+      '';
+      description = ''
+        The result of converting the structured kernel configuration in settings
+        to an intermediate string that can be parsed by generate-config.pl to
+        answer the kernel `make defconfig`.
+      '';
+    };
+
+    settings = mkOption {
+      type = types.attrsOf kernelItem;
+      example = literalExpression '' with lib.kernel; {
+        "9P_NET" = yes;
+        USB = option yes;
+        MMC_BLOCK_MINORS = freeform "32";
+      }'';
+      description = ''
+        Structured kernel configuration.
+      '';
+    };
+  };
+
+  config = {
+    intermediateNixConfig = generateNixKConf config.settings;
+  };
+}
diff --git a/nixos/modules/system/boot/kexec.nix b/nixos/modules/system/boot/kexec.nix
new file mode 100644
index 00000000000..02c2713ede1
--- /dev/null
+++ b/nixos/modules/system/boot/kexec.nix
@@ -0,0 +1,32 @@
+{ pkgs, lib, ... }:
+
+{
+  config = lib.mkIf (lib.meta.availableOn pkgs.stdenv.hostPlatform pkgs.kexec-tools) {
+    environment.systemPackages = [ pkgs.kexec-tools ];
+
+    systemd.services.prepare-kexec =
+      { description = "Preparation for kexec";
+        wantedBy = [ "kexec.target" ];
+        before = [ "systemd-kexec.service" ];
+        unitConfig.DefaultDependencies = false;
+        serviceConfig.Type = "oneshot";
+        path = [ pkgs.kexec-tools ];
+        script =
+          ''
+            # Don't load the current system profile if we already have a kernel loaded
+            if [[ 1 = "$(</sys/kernel/kexec_loaded)" ]] ; then
+              echo "kexec kernel has already been loaded, prepare-kexec skipped"
+              exit 0
+            fi
+
+            p=$(readlink -f /nix/var/nix/profiles/system)
+            if ! [[ -d $p ]]; then
+              echo "Could not find system profile for prepare-kexec"
+              exit 1
+            fi
+            echo "Loading NixOS system via kexec."
+            exec kexec --load $p/kernel --initrd=$p/initrd --append="$(cat $p/kernel-params) init=$p/init"
+          '';
+      };
+  };
+}
diff --git a/nixos/modules/system/boot/loader/efi.nix b/nixos/modules/system/boot/loader/efi.nix
new file mode 100644
index 00000000000..6043c904c45
--- /dev/null
+++ b/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/nixos/modules/system/boot/loader/generations-dir/generations-dir-builder.sh b/nixos/modules/system/boot/loader/generations-dir/generations-dir-builder.sh
new file mode 100644
index 00000000000..8ae23dc988c
--- /dev/null
+++ b/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/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix b/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix
new file mode 100644
index 00000000000..1437ab38770
--- /dev/null
+++ b/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix
@@ -0,0 +1,62 @@
+{ 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;
+  };
+
+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 = pkgs.stdenv.hostPlatform.linux-kernel.target;
+
+  };
+}
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..545b594674f
--- /dev/null
+++ b/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
@@ -0,0 +1,82 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  blCfg = config.boot.loader;
+  dtCfg = config.hardware.deviceTree;
+  cfg = blCfg.generic-extlinux-compatible;
+
+  timeoutStr = if blCfg.timeout == null then "-1" else toString blCfg.timeout;
+
+  # The builder used to write during system activation
+  builder = import ./extlinux-conf-builder.nix { inherit pkgs; };
+  # The builder exposed in populateCmd, which runs on the build architecture
+  populateBuilder = import ./extlinux-conf-builder.nix { pkgs = pkgs.buildPackages; };
+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.
+        '';
+      };
+
+      useGenerationDeviceTree = mkOption {
+        default = true;
+        type = types.bool;
+        description = ''
+          Whether to generate Device Tree-related directives in the
+          extlinux configuration.
+
+          When enabled, the bootloader will attempt to load the device
+          tree binaries from the generation's kernel.
+
+          Note that this affects all generations, regardless of the
+          setting value used in their configurations.
+        '';
+      };
+
+      configurationLimit = mkOption {
+        default = 20;
+        example = 10;
+        type = types.int;
+        description = ''
+          Maximum number of configurations in the boot menu.
+        '';
+      };
+
+      populateCmd = mkOption {
+        type = types.str;
+        readOnly = true;
+        description = ''
+          Contains the builder command used to populate an image,
+          honoring all options except the <literal>-c &lt;path-to-default-configuration&gt;</literal>
+          argument.
+          Useful to have for sdImage.populateRootCommands
+        '';
+      };
+
+    };
+  };
+
+  config = let
+    builderArgs = "-g ${toString cfg.configurationLimit} -t ${timeoutStr}"
+      + lib.optionalString (dtCfg.name != null) " -n ${dtCfg.name}"
+      + lib.optionalString (!cfg.useGenerationDeviceTree) " -r";
+  in
+    mkIf cfg.enable {
+      system.build.installBootLoader = "${builder} ${builderArgs} -c";
+      system.boot.loader.id = "generic-extlinux-compatible";
+
+      boot.loader.generic-extlinux-compatible.populateCmd = "${populateBuilder} ${builderArgs}";
+    };
+}
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..576a07c1d27
--- /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;
+  path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
+  inherit (pkgs) bash;
+}
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..1a0da005029
--- /dev/null
+++ b/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh
@@ -0,0 +1,157 @@
+#! @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>] [-n <dtbName>] [-r]" >&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:n:r" 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" ;;
+        n) dtbName="$OPTARG" ;;
+        r) noDeviceTree=1 ;;
+        \?) 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
+    dtbDir=$(readlink -m "$path/dtbs")
+    if [ -e "$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)"
+    echo "  APPEND init=$path/init $extraParams"
+
+    if [ -n "$noDeviceTree" ]; then
+        return
+    fi
+
+    if [ -d "$dtbDir" ]; then
+        # if a dtbName was specified explicitly, use that, else use FDTDIR
+        if [ -n "$dtbName" ]; then
+            echo "  FDT ../nixos/$(basename $dtbs)/${dtbName}"
+        else
+            echo "  FDTDIR ../nixos/$(basename $dtbs)"
+        fi
+    else
+        if [ -n "$dtbName" ]; then
+            echo "Explicitly requested dtbName $dtbName, but there's no FDTDIR - bailing out." >&2
+            exit 1
+        fi
+    fi
+}
+
+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/nixos/modules/system/boot/loader/grub/grub.nix b/nixos/modules/system/boot/loader/grub/grub.nix
new file mode 100644
index 00000000000..8db271f8713
--- /dev/null
+++ b/nixos/modules/system/boot/loader/grub/grub.nix
@@ -0,0 +1,848 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.boot.loader.grub;
+
+  efi = config.boot.loader.efi;
+
+  grubPkgs =
+    # Package set of targeted architecture
+    if cfg.forcei686 then pkgs.pkgsi686Linux else pkgs;
+
+  realGrub = if cfg.version == 1 then grubPkgs.grub
+    else if cfg.zfsSupport then grubPkgs.grub2.override { zfsSupport = true; }
+    else if cfg.trustedBoot.enable
+         then if cfg.trustedBoot.isHPLaptop
+              then grubPkgs.trustedGrub-for-HP
+              else grubPkgs.trustedGrub
+         else grubPkgs.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 = lib.getName realGrub;
+      fullVersion = lib.getVersion realGrub;
+      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;
+      users = if cfg.users == {} || cfg.version != 1 then cfg.users else throw "GRUB version 1 does not support user accounts.";
+      theme = f cfg.theme;
+      inherit efiSysMountPoint;
+      inherit (args) devices;
+      inherit (efi) canTouchEfiVariables;
+      inherit (cfg)
+        version extraConfig extraPerEntryConfig extraEntries forceInstall useOSProber
+        extraGrubInstallArgs
+        extraEntriesBeforeNixOS extraPrepareConfig configurationLimit copyKernels
+        default fsIdentifier efiSupport efiInstallAsRemovable gfxmodeEfi gfxmodeBios gfxpayloadEfi gfxpayloadBios;
+      path = with pkgs; makeBinPath (
+        [ coreutils gnused gnugrep findutils diffutils btrfs-progs util-linux mdadm ]
+        ++ optional (cfg.efiSupport && (cfg.version == 2)) efibootmgr
+        ++ optionals cfg.useOSProber [ busybox os-prober ]);
+      font = if cfg.font == null then ""
+        else (if lib.last (lib.splitString "." cfg.font) == "pf2"
+             then cfg.font
+             else "${convertedFont}");
+    });
+
+  bootDeviceCounters = foldr (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.gnomeFilePath;
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    boot.loader.grub = {
+
+      enable = mkOption {
+        default = !config.boot.isContainer;
+        defaultText = literalExpression "!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.
+        '';
+      };
+
+      users = mkOption {
+        default = {};
+        example = {
+          root = { hashedPasswordFile = "/path/to/file"; };
+        };
+        description = ''
+          User accounts for GRUB. When specified, the GRUB command line and
+          all boot options except the default are password-protected.
+          All passwords and hashes provided will be stored in /boot/grub/grub.cfg,
+          and will be visible to any local user who can read this file. Additionally,
+          any passwords and hashes provided directly in a Nix configuration
+          (as opposed to external files) will be copied into the Nix store, and
+          will be visible to all local users.
+        '';
+        type = with types; attrsOf (submodule {
+          options = {
+            hashedPasswordFile = mkOption {
+              example = "/path/to/file";
+              default = null;
+              type = with types; uniq (nullOr str);
+              description = ''
+                Specifies the path to a file containing the password hash
+                for the account, generated with grub-mkpasswd-pbkdf2.
+                This hash will be stored in /boot/grub/grub.cfg, and will
+                be visible to any local user who can read this file.
+              '';
+            };
+            hashedPassword = mkOption {
+              example = "grub.pbkdf2.sha512.10000.674DFFDEF76E13EA...2CC972B102CF4355";
+              default = null;
+              type = with types; uniq (nullOr str);
+              description = ''
+                Specifies the password hash for the account,
+                generated with grub-mkpasswd-pbkdf2.
+                This hash will be copied to the Nix store, and will be visible to all local users.
+              '';
+            };
+            passwordFile = mkOption {
+              example = "/path/to/file";
+              default = null;
+              type = with types; uniq (nullOr str);
+              description = ''
+                Specifies the path to a file containing the
+                clear text password for the account.
+                This password will be stored in /boot/grub/grub.cfg, and will
+                be visible to any local user who can read this file.
+              '';
+            };
+            password = mkOption {
+              example = "Pa$$w0rd!";
+              default = null;
+              type = with types; uniq (nullOr str);
+              description = ''
+                Specifies the clear text password for the account.
+                This password will be copied to the Nix store, and will be visible to all local users.
+              '';
+            };
+          };
+        });
+      };
+
+      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 --unit=0 --speed=115200 --word=8 --parity=no --stop=1
+          terminal_input --append serial
+          terminal_output --append serial
+        '';
+        type = types.lines;
+        description = ''
+          Additional GRUB commands inserted in the configuration file
+          just before the menu entries.
+        '';
+      };
+
+      extraGrubInstallArgs = mkOption {
+        default = [ ];
+        example = [ "--modules=nativedisk ahci pata part_gpt part_msdos diskfilter mdraid1x lvm ext2" ];
+        type = types.listOf types.str;
+        description = ''
+          Additional arguments passed to <literal>grub-install</literal>.
+
+          A use case for this is to build specific GRUB2 modules
+          directly into the GRUB2 kernel image, so that they are available
+          and activated even in the <literal>grub rescue</literal> shell.
+
+          They are also necessary when the BIOS/UEFI is bugged and cannot
+          correctly read large disks (e.g. above 2 TB), so GRUB2's own
+          <literal>nativedisk</literal> and related modules can be used
+          to use its own disk drivers. The example shows one such case.
+          This is also useful for booting from USB.
+          See the
+          <link xlink:href="http://git.savannah.gnu.org/cgit/grub.git/tree/grub-core/commands/nativedisk.c?h=grub-2.04#n326">
+          GRUB source code
+          </link>
+          for which disk modules are available.
+
+          The list elements are passed directly as <literal>argv</literal>
+          arguments to the <literal>grub-install</literal> program, in order.
+        '';
+      };
+
+      extraInstallCommands = mkOption {
+        default = "";
+        example = ''
+          # the example below generates detached signatures that GRUB can verify
+          # https://www.gnu.org/software/grub/manual/grub/grub.html#Using-digital-signatures
+          ''${pkgs.findutils}/bin/find /boot -not -path "/boot/efi/*" -type f -name '*.sig' -delete
+          old_gpg_home=$GNUPGHOME
+          export GNUPGHOME="$(mktemp -d)"
+          ''${pkgs.gnupg}/bin/gpg --import ''${priv_key} > /dev/null 2>&1
+          ''${pkgs.findutils}/bin/find /boot -not -path "/boot/efi/*" -type f -exec ''${pkgs.gnupg}/bin/gpg --detach-sign "{}" \; > /dev/null 2>&1
+          rm -rf $GNUPGHOME
+          export GNUPGHOME=$old_gpg_home
+        '';
+        type = types.lines;
+        description = ''
+          Additional shell commands inserted in the bootloader installer
+          script after generating 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 = literalExpression ''
+          { "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.
+        '';
+      };
+
+      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 = literalExpression "./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.str;
+        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>
+        '';
+      };
+
+      theme = mkOption {
+        type = types.nullOr types.path;
+        example = literalExpression "pkgs.nixos-grub2-theme";
+        default = null;
+        description = ''
+          Grub theme to be used.
+
+          <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";
+        defaultText = literalExpression ''"''${pkgs.grub2}/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 = 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.
+        '';
+      };
+
+      gfxpayloadEfi = mkOption {
+        default = "keep";
+        example = "text";
+        type = types.str;
+        description = ''
+          The gfxpayload to pass to GRUB when loading a graphical boot interface under EFI.
+        '';
+      };
+
+      gfxpayloadBios = mkOption {
+        default = "text";
+        example = "keep";
+        type = types.str;
+        description = ''
+          The gfxpayload 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.
+          Can also be set to "saved", which will make GRUB select
+          the menu item that was used at the last boot.
+        '';
+      };
+
+      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.
+        '';
+      };
+
+      forcei686 = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Whether to force the use of a ia32 boot loader on x64 systems. Required
+          to install and run NixOS on 64bit x86 systems with 32bit (U)EFI.
+        '';
+      };
+
+      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.str;
+          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; }
+      ];
+
+      boot.loader.supportsInitrdSecrets = true;
+
+      system.build.installBootLoader =
+        let
+          install-grub-pl = pkgs.substituteAll {
+            src = ./install-grub.pl;
+            utillinux = pkgs.util-linux;
+            btrfsprogs = pkgs.btrfs-progs;
+          };
+          perl = pkgs.perl.withPackages (p: with p; [
+            FileSlurp FileCopyRecursive
+            XMLLibXML XMLSAX XMLSAXBase
+            ListCompare JSON
+          ]);
+        in pkgs.writeScript "install-grub.sh" (''
+        #!${pkgs.runtimeShell}
+        set -e
+        ${optionalString cfg.enableCryptodisk "export GRUB_ENABLE_CRYPTODISK=y"}
+      '' + flip concatMapStrings cfg.mirroredBoots (args: ''
+        ${perl}/bin/perl ${install-grub-pl} ${grubConfig args} $@
+      '') + cfg.extraInstallCommands);
+
+      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}" "@bootPath@/${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 (n: c: if n == "nodev" then 0 else 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}";
+        }
+      ] ++ forEach 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" ])
+      (mkRemovedOptionModule [ "boot" "loader" "grub" "extraInitrd" ] ''
+        This option has been replaced with the bootloader agnostic
+        boot.initrd.secrets option. To migrate to the initrd secrets system,
+        extract the extraInitrd archive into your main filesystem:
+
+          # zcat /boot/extra_initramfs.gz | cpio -idvmD /etc/secrets/initrd
+          /path/to/secret1
+          /path/to/secret2
+
+        then replace boot.loader.grub.extraInitrd with boot.initrd.secrets:
+
+          boot.initrd.secrets = {
+            "/path/to/secret1" = "/etc/secrets/initrd/path/to/secret1";
+            "/path/to/secret2" = "/etc/secrets/initrd/path/to/secret2";
+          };
+
+        See the boot.initrd.secrets option documentation for more information.
+      '')
+    ];
+
+}
diff --git a/nixos/modules/system/boot/loader/grub/install-grub.pl b/nixos/modules/system/boot/loader/grub/install-grub.pl
new file mode 100644
index 00000000000..0c93b288fc6
--- /dev/null
+++ b/nixos/modules/system/boot/loader/grub/install-grub.pl
@@ -0,0 +1,780 @@
+use strict;
+use warnings;
+use Class::Struct;
+use XML::LibXML;
+use File::Basename;
+use File::Path;
+use File::stat;
+use File::Copy;
+use File::Copy::Recursive qw(rcopy pathrm);
+use File::Slurp;
+use File::Temp;
+use JSON;
+use File::Find;
+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 getList {
+    my ($name) = @_;
+    my @list = ();
+    foreach my $entry ($dom->findnodes("/expr/attrs/attr[\@name = '$name']/list/string/\@value")) {
+        $entry = $entry->findvalue(".") or die;
+        push(@list, $entry);
+    }
+    return @list;
+}
+
+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 $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 $gfxpayloadEfi = get("gfxpayloadEfi");
+my $gfxpayloadBios = get("gfxpayloadBios");
+my $bootloaderId = get("bootloaderId");
+my $forceInstall = get("forceInstall");
+my $font = get("font");
+my $theme = get("theme");
+my $saveDefault = $defaultEntry eq "saved";
+$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);
+}
+
+# Generate the header.
+my $conf .= "# Automatically generated.  DO NOT EDIT THIS FILE!\n";
+
+if ($grubVersion == 1) {
+    # $defaultEntry might be "saved", indicating that we want to use the last selected configuration as default.
+    # Incidentally this is already the correct value for the grub 1 config to achieve this behaviour.
+    $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 {
+    my @users = ();
+    foreach my $user ($dom->findnodes('/expr/attrs/attr[@name = "users"]/attrs/attr')) {
+        my $name = $user->findvalue('@name') or die;
+        my $hashedPassword = $user->findvalue('./attrs/attr[@name = "hashedPassword"]/string/@value');
+        my $hashedPasswordFile = $user->findvalue('./attrs/attr[@name = "hashedPasswordFile"]/string/@value');
+        my $password = $user->findvalue('./attrs/attr[@name = "password"]/string/@value');
+        my $passwordFile = $user->findvalue('./attrs/attr[@name = "passwordFile"]/string/@value');
+
+        if ($hashedPasswordFile) {
+            open(my $f, '<', $hashedPasswordFile) or die "Can't read file '$hashedPasswordFile'!";
+            $hashedPassword = <$f>;
+            chomp $hashedPassword;
+        }
+        if ($passwordFile) {
+            open(my $f, '<', $passwordFile) or die "Can't read file '$passwordFile'!";
+            $password = <$f>;
+            chomp $password;
+        }
+
+        if ($hashedPassword) {
+            if (index($hashedPassword, "grub.pbkdf2.") == 0) {
+                $conf .= "\npassword_pbkdf2 $name $hashedPassword";
+            }
+            else {
+                die "Password hash for GRUB user '$name' is not valid!";
+            }
+        }
+        elsif ($password) {
+            $conf .= "\npassword $name $password";
+        }
+        else {
+            die "GRUB user '$name' has no password!";
+        }
+        push(@users, $name);
+    }
+    if (@users) {
+        $conf .= "\nset superusers=\"" . join(' ',@users) . "\"\n";
+    }
+
+    if ($copyKernels == 0) {
+        $conf .= "
+            " . $grubStore->search;
+    }
+    # FIXME: should use grub-mkconfig.
+    my $defaultEntryText = $defaultEntry;
+    if ($saveDefault) {
+        $defaultEntryText = "\"\${saved_entry}\"";
+    }
+    $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
+          set boot_once=true
+        else
+          set default=$defaultEntryText
+          set timeout=$timeout
+        fi
+
+        function savedefault {
+            if [ -z \"\${boot_once}\"]; then
+            saved_entry=\"\${chosen}\"
+            save_env saved_entry
+            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=$gfxpayloadEfi
+              else
+                set gfxmode=$gfxmodeBios
+                set gfxpayload=$gfxpayloadBios
+              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
+        ";
+    }
+
+    rmtree("$bootPath/theme") or die "cannot clean up theme folder in $bootPath\n" if -e "$bootPath/theme";
+
+    if ($theme) {
+        # Copy theme
+        rcopy($theme, "$bootPath/theme") or die "cannot copy $theme to $bootPath\n";
+        $conf .= "
+            # Sets theme.
+            set theme=" . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/theme/theme.txt
+            export theme
+            # Load theme fonts, if any
+        ";
+
+        find( { wanted => sub {
+            if ($_ =~ /\.pf2$/i) {
+                $font = File::Spec->abs2rel($File::Find::name, $theme);
+                $conf .= "
+                    loadfont " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/theme/$font
+                ";
+            }
+        }, no_chdir => 1 }, $theme );
+    }
+}
+
+$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, $options) = @_;
+    return unless -e "$path/kernel" && -e "$path/initrd";
+
+    my $kernel = copyToKernelsDir(Cwd::abs_path("$path/kernel"));
+    my $initrd = copyToKernelsDir(Cwd::abs_path("$path/initrd"));
+
+    # Include second initrd with secrets
+    if (-e -x "$path/append-initrd-secrets") {
+        my $initrdName = basename($initrd);
+        my $initrdSecretsPath = "$bootPath/kernels/$initrdName-secrets";
+
+        mkpath(dirname($initrdSecretsPath), 0, 0755);
+        my $oldUmask = umask;
+        # Make sure initrd is not world readable (won't work if /boot is FAT)
+        umask 0137;
+        my $initrdSecretsPathTemp = File::Temp::mktemp("$initrdSecretsPath.XXXXXXXX");
+        system("$path/append-initrd-secrets", $initrdSecretsPathTemp) == 0 or die "failed to create initrd secrets: $!\n";
+        # Check whether any secrets were actually added
+        if (-e $initrdSecretsPathTemp && ! -z _) {
+            rename $initrdSecretsPathTemp, $initrdSecretsPath or die "failed to move initrd secrets into place: $!\n";
+            $copied{$initrdSecretsPath} = 1;
+            $initrd .= " " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/kernels/$initrdName-secrets";
+        } else {
+            unlink $initrdSecretsPathTemp;
+            rmdir dirname($initrdSecretsPathTemp);
+        }
+        umask $oldUmask;
+    }
+
+    my $xen = -e "$path/xen.gz" ? copyToKernelsDir(Cwd::abs_path("$path/xen.gz")) : undef;
+
+    # FIXME: $confName
+
+    my $kernelParams =
+        "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";
+        if ($saveDefault) {
+            $conf .= "  savedefault\n";
+        }
+        $conf .= "\n";
+    } else {
+        $conf .= "menuentry \"$name\" " . ($options||"") . " {\n";
+        if ($saveDefault) {
+            $conf .= "  savedefault\n";
+        }
+        $conf .= $grubBoot->search . "\n";
+        if ($copyKernels == 0) {
+            $conf .= $grubStore->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, "--unrestricted");
+
+$conf .= "$extraEntries\n" unless $extraEntriesBeforeNixOS;
+
+# Find all the children of the current default configuration
+# Do not search for grand children
+my @links = sort (glob "$defaultConfig/specialisation/*");
+foreach my $link (@links) {
+
+    my $entryName = "";
+
+    my $cfgName = readFile("$link/configuration-name");
+
+    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]);
+
+    if ($cfgName) {
+        $entryName = $cfgName;
+    } else {
+        my $linkname = basename($link);
+        $entryName = "($linkname - $date - $version)";
+    }
+    addEntry("NixOS - $entryName", $link);
+}
+
+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'";
+    }
+}
+
+# extraPrepareConfig could refer to @bootPath@, which we have to substitute
+$extraPrepareConfig =~ s/\@bootPath\@/$bootPath/g;
+
+# 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") {
+    if ($saveDefault) {
+        # os-prober will read this to determine if "savedefault" should be added to generated entries
+        $ENV{'GRUB_SAVEDEFAULT'} = "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 => '$',
+    extraGrubInstallArgs => '@',
+});
+# If you add something to the state file, only add it to the end
+# because it is read line-by-line.
+sub readGrubState {
+    my $defaultGrubState = GrubState->new(name => "", version => "", efi => "", devices => "", efiMountPoint => "", extraGrubInstallArgs => () );
+    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);
+    # Historically, arguments in the state file were one per each line, but that
+    # gets really messy when newlines are involved, structured arguments
+    # like lists are needed (they have to have a separator encoding), or even worse,
+    # when we need to remove a setting in the future. Thus, the 6th line is a JSON
+    # object that can store structured data, with named keys, and all new state
+    # should go in there.
+    my $jsonStateLine = <FILE>;
+    # For historical reasons we do not check the values above for un-definedness
+    # (that is, when the state file has too few lines and EOF is reached),
+    # because the above come from the first version of this logic and are thus
+    # guaranteed to be present.
+    $jsonStateLine = defined $jsonStateLine ? $jsonStateLine : '{}'; # empty JSON object
+    chomp($jsonStateLine);
+    if ($jsonStateLine eq "") {
+        $jsonStateLine = '{}'; # empty JSON object
+    }
+    my %jsonState = %{decode_json($jsonStateLine)};
+    my @extraGrubInstallArgs = exists($jsonState{'extraGrubInstallArgs'}) ? @{$jsonState{'extraGrubInstallArgs'}} : ();
+    close FILE;
+    my $grubState = GrubState->new(name => $name, version => $version, efi => $efi, devices => $devices, efiMountPoint => $efiMountPoint, extraGrubInstallArgs => \@extraGrubInstallArgs );
+    return $grubState
+}
+
+my @deviceTargets = getList('devices');
+my $prevGrubState = readGrubState();
+my @prevDeviceTargets = split/,/, $prevGrubState->devices;
+my @extraGrubInstallArgs = getList('extraGrubInstallArgs');
+my @prevExtraGrubInstallArgs = @{$prevGrubState->extraGrubInstallArgs};
+
+my $devicesDiffer = scalar (List::Compare->new( '-u', '-a', \@deviceTargets, \@prevDeviceTargets)->get_symmetric_difference());
+my $extraGrubInstallArgsDiffer = scalar (List::Compare->new( '-u', '-a', \@extraGrubInstallArgs, \@prevExtraGrubInstallArgs)->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 || $extraGrubInstallArgsDiffer || $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), @extraGrubInstallArgs);
+        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", @extraGrubInstallArgs);
+    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) {
+    # Temp file for atomic rename.
+    my $stateFile = "$bootPath/grub/state";
+    my $stateFileTmp = $stateFile . ".tmp";
+
+    open FILE, ">$stateFileTmp" or die "cannot create $stateFileTmp: $!\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;
+    my %jsonState = (
+        extraGrubInstallArgs => \@extraGrubInstallArgs
+    );
+    my $jsonStateLine = encode_json(\%jsonState);
+    print FILE $jsonStateLine, "\n" or die;
+    close FILE or die;
+
+    # Atomically switch to the new state file
+    rename $stateFileTmp, $stateFile or die "cannot rename $stateFileTmp to $stateFile: $!\n";
+}
diff --git a/nixos/modules/system/boot/loader/grub/ipxe.nix b/nixos/modules/system/boot/loader/grub/ipxe.nix
new file mode 100644
index 00000000000..ef8595592f4
--- /dev/null
+++ b/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 = literalExpression ''
+          { 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/nixos/modules/system/boot/loader/grub/memtest.nix b/nixos/modules/system/boot/loader/grub/memtest.nix
new file mode 100644
index 00000000000..71e50dd0577
--- /dev/null
+++ b/nixos/modules/system/boot/loader/grub/memtest.nix
@@ -0,0 +1,116 @@
+# This module adds Memtest86+/Memtest86 to the GRUB boot menu.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  memtest86 = pkgs.memtest86plus;
+  efiSupport = config.boot.loader.grub.efiSupport;
+  cfg = config.boot.loader.grub.memtest86;
+in
+
+{
+  options = {
+
+    boot.loader.grub.memtest86 = {
+
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Make Memtest86+ (or MemTest86 if EFI support is enabled),
+          a memory testing program, available from the
+          GRUB boot menu. MemTest86 is an unfree program, so
+          this requires <literal>allowUnfree</literal> to be set to
+          <literal>true</literal>.
+        '';
+      };
+
+      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 = mkMerge [
+    (mkIf (cfg.enable && efiSupport) {
+      assertions = [
+        {
+          assertion = cfg.params == [];
+          message = "Parameters are not available for MemTest86";
+        }
+      ];
+
+      boot.loader.grub.extraFiles = {
+        "memtest86.efi" = "${pkgs.memtest86-efi}/BOOTX64.efi";
+      };
+
+      boot.loader.grub.extraEntries = ''
+        menuentry "Memtest86" {
+          chainloader /memtest86.efi
+        }
+      '';
+    })
+
+    (mkIf (cfg.enable && !efiSupport) {
+      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/nixos/modules/system/boot/loader/init-script/init-script-builder.sh b/nixos/modules/system/boot/loader/init-script/init-script-builder.sh
new file mode 100644
index 00000000000..bd3fc64999d
--- /dev/null
+++ b/nixos/modules/system/boot/loader/init-script/init-script-builder.sh
@@ -0,0 +1,92 @@
+#! @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 "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/specialisation/* ) | 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/nixos/modules/system/boot/loader/init-script/init-script.nix b/nixos/modules/system/boot/loader/init-script/init-script.nix
new file mode 100644
index 00000000000..374d9524ff1
--- /dev/null
+++ b/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/nixos/modules/system/boot/loader/loader.nix b/nixos/modules/system/boot/loader/loader.nix
new file mode 100644
index 00000000000..01475f79b9c
--- /dev/null
+++ b/nixos/modules/system/boot/loader/loader.nix
@@ -0,0 +1,20 @@
+{ lib, ... }:
+
+with lib;
+
+{
+  imports = [
+    (mkRenamedOptionModule [ "boot" "loader" "grub" "timeout" ] [ "boot" "loader" "timeout" ])
+    (mkRenamedOptionModule [ "boot" "loader" "gummiboot" "timeout" ] [ "boot" "loader" "timeout" ])
+  ];
+
+    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.
+            '';
+        };
+    };
+}
diff --git a/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.nix b/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.nix
new file mode 100644
index 00000000000..64e106036ab
--- /dev/null
+++ b/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.nix
@@ -0,0 +1,9 @@
+{ pkgs, configTxt, firmware ? pkgs.raspberrypifw }:
+
+pkgs.substituteAll {
+  src = ./raspberrypi-builder.sh;
+  isExecutable = true;
+  inherit (pkgs) bash;
+  path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
+  inherit firmware configTxt;
+}
diff --git a/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.sh b/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.sh
new file mode 100644
index 00000000000..0541ca1ba62
--- /dev/null
+++ b/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.sh
@@ -0,0 +1,143 @@
+#! @bash@/bin/sh
+
+# This can end up being called disregarding the shebang.
+set -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/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/fixup4.dat    $target/fixup4.dat
+copyForced $fwdir/fixup4cd.dat  $target/fixup4cd.dat
+copyForced $fwdir/fixup4db.dat  $target/fixup4db.dat
+copyForced $fwdir/fixup4x.dat   $target/fixup4x.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/start4.elf    $target/start4.elf
+copyForced $fwdir/start4cd.elf  $target/start4cd.elf
+copyForced $fwdir/start4db.elf  $target/start4db.elf
+copyForced $fwdir/start4x.elf   $target/start4x.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/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix b/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix
new file mode 100644
index 00000000000..1023361f0b1
--- /dev/null
+++ b/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix
@@ -0,0 +1,105 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.boot.loader.raspberryPi;
+
+  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.hostPlatform.isAarch64;
+  optional = pkgs.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_64bit=1
+    '' + (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 4 ];
+        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.lines;
+        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.hostPlatform.isAarch64 || cfg.version >= 3;
+      message = "Only Raspberry Pi >= 3 supports aarch64.";
+    };
+
+    system.build.installBootLoader = builder;
+    system.boot.loader.id = "raspberrypi";
+    system.boot.loader.kernelFile = pkgs.stdenv.hostPlatform.linux-kernel.target;
+  };
+}
diff --git a/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.nix b/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.nix
new file mode 100644
index 00000000000..a4352ab9a24
--- /dev/null
+++ b/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.nix
@@ -0,0 +1,37 @@
+{ pkgs, version, configTxt }:
+
+let
+  isAarch64 = pkgs.stdenv.hostPlatform.isAarch64;
+
+  uboot =
+    if version == 0 then
+      pkgs.ubootRaspberryPiZero
+    else if version == 1 then
+      pkgs.ubootRaspberryPi
+    else if version == 2 then
+      pkgs.ubootRaspberryPi2
+    else if version == 3 then
+      if isAarch64 then
+        pkgs.ubootRaspberryPi3_64bit
+      else
+        pkgs.ubootRaspberryPi3_32bit
+    else
+      throw "U-Boot is not yet supported on the raspberry pi 4.";
+
+  extlinuxConfBuilder =
+    import ../generic-extlinux-compatible/extlinux-conf-builder.nix {
+      pkgs = pkgs.buildPackages;
+    };
+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/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.sh b/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.sh
new file mode 100644
index 00000000000..ea591427179
--- /dev/null
+++ b/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/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
new file mode 100644
index 00000000000..fa879437fd8
--- /dev/null
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
@@ -0,0 +1,316 @@
+#! @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
+from typing import NamedTuple, List, Optional
+
+class SystemIdentifier(NamedTuple):
+    profile: Optional[str]
+    generation: int
+    specialisation: Optional[str]
+
+
+def copy_if_not_exists(source: str, dest: str) -> None:
+    if not os.path.exists(dest):
+        shutil.copyfile(source, dest)
+
+
+def generation_dir(profile: Optional[str], generation: int) -> str:
+    if profile:
+        return "/nix/var/nix/profiles/system-profiles/%s-%d-link" % (profile, generation)
+    else:
+        return "/nix/var/nix/profiles/system-%d-link" % (generation)
+
+def system_dir(profile: Optional[str], generation: int, specialisation: Optional[str]) -> str:
+    d = generation_dir(profile, generation)
+    if specialisation:
+        return os.path.join(d, "specialisation", specialisation)
+    else:
+        return d
+
+BOOT_ENTRY = """title NixOS{profile}{specialisation}
+version Generation {generation} {description}
+linux {kernel}
+initrd {initrd}
+options {kernel_params}
+"""
+
+def generation_conf_filename(profile: Optional[str], generation: int, specialisation: Optional[str]) -> str:
+    pieces = [
+        "nixos",
+        profile or None,
+        "generation",
+        str(generation),
+        f"specialisation-{specialisation}" if specialisation else None,
+    ]
+    return "-".join(p for p in pieces if p) + ".conf"
+
+
+def write_loader_conf(profile: Optional[str], generation: int, specialisation: Optional[str]) -> None:
+    with open("@efiSysMountPoint@/loader/loader.conf.tmp", 'w') as f:
+        if "@timeout@" != "":
+            f.write("timeout @timeout@\n")
+        f.write("default %s\n" % generation_conf_filename(profile, generation, specialisation))
+        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: Optional[str], generation: int, specialisation: Optional[str], name: str) -> str:
+    return os.path.realpath("%s/%s" % (system_dir(profile, generation, specialisation), name))
+
+
+def copy_from_profile(profile: Optional[str], generation: int, specialisation: Optional[str], name: str, dry_run: bool = False) -> str:
+    store_file_path = profile_path(profile, generation, specialisation, 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: str) -> str:
+    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: Optional[str], generation: int, specialisation: Optional[str], machine_id: str) -> None:
+    kernel = copy_from_profile(profile, generation, specialisation, "kernel")
+    initrd = copy_from_profile(profile, generation, specialisation, "initrd")
+    try:
+        append_initrd_secrets = profile_path(profile, generation, specialisation, "append-initrd-secrets")
+        subprocess.check_call([append_initrd_secrets, "@efiSysMountPoint@%s" % (initrd)])
+    except FileNotFoundError:
+        pass
+    entry_file = "@efiSysMountPoint@/loader/entries/%s" % (
+        generation_conf_filename(profile, generation, specialisation))
+    generation_dir = os.readlink(system_dir(profile, generation, specialisation))
+    tmp_path = "%s.tmp" % (entry_file)
+    kernel_params = "init=%s/init " % 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 "",
+                    specialisation=" (%s)" % specialisation if specialisation 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: str) -> None:
+    try:
+        os.makedirs(path)
+    except OSError as e:
+        if e.errno != errno.EEXIST or not os.path.isdir(path):
+            raise
+
+
+def get_generations(profile: Optional[str] = None) -> List[SystemIdentifier]:
+    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()
+
+    configurationLimit = @configurationLimit@
+    configurations = [
+        SystemIdentifier(
+            profile=profile,
+            generation=int(line.split()[0]),
+            specialisation=None
+        )
+        for line in gen_lines
+    ]
+    return configurations[-configurationLimit:]
+
+
+def get_specialisations(profile: Optional[str], generation: int, _: Optional[str]) -> List[SystemIdentifier]:
+    specialisations_dir = os.path.join(
+            system_dir(profile, generation, None), "specialisation")
+    if not os.path.exists(specialisations_dir):
+        return []
+    return [SystemIdentifier(profile, generation, spec) for spec in os.listdir(specialisations_dir)]
+
+
+def remove_old_entries(gens: List[SystemIdentifier]) -> None:
+    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_number = int(rex_generation.sub(r"\1", path))
+            if not (prof, gen_number) in gens:
+                os.unlink(path)
+        except ValueError:
+            pass
+    for path in glob.iglob("@efiSysMountPoint@/efi/nixos/*"):
+        if not path in known_paths and not os.path.isdir(path):
+            os.unlink(path)
+
+
+def get_profiles() -> List[str]:
+    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() -> None:
+    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.run(
+          cmd, text=True, check=True, stdout=subprocess.PIPE
+        ).stdout.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")
+
+        flags = []
+
+        if "@canTouchEfiVariables@" != "1":
+            flags.append("--no-variables")
+
+        if "@graceful@" == "1":
+            flags.append("--graceful")
+
+        subprocess.check_call(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@"] + flags + ["install"])
+    else:
+        # Update bootloader to latest if needed
+        systemd_version = subprocess.check_output(["@systemd@/bin/bootctl", "--version"], universal_newlines=True).split()[2]
+        sdboot_status = subprocess.check_output(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@", "status"], universal_newlines=True)
+
+        # See status_binaries() in systemd bootctl.c for code which generates this
+        m = re.search("^\W+File:.*/EFI/(BOOT|systemd)/.*\.efi \(systemd-boot ([\d.]+[^)]*)\)$",
+                      sdboot_status, re.IGNORECASE | re.MULTILINE)
+
+        needs_install = False
+
+        if m is None:
+            print("could not find any previously installed systemd-boot, installing.")
+            # Let systemd-boot attempt an installation if a previous one wasn't found
+            needs_install = True
+        else:
+            sdboot_version = f'({m.group(2)})'
+            if systemd_version != sdboot_version:
+                print("updating systemd-boot from %s to %s" % (sdboot_version, systemd_version))
+                needs_install = True
+
+        if needs_install:
+            subprocess.check_call(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@", "update"])
+
+    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:
+        try:
+            write_entry(*gen, machine_id)
+            for specialisation in get_specialisations(*gen):
+                write_entry(*specialisation, machine_id)
+            if os.readlink(system_dir(*gen)) == args.default_config:
+                write_loader_conf(*gen)
+        except OSError as e:
+            profile = f"profile '{gen.profile}'" if gen.profile else "default profile"
+            print("ignoring {} in the list of boot entries because of the following error:\n{}".format(profile, e), file=sys.stderr)
+
+    for root, _, files in os.walk('@efiSysMountPoint@/efi/nixos/.extra-files', topdown=False):
+        relative_root = root.removeprefix("@efiSysMountPoint@/efi/nixos/.extra-files").removeprefix("/")
+        actual_root = os.path.join("@efiSysMountPoint@", relative_root)
+
+        for file in files:
+            actual_file = os.path.join(actual_root, file)
+
+            if os.path.exists(actual_file):
+                os.unlink(actual_file)
+            os.unlink(os.path.join(root, file))
+
+        if not len(os.listdir(actual_root)):
+            os.rmdir(actual_root)
+        os.rmdir(root)
+
+    mkdir_p("@efiSysMountPoint@/efi/nixos/.extra-files")
+
+    subprocess.check_call("@copyExtraFiles@")
+
+    # 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/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
new file mode 100644
index 00000000000..c07567ec82e
--- /dev/null
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
@@ -0,0 +1,303 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.boot.loader.systemd-boot;
+
+  efi = config.boot.loader.efi;
+
+  systemdBootBuilder = 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";
+
+    configurationLimit = if cfg.configurationLimit == null then 0 else cfg.configurationLimit;
+
+    inherit (cfg) consoleMode graceful;
+
+    inherit (efi) efiSysMountPoint canTouchEfiVariables;
+
+    memtest86 = if cfg.memtest86.enable then pkgs.memtest86-efi else "";
+
+    netbootxyz = if cfg.netbootxyz.enable then pkgs.netbootxyz-efi else "";
+
+    copyExtraFiles = pkgs.writeShellScript "copy-extra-files" ''
+      empty_file=$(mktemp)
+
+      ${concatStrings (mapAttrsToList (n: v: ''
+        ${pkgs.coreutils}/bin/install -Dp "${v}" "${efi.efiSysMountPoint}/"${escapeShellArg n}
+        ${pkgs.coreutils}/bin/install -D $empty_file "${efi.efiSysMountPoint}/efi/nixos/.extra-files/"${escapeShellArg n}
+      '') cfg.extraFiles)}
+
+      ${concatStrings (mapAttrsToList (n: v: ''
+        ${pkgs.coreutils}/bin/install -Dp "${pkgs.writeText n v}" "${efi.efiSysMountPoint}/loader/entries/"${escapeShellArg n}
+        ${pkgs.coreutils}/bin/install -D $empty_file "${efi.efiSysMountPoint}/efi/nixos/.extra-files/loader/entries/"${escapeShellArg n}
+      '') cfg.extraEntries)}
+    '';
+  };
+
+  checkedSystemdBootBuilder = pkgs.runCommand "systemd-boot" {
+    nativeBuildInputs = [ pkgs.mypy ];
+  } ''
+    install -m755 ${systemdBootBuilder} $out
+    mypy \
+      --no-implicit-optional \
+      --disallow-untyped-calls \
+      --disallow-untyped-defs \
+      $out
+  '';
+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.
+      '';
+    };
+
+    configurationLimit = mkOption {
+      default = null;
+      example = 120;
+      type = types.nullOr types.int;
+      description = ''
+        Maximum number of latest generations in the boot menu.
+        Useful to prevent boot partition running out of disk space.
+
+        <literal>null</literal> means no limit i.e. all generations
+        that were not garbage collected yet.
+      '';
+    };
+
+    consoleMode = mkOption {
+      default = "keep";
+
+      type = types.enum [ "0" "1" "2" "auto" "max" "keep" ];
+
+      description = ''
+        The resolution of the console. The following values are valid:
+
+        <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>
+      '';
+    };
+
+    memtest86 = {
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Make MemTest86 available from the systemd-boot menu. MemTest86 is a
+          program for testing memory.  MemTest86 is an unfree program, so
+          this requires <literal>allowUnfree</literal> to be set to
+          <literal>true</literal>.
+        '';
+      };
+
+      entryFilename = mkOption {
+        default = "memtest86.conf";
+        type = types.str;
+        description = ''
+          <literal>systemd-boot</literal> orders the menu entries by the config file names,
+          so if you want something to appear after all the NixOS entries,
+          it should start with <filename>o</filename> or onwards.
+        '';
+      };
+    };
+
+    netbootxyz = {
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Make <literal>netboot.xyz</literal> available from the
+          <literal>systemd-boot</literal> menu. <literal>netboot.xyz</literal>
+          is a menu system that allows you to boot OS installers and
+          utilities over the network.
+        '';
+      };
+
+      entryFilename = mkOption {
+        default = "o_netbootxyz.conf";
+        type = types.str;
+        description = ''
+          <literal>systemd-boot</literal> orders the menu entries by the config file names,
+          so if you want something to appear after all the NixOS entries,
+          it should start with <filename>o</filename> or onwards.
+        '';
+      };
+    };
+
+    extraEntries = mkOption {
+      type = types.attrsOf types.lines;
+      default = {};
+      example = literalExpression ''
+        { "memtest86.conf" = '''
+          title MemTest86
+          efi /efi/memtest86/memtest86.efi
+        '''; }
+      '';
+      description = ''
+        Any additional entries you want added to the <literal>systemd-boot</literal> menu.
+        These entries will be copied to <filename>/boot/loader/entries</filename>.
+        Each attribute name denotes the destination file name,
+        and the corresponding attribute value is the contents of the entry.
+
+        <literal>systemd-boot</literal> orders the menu entries by the config file names,
+        so if you want something to appear after all the NixOS entries,
+        it should start with <filename>o</filename> or onwards.
+      '';
+    };
+
+    extraFiles = mkOption {
+      type = types.attrsOf types.path;
+      default = {};
+      example = literalExpression ''
+        { "efi/memtest86/memtest86.efi" = "''${pkgs.memtest86-efi}/BOOTX64.efi"; }
+      '';
+      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.
+      '';
+    };
+
+    graceful = mkOption {
+      default = false;
+
+      type = types.bool;
+
+      description = ''
+        Invoke <literal>bootctl install</literal> with the <literal>--graceful</literal> option,
+        which ignores errors when EFI variables cannot be written or when the EFI System Partition
+        cannot be found. Currently only applies to random seed operations.
+
+        Only enable this option if <literal>systemd-boot</literal> otherwise fails to install, as the
+        scope or implication of the <literal>--graceful</literal> option may change in the future.
+      '';
+    };
+
+  };
+
+  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";
+      }
+    ] ++ concatMap (filename: [
+      {
+        assertion = !(hasInfix "/" filename);
+        message = "boot.loader.systemd-boot.extraEntries.${lib.strings.escapeNixIdentifier filename} is invalid: entries within folders are not supported";
+      }
+      {
+        assertion = hasSuffix ".conf" filename;
+        message = "boot.loader.systemd-boot.extraEntries.${lib.strings.escapeNixIdentifier filename} is invalid: entries must have a .conf file extension";
+      }
+    ]) (builtins.attrNames cfg.extraEntries)
+      ++ concatMap (filename: [
+        {
+          assertion = !(hasPrefix "/" filename);
+          message = "boot.loader.systemd-boot.extraFiles.${lib.strings.escapeNixIdentifier filename} is invalid: paths must not begin with a slash";
+        }
+        {
+          assertion = !(hasInfix ".." filename);
+          message = "boot.loader.systemd-boot.extraFiles.${lib.strings.escapeNixIdentifier filename} is invalid: paths must not reference the parent directory";
+        }
+        {
+          assertion = !(hasInfix "nixos/.extra-files" (toLower filename));
+          message = "boot.loader.systemd-boot.extraFiles.${lib.strings.escapeNixIdentifier filename} is invalid: files cannot be placed in the nixos/.extra-files directory";
+        }
+      ]) (builtins.attrNames cfg.extraFiles);
+
+    boot.loader.grub.enable = mkDefault false;
+
+    boot.loader.supportsInitrdSecrets = true;
+
+    boot.loader.systemd-boot.extraFiles = mkMerge [
+      # TODO: This is hard-coded to use the 64-bit EFI app, but it could probably
+      # be updated to use the 32-bit EFI app on 32-bit systems.  The 32-bit EFI
+      # app filename is BOOTIA32.efi.
+      (mkIf cfg.memtest86.enable {
+        "efi/memtest86/BOOTX64.efi" = "${pkgs.memtest86-efi}/BOOTX64.efi";
+      })
+      (mkIf cfg.netbootxyz.enable {
+        "efi/netbootxyz/netboot.xyz.efi" = "${pkgs.netbootxyz-efi}";
+      })
+    ];
+
+    boot.loader.systemd-boot.extraEntries = mkMerge [
+      (mkIf cfg.memtest86.enable {
+        "${cfg.memtest86.entryFilename}" = ''
+          title  MemTest86
+          efi    /efi/memtest86/BOOTX64.efi
+        '';
+      })
+      (mkIf cfg.netbootxyz.enable {
+        "${cfg.netbootxyz.entryFilename}" = ''
+          title  netboot.xyz
+          efi    /efi/netbootxyz/netboot.xyz.efi
+        '';
+      })
+    ];
+
+    system = {
+      build.installBootLoader = checkedSystemdBootBuilder;
+
+      boot.loader.id = "systemd-boot";
+
+      requiredKernelConfig = with config.lib.kernelConfig; [
+        (isYes "EFI_STUB")
+      ];
+    };
+  };
+}
diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix
new file mode 100644
index 00000000000..f0d3170dc5a
--- /dev/null
+++ b/nixos/modules/system/boot/luksroot.nix
@@ -0,0 +1,941 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  luks = config.boot.initrd.luks;
+  kernelPackages = config.boot.kernelPackages;
+
+  commonFunctions = ''
+    die() {
+        echo "$@" >&2
+        exit 1
+    }
+
+    dev_exist() {
+        local target="$1"
+        if [ -e $target ]; then
+            return 0
+        else
+            local uuid=$(echo -n $target | sed -e 's,UUID=\(.*\),\1,g')
+            blkid --uuid $uuid >/dev/null
+            return $?
+        fi
+    }
+
+    wait_target() {
+        local name="$1"
+        local target="$2"
+        local secs="''${3:-10}"
+        local desc="''${4:-$name $target to appear}"
+
+        if ! dev_exist $target; then
+            echo -n "Waiting $secs seconds for $desc..."
+            local success=false;
+            for try in $(seq $secs); do
+                echo -n "."
+                sleep 1
+                if dev_exist $target; then
+                    success=true
+                    break
+                fi
+            done
+            if [ $success == true ]; then
+                echo " - success";
+                return 0
+            else
+                echo " - failure";
+                return 1
+            fi
+        fi
+        return 0
+    }
+
+    wait_yubikey() {
+        local secs="''${1:-10}"
+
+        ykinfo -v 1>/dev/null 2>&1
+        if [ $? != 0 ]; then
+            echo -n "Waiting $secs seconds for YubiKey to appear..."
+            local success=false
+            for try in $(seq $secs); do
+                echo -n .
+                sleep 1
+                ykinfo -v 1>/dev/null 2>&1
+                if [ $? == 0 ]; then
+                    success=true
+                    break
+                fi
+            done
+            if [ $success == true ]; then
+                echo " - success";
+                return 0
+            else
+                echo " - failure";
+                return 1
+            fi
+        fi
+        return 0
+    }
+
+    wait_gpgcard() {
+        local secs="''${1:-10}"
+
+        gpg --card-status > /dev/null 2> /dev/null
+        if [ $? != 0 ]; then
+            echo -n "Waiting $secs seconds for GPG Card to appear"
+            local success=false
+            for try in $(seq $secs); do
+                echo -n .
+                sleep 1
+                gpg --card-status > /dev/null 2> /dev/null
+                if [ $? == 0 ]; then
+                    success=true
+                    break
+                fi
+            done
+            if [ $success == true ]; then
+                echo " - success";
+                return 0
+            else
+                echo " - failure";
+                return 1
+            fi
+        fi
+        return 0
+    }
+  '';
+
+  preCommands = ''
+    # A place to store crypto things
+
+    # A ramfs is used here to ensure that the file used to update
+    # the key slot with cryptsetup will never get swapped out.
+    # Warning: Do NOT replace with tmpfs!
+    mkdir -p /crypt-ramfs
+    mount -t ramfs none /crypt-ramfs
+
+    # Cryptsetup locking directory
+    mkdir -p /run/cryptsetup
+
+    # For YubiKey salt storage
+    mkdir -p /crypt-storage
+
+    ${optionalString luks.gpgSupport ''
+    export GPG_TTY=$(tty)
+    export GNUPGHOME=/crypt-ramfs/.gnupg
+
+    gpg-agent --daemon --scdaemon-program $out/bin/scdaemon > /dev/null 2> /dev/null
+    ''}
+
+    # Disable all input echo for the whole stage. We could use read -s
+    # instead but that would ocasionally leak characters between read
+    # invocations.
+    stty -echo
+  '';
+
+  postCommands = ''
+    stty echo
+    umount /crypt-storage 2>/dev/null
+    umount /crypt-ramfs 2>/dev/null
+  '';
+
+  openCommand = name: dev: assert name == dev.name;
+  let
+    csopen = "cryptsetup luksOpen ${dev.device} ${dev.name}"
+           + optionalString dev.allowDiscards " --allow-discards"
+           + optionalString dev.bypassWorkqueues " --perf-no_read_workqueue --perf-no_write_workqueue"
+           + optionalString (dev.header != null) " --header=${dev.header}";
+    cschange = "cryptsetup luksChangeKey ${dev.device} ${optionalString (dev.header != null) "--header=${dev.header}"}";
+  in ''
+    # Wait for luksRoot (and optionally keyFile and/or header) to appear, e.g.
+    # if on a USB drive.
+    wait_target "device" ${dev.device} || die "${dev.device} is unavailable"
+
+    ${optionalString (dev.header != null) ''
+      wait_target "header" ${dev.header} || die "${dev.header} is unavailable"
+    ''}
+
+    do_open_passphrase() {
+        local passphrase
+
+        while true; do
+            echo -n "Passphrase for ${dev.device}: "
+            passphrase=
+            while true; do
+                if [ -e /crypt-ramfs/passphrase ]; then
+                    echo "reused"
+                    passphrase=$(cat /crypt-ramfs/passphrase)
+                    break
+                else
+                    # ask cryptsetup-askpass
+                    echo -n "${dev.device}" > /crypt-ramfs/device
+
+                    # and try reading it from /dev/console with a timeout
+                    IFS= read -t 1 -r passphrase
+                    if [ -n "$passphrase" ]; then
+                       ${if luks.reusePassphrases then ''
+                         # remember it for the next device
+                         echo -n "$passphrase" > /crypt-ramfs/passphrase
+                       '' else ''
+                         # Don't save it to ramfs. We are very paranoid
+                       ''}
+                       echo
+                       break
+                    fi
+                fi
+            done
+            echo -n "Verifying passphrase for ${dev.device}..."
+            echo -n "$passphrase" | ${csopen} --key-file=-
+            if [ $? == 0 ]; then
+                echo " - success"
+                ${if luks.reusePassphrases then ''
+                  # we don't rm here because we might reuse it for the next device
+                '' else ''
+                  rm -f /crypt-ramfs/passphrase
+                ''}
+                break
+            else
+                echo " - failure"
+                # ask for a different one
+                rm -f /crypt-ramfs/passphrase
+            fi
+        done
+    }
+
+    # LUKS
+    open_normally() {
+        ${if (dev.keyFile != null) then ''
+        if wait_target "key file" ${dev.keyFile}; then
+            ${csopen} --key-file=${dev.keyFile} \
+              ${optionalString (dev.keyFileSize != null) "--keyfile-size=${toString dev.keyFileSize}"} \
+              ${optionalString (dev.keyFileOffset != null) "--keyfile-offset=${toString dev.keyFileOffset}"}
+        else
+            ${if dev.fallbackToPassword then "echo" else "die"} "${dev.keyFile} is unavailable"
+            echo " - failing back to interactive password prompt"
+            do_open_passphrase
+        fi
+        '' else ''
+        do_open_passphrase
+        ''}
+    }
+
+    ${optionalString (luks.yubikeySupport && (dev.yubikey != null)) ''
+    # YubiKey
+    rbtohex() {
+        ( od -An -vtx1 | tr -d ' \n' )
+    }
+
+    hextorb() {
+        ( tr '[:lower:]' '[:upper:]' | sed -e 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/gI' | xargs printf )
+    }
+
+    do_open_yubikey() {
+        # Make all of these local to this function
+        # to prevent their values being leaked
+        local salt
+        local iterations
+        local k_user
+        local challenge
+        local response
+        local k_luks
+        local opened
+        local new_salt
+        local new_iterations
+        local new_challenge
+        local new_response
+        local new_k_luks
+
+        mount -t ${dev.yubikey.storage.fsType} ${dev.yubikey.storage.device} /crypt-storage || \
+          die "Failed to mount YubiKey salt storage device"
+
+        salt="$(cat /crypt-storage${dev.yubikey.storage.path} | sed -n 1p | tr -d '\n')"
+        iterations="$(cat /crypt-storage${dev.yubikey.storage.path} | sed -n 2p | tr -d '\n')"
+        challenge="$(echo -n $salt | openssl-wrap dgst -binary -sha512 | rbtohex)"
+        response="$(ykchalresp -${toString dev.yubikey.slot} -x $challenge 2>/dev/null)"
+
+        for try in $(seq 3); do
+            ${optionalString dev.yubikey.twoFactor ''
+            echo -n "Enter two-factor passphrase: "
+            k_user=
+            while true; do
+                if [ -e /crypt-ramfs/passphrase ]; then
+                    echo "reused"
+                    k_user=$(cat /crypt-ramfs/passphrase)
+                    break
+                else
+                    # Try reading it from /dev/console with a timeout
+                    IFS= read -t 1 -r k_user
+                    if [ -n "$k_user" ]; then
+                       ${if luks.reusePassphrases then ''
+                         # Remember it for the next device
+                         echo -n "$k_user" > /crypt-ramfs/passphrase
+                       '' else ''
+                         # Don't save it to ramfs. We are very paranoid
+                       ''}
+                       echo
+                       break
+                    fi
+                fi
+            done
+            ''}
+
+            if [ ! -z "$k_user" ]; then
+                k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString dev.yubikey.keyLength} $iterations $response | rbtohex)"
+            else
+                k_luks="$(echo | pbkdf2-sha512 ${toString dev.yubikey.keyLength} $iterations $response | rbtohex)"
+            fi
+
+            echo -n "$k_luks" | hextorb | ${csopen} --key-file=-
+
+            if [ $? == 0 ]; then
+                opened=true
+                ${if luks.reusePassphrases then ''
+                  # We don't rm here because we might reuse it for the next device
+                '' else ''
+                  rm -f /crypt-ramfs/passphrase
+                ''}
+                break
+            else
+                opened=false
+                echo "Authentication failed!"
+            fi
+        done
+
+        [ "$opened" == false ] && die "Maximum authentication errors reached"
+
+        echo -n "Gathering entropy for new salt (please enter random keys to generate entropy if this blocks for long)..."
+        for i in $(seq ${toString dev.yubikey.saltLength}); do
+            byte="$(dd if=/dev/random bs=1 count=1 2>/dev/null | rbtohex)";
+            new_salt="$new_salt$byte";
+            echo -n .
+        done;
+        echo "ok"
+
+        new_iterations="$iterations"
+        ${optionalString (dev.yubikey.iterationStep > 0) ''
+        new_iterations="$(($new_iterations + ${toString dev.yubikey.iterationStep}))"
+        ''}
+
+        new_challenge="$(echo -n $new_salt | openssl-wrap dgst -binary -sha512 | rbtohex)"
+
+        new_response="$(ykchalresp -${toString dev.yubikey.slot} -x $new_challenge 2>/dev/null)"
+
+        if [ ! -z "$k_user" ]; then
+            new_k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString dev.yubikey.keyLength} $new_iterations $new_response | rbtohex)"
+        else
+            new_k_luks="$(echo | pbkdf2-sha512 ${toString dev.yubikey.keyLength} $new_iterations $new_response | rbtohex)"
+        fi
+
+        echo -n "$new_k_luks" | hextorb > /crypt-ramfs/new_key
+        echo -n "$k_luks" | hextorb | ${cschange} --key-file=- /crypt-ramfs/new_key
+
+        if [ $? == 0 ]; then
+            echo -ne "$new_salt\n$new_iterations" > /crypt-storage${dev.yubikey.storage.path}
+            sync /crypt-storage${dev.yubikey.storage.path}
+        else
+            echo "Warning: Could not update LUKS key, current challenge persists!"
+        fi
+
+        rm -f /crypt-ramfs/new_key
+        umount /crypt-storage
+    }
+
+    open_with_hardware() {
+        if wait_yubikey ${toString dev.yubikey.gracePeriod}; then
+            do_open_yubikey
+        else
+            echo "No YubiKey found, falling back to non-YubiKey open procedure"
+            open_normally
+        fi
+    }
+    ''}
+
+    ${optionalString (luks.gpgSupport && (dev.gpgCard != null)) ''
+
+    do_open_gpg_card() {
+        # Make all of these local to this function
+        # to prevent their values being leaked
+        local pin
+        local opened
+
+        gpg --import /gpg-keys/${dev.device}/pubkey.asc > /dev/null 2> /dev/null
+
+        gpg --card-status > /dev/null 2> /dev/null
+
+        for try in $(seq 3); do
+            echo -n "PIN for GPG Card associated with device ${dev.device}: "
+            pin=
+            while true; do
+                if [ -e /crypt-ramfs/passphrase ]; then
+                    echo "reused"
+                    pin=$(cat /crypt-ramfs/passphrase)
+                    break
+                else
+                    # and try reading it from /dev/console with a timeout
+                    IFS= read -t 1 -r pin
+                    if [ -n "$pin" ]; then
+                       ${if luks.reusePassphrases then ''
+                         # remember it for the next device
+                         echo -n "$pin" > /crypt-ramfs/passphrase
+                       '' else ''
+                         # Don't save it to ramfs. We are very paranoid
+                       ''}
+                       echo
+                       break
+                    fi
+                fi
+            done
+            echo -n "Verifying passphrase for ${dev.device}..."
+            echo -n "$pin" | gpg -q --batch --passphrase-fd 0 --pinentry-mode loopback -d /gpg-keys/${dev.device}/cryptkey.gpg 2> /dev/null | ${csopen} --key-file=- > /dev/null 2> /dev/null
+            if [ $? == 0 ]; then
+                echo " - success"
+                ${if luks.reusePassphrases then ''
+                  # we don't rm here because we might reuse it for the next device
+                '' else ''
+                  rm -f /crypt-ramfs/passphrase
+                ''}
+                break
+            else
+                echo " - failure"
+                # ask for a different one
+                rm -f /crypt-ramfs/passphrase
+            fi
+        done
+
+        [ "$opened" == false ] && die "Maximum authentication errors reached"
+    }
+
+    open_with_hardware() {
+        if wait_gpgcard ${toString dev.gpgCard.gracePeriod}; then
+            do_open_gpg_card
+        else
+            echo "No GPG Card found, falling back to normal open procedure"
+            open_normally
+        fi
+    }
+    ''}
+
+    ${optionalString (luks.fido2Support && (dev.fido2.credential != null)) ''
+
+    open_with_hardware() {
+      local passsphrase
+
+        ${if dev.fido2.passwordLess then ''
+          export passphrase=""
+        '' else ''
+          read -rsp "FIDO2 salt for ${dev.device}: " passphrase
+          echo
+        ''}
+        ${optionalString (lib.versionOlder kernelPackages.kernel.version "5.4") ''
+          echo "On systems with Linux Kernel < 5.4, it might take a while to initialize the CRNG, you might want to use linuxPackages_latest."
+          echo "Please move your mouse to create needed randomness."
+        ''}
+          echo "Waiting for your FIDO2 device..."
+          fido2luks open ${dev.device} ${dev.name} ${dev.fido2.credential} --await-dev ${toString dev.fido2.gracePeriod} --salt string:$passphrase
+        if [ $? -ne 0 ]; then
+          echo "No FIDO2 key found, falling back to normal open procedure"
+          open_normally
+        fi
+    }
+    ''}
+
+    # commands to run right before we mount our device
+    ${dev.preOpenCommands}
+
+    ${if (luks.yubikeySupport && (dev.yubikey != null)) || (luks.gpgSupport && (dev.gpgCard != null)) || (luks.fido2Support && (dev.fido2.credential != null)) then ''
+    open_with_hardware
+    '' else ''
+    open_normally
+    ''}
+
+    # commands to run right after we mounted our device
+    ${dev.postOpenCommands}
+  '';
+
+  askPass = pkgs.writeScriptBin "cryptsetup-askpass" ''
+    #!/bin/sh
+
+    ${commonFunctions}
+
+    while true; do
+        wait_target "luks" /crypt-ramfs/device 10 "LUKS to request a passphrase" || die "Passphrase is not requested now"
+        device=$(cat /crypt-ramfs/device)
+
+        echo -n "Passphrase for $device: "
+        IFS= read -rs passphrase
+        echo
+
+        rm /crypt-ramfs/device
+        echo -n "$passphrase" > /crypt-ramfs/passphrase
+    done
+  '';
+
+  preLVM = filterAttrs (n: v: v.preLVM) luks.devices;
+  postLVM = filterAttrs (n: v: !v.preLVM) luks.devices;
+
+in
+{
+  imports = [
+    (mkRemovedOptionModule [ "boot" "initrd" "luks" "enable" ] "")
+  ];
+
+  options = {
+
+    boot.initrd.luks.mitigateDMAAttacks = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Unless enabled, encryption keys can be easily recovered by an attacker with physical
+        access to any machine with PCMCIA, ExpressCard, ThunderBolt or FireWire port.
+        More information is available at <link xlink:href="http://en.wikipedia.org/wiki/DMA_attack"/>.
+
+        This option blacklists FireWire drivers, but doesn't remove them. You can manually
+        load the drivers if you need to use a FireWire device, but don't forget to unload them!
+      '';
+    };
+
+    boot.initrd.luks.cryptoModules = mkOption {
+      type = types.listOf types.str;
+      default =
+        [ "aes" "aes_generic" "blowfish" "twofish"
+          "serpent" "cbc" "xts" "lrw" "sha1" "sha256" "sha512"
+          "af_alg" "algif_skcipher"
+        ];
+      description = ''
+        A list of cryptographic kernel modules needed to decrypt the root device(s).
+        The default includes all common modules.
+      '';
+    };
+
+    boot.initrd.luks.forceLuksSupportInInitrd = mkOption {
+      type = types.bool;
+      default = false;
+      internal = true;
+      description = ''
+        Whether to configure luks support in the initrd, when no luks
+        devices are configured.
+      '';
+    };
+
+    boot.initrd.luks.reusePassphrases = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        When opening a new LUKS device try reusing last successful
+        passphrase.
+
+        Useful for mounting a number of devices that use the same
+        passphrase without retyping it several times.
+
+        Such setup can be useful if you use <command>cryptsetup
+        luksSuspend</command>. Different LUKS devices will still have
+        different master keys even when using the same passphrase.
+      '';
+    };
+
+    boot.initrd.luks.devices = mkOption {
+      default = { };
+      example = { luksroot.device = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08"; };
+      description = ''
+        The encrypted disk that should be opened before the root
+        filesystem is mounted. Both LVM-over-LUKS and LUKS-over-LVM
+        setups are supported. The unencrypted devices can be accessed as
+        <filename>/dev/mapper/<replaceable>name</replaceable></filename>.
+      '';
+
+      type = with types; attrsOf (submodule (
+        { name, ... }: { options = {
+
+          name = mkOption {
+            visible = false;
+            default = name;
+            example = "luksroot";
+            type = types.str;
+            description = "Name of the unencrypted device in <filename>/dev/mapper</filename>.";
+          };
+
+          device = mkOption {
+            example = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08";
+            type = types.str;
+            description = "Path of the underlying encrypted block device.";
+          };
+
+          header = mkOption {
+            default = null;
+            example = "/root/header.img";
+            type = types.nullOr types.str;
+            description = ''
+              The name of the file or block device that
+              should be used as header for the encrypted device.
+            '';
+          };
+
+          keyFile = mkOption {
+            default = null;
+            example = "/dev/sdb1";
+            type = types.nullOr types.str;
+            description = ''
+              The name of the file (can be a raw device or a partition) that
+              should be used as the decryption key for the encrypted device. If
+              not specified, you will be prompted for a passphrase instead.
+            '';
+          };
+
+          keyFileSize = mkOption {
+            default = null;
+            example = 4096;
+            type = types.nullOr types.int;
+            description = ''
+              The size of the key file. Use this if only the beginning of the
+              key file should be used as a key (often the case if a raw device
+              or partition is used as key file). If not specified, the whole
+              <literal>keyFile</literal> will be used decryption, instead of just
+              the first <literal>keyFileSize</literal> bytes.
+            '';
+          };
+
+          keyFileOffset = mkOption {
+            default = null;
+            example = 4096;
+            type = types.nullOr types.int;
+            description = ''
+              The offset of the key file. Use this in combination with
+              <literal>keyFileSize</literal> to use part of a file as key file
+              (often the case if a raw device or partition is used as a key file).
+              If not specified, the key begins at the first byte of
+              <literal>keyFile</literal>.
+            '';
+          };
+
+          # FIXME: get rid of this option.
+          preLVM = mkOption {
+            default = true;
+            type = types.bool;
+            description = "Whether the luksOpen will be attempted before LVM scan or after it.";
+          };
+
+          allowDiscards = mkOption {
+            default = false;
+            type = types.bool;
+            description = ''
+              Whether to allow TRIM requests to the underlying device. This option
+              has security implications; please read the LUKS documentation before
+              activating it.
+              This option is incompatible with authenticated encryption (dm-crypt
+              stacked over dm-integrity).
+            '';
+          };
+
+          bypassWorkqueues = mkOption {
+            default = false;
+            type = types.bool;
+            description = ''
+              Whether to bypass dm-crypt's internal read and write workqueues.
+              Enabling this should improve performance on SSDs; see
+              <link xlink:href="https://wiki.archlinux.org/index.php/Dm-crypt/Specialties#Disable_workqueue_for_increased_solid_state_drive_(SSD)_performance">here</link>
+              for more information. Needs Linux 5.9 or later.
+            '';
+          };
+
+          fallbackToPassword = mkOption {
+            default = false;
+            type = types.bool;
+            description = ''
+              Whether to fallback to interactive passphrase prompt if the keyfile
+              cannot be found. This will prevent unattended boot should the keyfile
+              go missing.
+            '';
+          };
+
+          gpgCard = mkOption {
+            default = null;
+            description = ''
+              The option to use this LUKS device with a GPG encrypted luks password by the GPG Smartcard.
+              If null (the default), GPG-Smartcard will be disabled for this device.
+            '';
+
+            type = with types; nullOr (submodule {
+              options = {
+                gracePeriod = mkOption {
+                  default = 10;
+                  type = types.int;
+                  description = "Time in seconds to wait for the GPG Smartcard.";
+                };
+
+                encryptedPass = mkOption {
+                  type = types.path;
+                  description = "Path to the GPG encrypted passphrase.";
+                };
+
+                publicKey = mkOption {
+                  type = types.path;
+                  description = "Path to the Public Key.";
+                };
+              };
+            });
+          };
+
+          fido2 = {
+            credential = mkOption {
+              default = null;
+              example = "f1d00200d8dc783f7fb1e10ace8da27f8312d72692abfca2f7e4960a73f48e82e1f7571f6ebfcee9fb434f9886ccc8fcc52a6614d8d2";
+              type = types.nullOr types.str;
+              description = "The FIDO2 credential ID.";
+            };
+
+            gracePeriod = mkOption {
+              default = 10;
+              type = types.int;
+              description = "Time in seconds to wait for the FIDO2 key.";
+            };
+
+            passwordLess = mkOption {
+              default = false;
+              type = types.bool;
+              description = ''
+                Defines whatever to use an empty string as a default salt.
+
+                Enable only when your device is PIN protected, such as <link xlink:href="https://trezor.io/">Trezor</link>.
+              '';
+            };
+          };
+
+          yubikey = mkOption {
+            default = null;
+            description = ''
+              The options to use for this LUKS device in YubiKey-PBA.
+              If null (the default), YubiKey-PBA will be disabled for this device.
+            '';
+
+            type = with types; nullOr (submodule {
+              options = {
+                twoFactor = mkOption {
+                  default = true;
+                  type = types.bool;
+                  description = "Whether to use a passphrase and a YubiKey (true), or only a YubiKey (false).";
+                };
+
+                slot = mkOption {
+                  default = 2;
+                  type = types.int;
+                  description = "Which slot on the YubiKey to challenge.";
+                };
+
+                saltLength = mkOption {
+                  default = 16;
+                  type = types.int;
+                  description = "Length of the new salt in byte (64 is the effective maximum).";
+                };
+
+                keyLength = mkOption {
+                  default = 64;
+                  type = types.int;
+                  description = "Length of the LUKS slot key derived with PBKDF2 in byte.";
+                };
+
+                iterationStep = mkOption {
+                  default = 0;
+                  type = types.int;
+                  description = "How much the iteration count for PBKDF2 is increased at each successful authentication.";
+                };
+
+                gracePeriod = mkOption {
+                  default = 10;
+                  type = types.int;
+                  description = "Time in seconds to wait for the YubiKey.";
+                };
+
+                /* TODO: Add to the documentation of the current module:
+
+                   Options related to the storing the salt.
+                */
+                storage = {
+                  device = mkOption {
+                    default = "/dev/sda1";
+                    type = types.path;
+                    description = ''
+                      An unencrypted device that will temporarily be mounted in stage-1.
+                      Must contain the current salt to create the challenge for this LUKS device.
+                    '';
+                  };
+
+                  fsType = mkOption {
+                    default = "vfat";
+                    type = types.str;
+                    description = "The filesystem of the unencrypted device.";
+                  };
+
+                  path = mkOption {
+                    default = "/crypt-storage/default";
+                    type = types.str;
+                    description = ''
+                      Absolute path of the salt on the unencrypted device with
+                      that device's root directory as "/".
+                    '';
+                  };
+                };
+              };
+            });
+          };
+
+          preOpenCommands = mkOption {
+            type = types.lines;
+            default = "";
+            example = ''
+              mkdir -p /tmp/persistent
+              mount -t zfs rpool/safe/persistent /tmp/persistent
+            '';
+            description = ''
+              Commands that should be run right before we try to mount our LUKS device.
+              This can be useful, if the keys needed to open the drive is on another partion.
+            '';
+          };
+
+          postOpenCommands = mkOption {
+            type = types.lines;
+            default = "";
+            example = ''
+              umount /tmp/persistent
+            '';
+            description = ''
+              Commands that should be run right after we have mounted our LUKS device.
+            '';
+          };
+        };
+      }));
+    };
+
+    boot.initrd.luks.gpgSupport = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Enables support for authenticating with a GPG encrypted password.
+      '';
+    };
+
+    boot.initrd.luks.yubikeySupport = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+            Enables support for authenticating with a YubiKey on LUKS devices.
+            See the NixOS wiki for information on how to properly setup a LUKS device
+            and a YubiKey to work with this feature.
+          '';
+    };
+
+    boot.initrd.luks.fido2Support = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Enables support for authenticating with FIDO2 devices.
+      '';
+    };
+
+  };
+
+  config = mkIf (luks.devices != {} || luks.forceLuksSupportInInitrd) {
+
+    assertions =
+      [ { assertion = !(luks.gpgSupport && luks.yubikeySupport);
+          message = "YubiKey and GPG Card may not be used at the same time.";
+        }
+
+        { assertion = !(luks.gpgSupport && luks.fido2Support);
+          message = "FIDO2 and GPG Card may not be used at the same time.";
+        }
+
+        { assertion = !(luks.fido2Support && luks.yubikeySupport);
+          message = "FIDO2 and YubiKey may not be used at the same time.";
+        }
+
+        { assertion = any (dev: dev.bypassWorkqueues) (attrValues luks.devices)
+                      -> versionAtLeast kernelPackages.kernel.version "5.9";
+          message = "boot.initrd.luks.devices.<name>.bypassWorkqueues is not supported for kernels older than 5.9";
+        }
+      ];
+
+    # actually, sbp2 driver is the one enabling the DMA attack, but this needs to be tested
+    boot.blacklistedKernelModules = optionals luks.mitigateDMAAttacks
+      ["firewire_ohci" "firewire_core" "firewire_sbp2"];
+
+    # Some modules that may be needed for mounting anything ciphered
+    boot.initrd.availableKernelModules = [ "dm_mod" "dm_crypt" "cryptd" "input_leds" ]
+      ++ luks.cryptoModules
+      # workaround until https://marc.info/?l=linux-crypto-vger&m=148783562211457&w=4 is merged
+      # remove once 'modprobe --show-depends xts' shows ecb as a dependency
+      ++ (if builtins.elem "xts" luks.cryptoModules then ["ecb"] else []);
+
+    # copy the cryptsetup binary and it's dependencies
+    boot.initrd.extraUtilsCommands = ''
+      copy_bin_and_libs ${pkgs.cryptsetup}/bin/cryptsetup
+      copy_bin_and_libs ${askPass}/bin/cryptsetup-askpass
+      sed -i s,/bin/sh,$out/bin/sh, $out/bin/cryptsetup-askpass
+
+      ${optionalString luks.yubikeySupport ''
+        copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykchalresp
+        copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykinfo
+        copy_bin_and_libs ${pkgs.openssl.bin}/bin/openssl
+
+        cc -O3 -I${pkgs.openssl.dev}/include -L${pkgs.openssl.out}/lib ${./pbkdf2-sha512.c} -o pbkdf2-sha512 -lcrypto
+        strip -s pbkdf2-sha512
+        copy_bin_and_libs pbkdf2-sha512
+
+        mkdir -p $out/etc/ssl
+        cp -pdv ${pkgs.openssl.out}/etc/ssl/openssl.cnf $out/etc/ssl
+
+        cat > $out/bin/openssl-wrap <<EOF
+        #!$out/bin/sh
+        export OPENSSL_CONF=$out/etc/ssl/openssl.cnf
+        $out/bin/openssl "\$@"
+        EOF
+        chmod +x $out/bin/openssl-wrap
+      ''}
+
+      ${optionalString luks.fido2Support ''
+        copy_bin_and_libs ${pkgs.fido2luks}/bin/fido2luks
+      ''}
+
+
+      ${optionalString luks.gpgSupport ''
+        copy_bin_and_libs ${pkgs.gnupg}/bin/gpg
+        copy_bin_and_libs ${pkgs.gnupg}/bin/gpg-agent
+        copy_bin_and_libs ${pkgs.gnupg}/libexec/scdaemon
+
+        ${concatMapStringsSep "\n" (x:
+          if x.gpgCard != null then
+            ''
+              mkdir -p $out/secrets/gpg-keys/${x.device}
+              cp -a ${x.gpgCard.encryptedPass} $out/secrets/gpg-keys/${x.device}/cryptkey.gpg
+              cp -a ${x.gpgCard.publicKey} $out/secrets/gpg-keys/${x.device}/pubkey.asc
+            ''
+          else ""
+          ) (attrValues luks.devices)
+        }
+      ''}
+    '';
+
+    boot.initrd.extraUtilsCommandsTest = ''
+      $out/bin/cryptsetup --version
+      ${optionalString luks.yubikeySupport ''
+        $out/bin/ykchalresp -V
+        $out/bin/ykinfo -V
+        $out/bin/openssl-wrap version
+      ''}
+      ${optionalString luks.gpgSupport ''
+        $out/bin/gpg --version
+        $out/bin/gpg-agent --version
+        $out/bin/scdaemon --version
+      ''}
+      ${optionalString luks.fido2Support ''
+        $out/bin/fido2luks --version
+      ''}
+    '';
+
+    boot.initrd.preFailCommands = postCommands;
+    boot.initrd.preLVMCommands = commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand preLVM) + postCommands;
+    boot.initrd.postDeviceCommands = commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand postLVM) + postCommands;
+
+    environment.systemPackages = [ pkgs.cryptsetup ];
+  };
+}
diff --git a/nixos/modules/system/boot/modprobe.nix b/nixos/modules/system/boot/modprobe.nix
new file mode 100644
index 00000000000..e683d181729
--- /dev/null
+++ b/nixos/modules/system/boot/modprobe.nix
@@ -0,0 +1,70 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+  ###### interface
+
+  options = {
+
+    boot.blacklistedKernelModules = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = [ "cirrusfb" "i2c_piix4" ];
+      description = ''
+        List of names of kernel modules that should not be loaded
+        automatically by the hardware probing code.
+      '';
+    };
+
+    boot.extraModprobeConfig = mkOption {
+      default = "";
+      example =
+        ''
+          options parport_pc io=0x378 irq=7 dma=1
+        '';
+      description = ''
+        Any additional configuration to be appended to the generated
+        <filename>modprobe.conf</filename>.  This is typically used to
+        specify module options.  See
+        <citerefentry><refentrytitle>modprobe.d</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+      type = types.lines;
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf (!config.boot.isContainer) {
+
+    environment.etc."modprobe.d/ubuntu.conf".source = "${pkgs.kmod-blacklist-ubuntu}/modprobe.conf";
+
+    environment.etc."modprobe.d/nixos.conf".text =
+      ''
+        ${flip concatMapStrings config.boot.blacklistedKernelModules (name: ''
+          blacklist ${name}
+        '')}
+        ${config.boot.extraModprobeConfig}
+      '';
+    environment.etc."modprobe.d/debian.conf".source = pkgs.kmod-debian-aliases;
+
+    environment.etc."modprobe.d/systemd.conf".source = "${pkgs.systemd}/lib/modprobe.d/systemd.conf";
+
+    environment.systemPackages = [ pkgs.kmod ];
+
+    system.activationScripts.modprobe = stringAfter ["specialfs"]
+      ''
+        # Allow the kernel to find our wrapped modprobe (which searches
+        # in the right location in the Nix store for kernel modules).
+        # We need this when the kernel (or some module) auto-loads a
+        # module.
+        echo ${pkgs.kmod}/bin/modprobe > /proc/sys/kernel/modprobe
+      '';
+
+  };
+
+}
diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix
new file mode 100644
index 00000000000..ac1e4ef34b4
--- /dev/null
+++ b/nixos/modules/system/boot/networkd.nix
@@ -0,0 +1,1797 @@
+{ config, lib, pkgs, utils, ... }:
+
+with utils.systemdUtils.unitOptions;
+with utils.systemdUtils.lib;
+with lib;
+
+let
+
+  cfg = config.systemd.network;
+
+  check = {
+
+    link = {
+
+      sectionLink = checkUnitConfig "Link" [
+        (assertOnlyFields [
+          "Description"
+          "Alias"
+          "MACAddressPolicy"
+          "MACAddress"
+          "NamePolicy"
+          "Name"
+          "AlternativeNamesPolicy"
+          "AlternativeName"
+          "MTUBytes"
+          "BitsPerSecond"
+          "Duplex"
+          "AutoNegotiation"
+          "WakeOnLan"
+          "Port"
+          "Advertise"
+          "ReceiveChecksumOffload"
+          "TransmitChecksumOffload"
+          "TCPSegmentationOffload"
+          "TCP6SegmentationOffload"
+          "GenericSegmentationOffload"
+          "GenericReceiveOffload"
+          "LargeReceiveOffload"
+          "RxChannels"
+          "TxChannels"
+          "OtherChannels"
+          "CombinedChannels"
+          "RxBufferSize"
+          "TxBufferSize"
+        ])
+        (assertValueOneOf "MACAddressPolicy" ["persistent" "random" "none"])
+        (assertMacAddress "MACAddress")
+        (assertByteFormat "MTUBytes")
+        (assertByteFormat "BitsPerSecond")
+        (assertValueOneOf "Duplex" ["half" "full"])
+        (assertValueOneOf "AutoNegotiation" boolValues)
+        (assertValueOneOf "WakeOnLan" ["phy" "unicast" "multicast" "broadcast" "arp" "magic" "secureon" "off"])
+        (assertValueOneOf "Port" ["tp" "aui" "bnc" "mii" "fibre"])
+        (assertValueOneOf "ReceiveChecksumOffload" boolValues)
+        (assertValueOneOf "TransmitChecksumOffload" boolValues)
+        (assertValueOneOf "TCPSegmentationOffload" boolValues)
+        (assertValueOneOf "TCP6SegmentationOffload" boolValues)
+        (assertValueOneOf "GenericSegmentationOffload" boolValues)
+        (assertValueOneOf "GenericReceiveOffload" boolValues)
+        (assertValueOneOf "LargeReceiveOffload" boolValues)
+        (assertInt "RxChannels")
+        (assertRange "RxChannels" 1 4294967295)
+        (assertInt "TxChannels")
+        (assertRange "TxChannels" 1 4294967295)
+        (assertInt "OtherChannels")
+        (assertRange "OtherChannels" 1 4294967295)
+        (assertInt "CombinedChannels")
+        (assertRange "CombinedChannels" 1 4294967295)
+        (assertInt "RxBufferSize")
+        (assertInt "TxBufferSize")
+      ];
+    };
+
+    netdev = let
+
+      tunChecks = [
+        (assertOnlyFields [
+          "MultiQueue"
+          "PacketInfo"
+          "VNetHeader"
+          "User"
+          "Group"
+        ])
+        (assertValueOneOf "MultiQueue" boolValues)
+        (assertValueOneOf "PacketInfo" boolValues)
+        (assertValueOneOf "VNetHeader" boolValues)
+      ];
+    in {
+
+      sectionNetdev = checkUnitConfig "Netdev" [
+        (assertOnlyFields [
+          "Description"
+          "Name"
+          "Kind"
+          "MTUBytes"
+          "MACAddress"
+        ])
+        (assertHasField "Name")
+        (assertHasField "Kind")
+        (assertValueOneOf "Kind" [
+          "bond"
+          "bridge"
+          "dummy"
+          "gre"
+          "gretap"
+          "erspan"
+          "ip6gre"
+          "ip6tnl"
+          "ip6gretap"
+          "ipip"
+          "ipvlan"
+          "macvlan"
+          "macvtap"
+          "sit"
+          "tap"
+          "tun"
+          "veth"
+          "vlan"
+          "vti"
+          "vti6"
+          "vxlan"
+          "geneve"
+          "l2tp"
+          "macsec"
+          "vrf"
+          "vcan"
+          "vxcan"
+          "wireguard"
+          "netdevsim"
+          "nlmon"
+          "fou"
+          "xfrm"
+          "ifb"
+          "batadv"
+        ])
+        (assertByteFormat "MTUBytes")
+        (assertMacAddress "MACAddress")
+      ];
+
+      sectionVLAN = checkUnitConfig "VLAN" [
+        (assertOnlyFields [
+          "Id"
+          "GVRP"
+          "MVRP"
+          "LooseBinding"
+          "ReorderHeader"
+        ])
+        (assertInt "Id")
+        (assertRange "Id" 0 4094)
+        (assertValueOneOf "GVRP" boolValues)
+        (assertValueOneOf "MVRP" boolValues)
+        (assertValueOneOf "LooseBinding" boolValues)
+        (assertValueOneOf "ReorderHeader" boolValues)
+      ];
+
+      sectionMACVLAN = checkUnitConfig "MACVLAN" [
+        (assertOnlyFields [
+          "Mode"
+        ])
+        (assertValueOneOf "Mode" ["private" "vepa" "bridge" "passthru"])
+      ];
+
+      sectionVXLAN = checkUnitConfig "VXLAN" [
+        (assertOnlyFields [
+          "VNI"
+          "Remote"
+          "Local"
+          "Group"
+          "TOS"
+          "TTL"
+          "MacLearning"
+          "FDBAgeingSec"
+          "MaximumFDBEntries"
+          "ReduceARPProxy"
+          "L2MissNotification"
+          "L3MissNotification"
+          "RouteShortCircuit"
+          "UDPChecksum"
+          "UDP6ZeroChecksumTx"
+          "UDP6ZeroChecksumRx"
+          "RemoteChecksumTx"
+          "RemoteChecksumRx"
+          "GroupPolicyExtension"
+          "GenericProtocolExtension"
+          "DestinationPort"
+          "PortRange"
+          "FlowLabel"
+          "IPDoNotFragment"
+        ])
+        (assertInt "VNI")
+        (assertRange "VNI" 1 16777215)
+        (assertValueOneOf "MacLearning" boolValues)
+        (assertInt "MaximumFDBEntries")
+        (assertValueOneOf "ReduceARPProxy" boolValues)
+        (assertValueOneOf "L2MissNotification" boolValues)
+        (assertValueOneOf "L3MissNotification" boolValues)
+        (assertValueOneOf "RouteShortCircuit" boolValues)
+        (assertValueOneOf "UDPChecksum" boolValues)
+        (assertValueOneOf "UDP6ZeroChecksumTx" boolValues)
+        (assertValueOneOf "UDP6ZeroChecksumRx" boolValues)
+        (assertValueOneOf "RemoteChecksumTx" boolValues)
+        (assertValueOneOf "RemoteChecksumRx" boolValues)
+        (assertValueOneOf "GroupPolicyExtension" boolValues)
+        (assertValueOneOf "GenericProtocolExtension" boolValues)
+        (assertInt "FlowLabel")
+        (assertRange "FlowLabel" 0 1048575)
+        (assertValueOneOf "IPDoNotFragment" (boolValues + ["inherit"]))
+      ];
+
+      sectionTunnel = checkUnitConfig "Tunnel" [
+        (assertOnlyFields [
+          "Local"
+          "Remote"
+          "TOS"
+          "TTL"
+          "DiscoverPathMTU"
+          "IPv6FlowLabel"
+          "CopyDSCP"
+          "EncapsulationLimit"
+          "Key"
+          "InputKey"
+          "OutputKey"
+          "Mode"
+          "Independent"
+          "AssignToLoopback"
+          "AllowLocalRemote"
+          "FooOverUDP"
+          "FOUDestinationPort"
+          "FOUSourcePort"
+          "Encapsulation"
+          "IPv6RapidDeploymentPrefix"
+          "ISATAP"
+          "SerializeTunneledPackets"
+          "ERSPANIndex"
+        ])
+        (assertInt "TTL")
+        (assertRange "TTL" 0 255)
+        (assertValueOneOf "DiscoverPathMTU" boolValues)
+        (assertValueOneOf "CopyDSCP" boolValues)
+        (assertValueOneOf "Mode" ["ip6ip6" "ipip6" "any"])
+        (assertValueOneOf "Independent" boolValues)
+        (assertValueOneOf "AssignToLoopback" boolValues)
+        (assertValueOneOf "AllowLocalRemote" boolValues)
+        (assertValueOneOf "FooOverUDP" boolValues)
+        (assertPort "FOUDestinationPort")
+        (assertPort "FOUSourcePort")
+        (assertValueOneOf "Encapsulation" ["FooOverUDP" "GenericUDPEncapsulation"])
+        (assertValueOneOf "ISATAP" boolValues)
+        (assertValueOneOf "SerializeTunneledPackets" boolValues)
+        (assertInt "ERSPANIndex")
+        (assertRange "ERSPANIndex" 1 1048575)
+      ];
+
+      sectionFooOverUDP = checkUnitConfig "FooOverUDP" [
+        (assertOnlyFields [
+          "Port"
+          "Encapsulation"
+          "Protocol"
+        ])
+        (assertPort "Port")
+        (assertValueOneOf "Encapsulation" ["FooOverUDP" "GenericUDPEncapsulation"])
+      ];
+
+      sectionPeer = checkUnitConfig "Peer" [
+        (assertOnlyFields [
+          "Name"
+          "MACAddress"
+        ])
+        (assertMacAddress "MACAddress")
+      ];
+
+      sectionTun = checkUnitConfig "Tun" tunChecks;
+
+      sectionTap = checkUnitConfig "Tap" tunChecks;
+
+      # NOTE The PrivateKey directive is missing on purpose here, please
+      # do not add it to this list. The nix store is world-readable let's
+      # refrain ourselves from providing a footgun.
+      sectionWireGuard = checkUnitConfig "WireGuard" [
+        (assertOnlyFields [
+          "PrivateKeyFile"
+          "ListenPort"
+          "FirewallMark"
+        ])
+        (assertInt "FirewallMark")
+        (assertRange "FirewallMark" 1 4294967295)
+      ];
+
+      # NOTE The PresharedKey directive is missing on purpose here, please
+      # do not add it to this list. The nix store is world-readable,let's
+      # refrain ourselves from providing a footgun.
+      sectionWireGuardPeer = checkUnitConfig "WireGuardPeer" [
+        (assertOnlyFields [
+          "PublicKey"
+          "PresharedKeyFile"
+          "AllowedIPs"
+          "Endpoint"
+          "PersistentKeepalive"
+        ])
+        (assertInt "PersistentKeepalive")
+        (assertRange "PersistentKeepalive" 0 65535)
+      ];
+
+      sectionBond = checkUnitConfig "Bond" [
+        (assertOnlyFields [
+          "Mode"
+          "TransmitHashPolicy"
+          "LACPTransmitRate"
+          "MIIMonitorSec"
+          "UpDelaySec"
+          "DownDelaySec"
+          "LearnPacketIntervalSec"
+          "AdSelect"
+          "AdActorSystemPriority"
+          "AdUserPortKey"
+          "AdActorSystem"
+          "FailOverMACPolicy"
+          "ARPValidate"
+          "ARPIntervalSec"
+          "ARPIPTargets"
+          "ARPAllTargets"
+          "PrimaryReselectPolicy"
+          "ResendIGMP"
+          "PacketsPerSlave"
+          "GratuitousARP"
+          "AllSlavesActive"
+          "DynamicTransmitLoadBalancing"
+          "MinLinks"
+        ])
+        (assertValueOneOf "Mode" [
+          "balance-rr"
+          "active-backup"
+          "balance-xor"
+          "broadcast"
+          "802.3ad"
+          "balance-tlb"
+          "balance-alb"
+        ])
+        (assertValueOneOf "TransmitHashPolicy" [
+          "layer2"
+          "layer3+4"
+          "layer2+3"
+          "encap2+3"
+          "encap3+4"
+        ])
+        (assertValueOneOf "LACPTransmitRate" ["slow" "fast"])
+        (assertValueOneOf "AdSelect" ["stable" "bandwidth" "count"])
+        (assertInt "AdActorSystemPriority")
+        (assertRange "AdActorSystemPriority" 1 65535)
+        (assertInt "AdUserPortKey")
+        (assertRange "AdUserPortKey" 0 1023)
+        (assertValueOneOf "FailOverMACPolicy" ["none" "active" "follow"])
+        (assertValueOneOf "ARPValidate" ["none" "active" "backup" "all"])
+        (assertValueOneOf "ARPAllTargets" ["any" "all"])
+        (assertValueOneOf "PrimaryReselectPolicy" ["always" "better" "failure"])
+        (assertInt "ResendIGMP")
+        (assertRange "ResendIGMP" 0 255)
+        (assertInt "PacketsPerSlave")
+        (assertRange "PacketsPerSlave" 0 65535)
+        (assertInt "GratuitousARP")
+        (assertRange "GratuitousARP" 0 255)
+        (assertValueOneOf "AllSlavesActive" boolValues)
+        (assertValueOneOf "DynamicTransmitLoadBalancing" boolValues)
+        (assertInt "MinLinks")
+        (assertMinimum "MinLinks" 0)
+      ];
+
+      sectionXfrm = checkUnitConfig "Xfrm" [
+        (assertOnlyFields [
+          "InterfaceId"
+          "Independent"
+        ])
+        (assertInt "InterfaceId")
+        (assertRange "InterfaceId" 1 4294967295)
+        (assertValueOneOf "Independent" boolValues)
+      ];
+
+      sectionVRF = checkUnitConfig "VRF" [
+        (assertOnlyFields [
+          "Table"
+        ])
+        (assertInt "Table")
+        (assertMinimum "Table" 0)
+      ];
+
+      sectionBatmanAdvanced = checkUnitConfig "BatmanAdvanced" [
+        (assertOnlyFields [
+          "GatewayMode"
+          "Aggregation"
+          "BridgeLoopAvoidance"
+          "DistributedArpTable"
+          "Fragmentation"
+          "HopPenalty"
+          "OriginatorIntervalSec"
+          "GatewayBandwithDown"
+          "GatewayBandwithUp"
+          "RoutingAlgorithm"
+        ])
+        (assertValueOneOf "GatewayMode" ["off" "client" "server"])
+        (assertValueOneOf "Aggregation" boolValues)
+        (assertValueOneOf "BridgeLoopAvoidance" boolValues)
+        (assertValueOneOf "DistributedArpTable" boolValues)
+        (assertValueOneOf "Fragmentation" boolValues)
+        (assertInt "HopPenalty")
+        (assertRange "HopPenalty" 0 255)
+        (assertValueOneOf "RoutingAlgorithm" ["batman-v" "batman-iv"])
+      ];
+    };
+
+    network = {
+
+      sectionLink = checkUnitConfig "Link" [
+        (assertOnlyFields [
+          "MACAddress"
+          "MTUBytes"
+          "ARP"
+          "Multicast"
+          "AllMulticast"
+          "Unmanaged"
+          "RequiredForOnline"
+          "ActivationPolicy"
+        ])
+        (assertMacAddress "MACAddress")
+        (assertByteFormat "MTUBytes")
+        (assertValueOneOf "ARP" boolValues)
+        (assertValueOneOf "Multicast" boolValues)
+        (assertValueOneOf "AllMulticast" boolValues)
+        (assertValueOneOf "Unmanaged" boolValues)
+        (assertValueOneOf "RequiredForOnline" (boolValues ++ [
+          "missing"
+          "off"
+          "no-carrier"
+          "dormant"
+          "degraded-carrier"
+          "carrier"
+          "degraded"
+          "enslaved"
+          "routable"
+        ]))
+        (assertValueOneOf "ActivationPolicy" ([
+          "up"
+          "always-up"
+          "manual"
+          "always-down"
+          "down"
+          "bound"
+        ]))
+      ];
+
+      sectionNetwork = checkUnitConfig "Network" [
+        (assertOnlyFields [
+          "Description"
+          "DHCP"
+          "DHCPServer"
+          "LinkLocalAddressing"
+          "IPv4LLRoute"
+          "DefaultRouteOnDevice"
+          "IPv6Token"
+          "LLMNR"
+          "MulticastDNS"
+          "DNSOverTLS"
+          "DNSSEC"
+          "DNSSECNegativeTrustAnchors"
+          "LLDP"
+          "EmitLLDP"
+          "BindCarrier"
+          "Address"
+          "Gateway"
+          "DNS"
+          "Domains"
+          "DNSDefaultRoute"
+          "NTP"
+          "IPForward"
+          "IPMasquerade"
+          "IPv6PrivacyExtensions"
+          "IPv6AcceptRA"
+          "IPv6DuplicateAddressDetection"
+          "IPv6HopLimit"
+          "IPv4ProxyARP"
+          "IPv6ProxyNDP"
+          "IPv6ProxyNDPAddress"
+          "IPv6SendRA"
+          "DHCPv6PrefixDelegation"
+          "IPv6MTUBytes"
+          "Bridge"
+          "Bond"
+          "VRF"
+          "VLAN"
+          "IPVLAN"
+          "MACVLAN"
+          "VXLAN"
+          "Tunnel"
+          "MACsec"
+          "ActiveSlave"
+          "PrimarySlave"
+          "ConfigureWithoutCarrier"
+          "IgnoreCarrierLoss"
+          "Xfrm"
+          "KeepConfiguration"
+          "BatmanAdvanced"
+        ])
+        # Note: For DHCP the values both, none, v4, v6 are deprecated
+        (assertValueOneOf "DHCP" ["yes" "no" "ipv4" "ipv6"])
+        (assertValueOneOf "DHCPServer" boolValues)
+        (assertValueOneOf "LinkLocalAddressing" ["yes" "no" "ipv4" "ipv6" "fallback" "ipv4-fallback"])
+        (assertValueOneOf "IPv4LLRoute" boolValues)
+        (assertValueOneOf "DefaultRouteOnDevice" boolValues)
+        (assertValueOneOf "LLMNR" (boolValues ++ ["resolve"]))
+        (assertValueOneOf "MulticastDNS" (boolValues ++ ["resolve"]))
+        (assertValueOneOf "DNSOverTLS" (boolValues ++ ["opportunistic"]))
+        (assertValueOneOf "DNSSEC" (boolValues ++ ["allow-downgrade"]))
+        (assertValueOneOf "LLDP" (boolValues ++ ["routers-only"]))
+        (assertValueOneOf "EmitLLDP" (boolValues ++ ["nearest-bridge" "non-tpmr-bridge" "customer-bridge"]))
+        (assertValueOneOf "DNSDefaultRoute" boolValues)
+        (assertValueOneOf "IPForward" (boolValues ++ ["ipv4" "ipv6"]))
+        (assertValueOneOf "IPMasquerade" (boolValues ++ ["ipv4" "ipv6" "both"]))
+        (assertValueOneOf "IPv6PrivacyExtensions" (boolValues ++ ["prefer-public" "kernel"]))
+        (assertValueOneOf "IPv6AcceptRA" boolValues)
+        (assertInt "IPv6DuplicateAddressDetection")
+        (assertMinimum "IPv6DuplicateAddressDetection" 0)
+        (assertInt "IPv6HopLimit")
+        (assertMinimum "IPv6HopLimit" 0)
+        (assertValueOneOf "IPv4ProxyARP" boolValues)
+        (assertValueOneOf "IPv6ProxyNDP" boolValues)
+        (assertValueOneOf "IPv6SendRA" boolValues)
+        (assertValueOneOf "DHCPv6PrefixDelegation" boolValues)
+        (assertByteFormat "IPv6MTUBytes")
+        (assertValueOneOf "ActiveSlave" boolValues)
+        (assertValueOneOf "PrimarySlave" boolValues)
+        (assertValueOneOf "ConfigureWithoutCarrier" boolValues)
+        (assertValueOneOf "IgnoreCarrierLoss" boolValues)
+        (assertValueOneOf "KeepConfiguration" (boolValues ++ ["static" "dhcp-on-stop" "dhcp"]))
+      ];
+
+      sectionAddress = checkUnitConfig "Address" [
+        (assertOnlyFields [
+          "Address"
+          "Peer"
+          "Broadcast"
+          "Label"
+          "PreferredLifetime"
+          "Scope"
+          "HomeAddress"
+          "DuplicateAddressDetection"
+          "ManageTemporaryAddress"
+          "AddPrefixRoute"
+          "AutoJoin"
+        ])
+        (assertHasField "Address")
+        (assertValueOneOf "PreferredLifetime" ["forever" "infinity" "0" 0])
+        (assertValueOneOf "HomeAddress" boolValues)
+        (assertValueOneOf "DuplicateAddressDetection" ["ipv4" "ipv6" "both" "none"])
+        (assertValueOneOf "ManageTemporaryAddress" boolValues)
+        (assertValueOneOf "AddPrefixRoute" boolValues)
+        (assertValueOneOf "AutoJoin" boolValues)
+      ];
+
+      sectionRoutingPolicyRule = checkUnitConfig "RoutingPolicyRule" [
+        (assertOnlyFields [
+          "TypeOfService"
+          "From"
+          "To"
+          "FirewallMark"
+          "Table"
+          "Priority"
+          "IncomingInterface"
+          "OutgoingInterface"
+          "SourcePort"
+          "DestinationPort"
+          "IPProtocol"
+          "InvertRule"
+          "Family"
+          "User"
+          "SuppressPrefixLength"
+          "Type"
+        ])
+        (assertInt "TypeOfService")
+        (assertRange "TypeOfService" 0 255)
+        (assertInt "FirewallMark")
+        (assertRange "FirewallMark" 1 4294967295)
+        (assertInt "Priority")
+        (assertPort "SourcePort")
+        (assertPort "DestinationPort")
+        (assertValueOneOf "InvertRule" boolValues)
+        (assertValueOneOf "Family" ["ipv4" "ipv6" "both"])
+        (assertInt "SuppressPrefixLength")
+        (assertRange "SuppressPrefixLength" 0 128)
+        (assertValueOneOf "Type" ["blackhole" "unreachable" "prohibit"])
+      ];
+
+      sectionRoute = checkUnitConfig "Route" [
+        (assertOnlyFields [
+          "Gateway"
+          "GatewayOnLink"
+          "Destination"
+          "Source"
+          "Metric"
+          "IPv6Preference"
+          "Scope"
+          "PreferredSource"
+          "Table"
+          "Protocol"
+          "Type"
+          "InitialCongestionWindow"
+          "InitialAdvertisedReceiveWindow"
+          "QuickAck"
+          "FastOpenNoCookie"
+          "TTLPropagate"
+          "MTUBytes"
+          "IPServiceType"
+          "MultiPathRoute"
+        ])
+        (assertValueOneOf "GatewayOnLink" boolValues)
+        (assertInt "Metric")
+        (assertValueOneOf "IPv6Preference" ["low" "medium" "high"])
+        (assertValueOneOf "Scope" ["global" "site" "link" "host" "nowhere"])
+        (assertValueOneOf "Type" [
+          "unicast"
+          "local"
+          "broadcast"
+          "anycast"
+          "multicast"
+          "blackhole"
+          "unreachable"
+          "prohibit"
+          "throw"
+          "nat"
+          "xresolve"
+        ])
+        (assertValueOneOf "QuickAck" boolValues)
+        (assertValueOneOf "FastOpenNoCookie" boolValues)
+        (assertValueOneOf "TTLPropagate" boolValues)
+        (assertByteFormat "MTUBytes")
+        (assertValueOneOf "IPServiceType" ["CS6" "CS4"])
+      ];
+
+      sectionDHCPv4 = checkUnitConfig "DHCPv4" [
+        (assertOnlyFields [
+          "UseDNS"
+          "RoutesToDNS"
+          "UseNTP"
+          "UseSIP"
+          "UseMTU"
+          "Anonymize"
+          "SendHostname"
+          "UseHostname"
+          "Hostname"
+          "UseDomains"
+          "UseRoutes"
+          "UseTimezone"
+          "ClientIdentifier"
+          "VendorClassIdentifier"
+          "UserClass"
+          "MaxAttempts"
+          "DUIDType"
+          "DUIDRawData"
+          "IAID"
+          "RequestBroadcast"
+          "RouteMetric"
+          "RouteTable"
+          "RouteMTUBytes"
+          "ListenPort"
+          "SendRelease"
+          "SendDecline"
+          "BlackList"
+          "RequestOptions"
+          "SendOption"
+        ])
+        (assertValueOneOf "UseDNS" boolValues)
+        (assertValueOneOf "RoutesToDNS" boolValues)
+        (assertValueOneOf "UseNTP" boolValues)
+        (assertValueOneOf "UseSIP" boolValues)
+        (assertValueOneOf "UseMTU" boolValues)
+        (assertValueOneOf "Anonymize" boolValues)
+        (assertValueOneOf "SendHostname" boolValues)
+        (assertValueOneOf "UseHostname" boolValues)
+        (assertValueOneOf "UseDomains" (boolValues ++ ["route"]))
+        (assertValueOneOf "UseRoutes" boolValues)
+        (assertValueOneOf "UseTimezone" boolValues)
+        (assertValueOneOf "ClientIdentifier" ["mac" "duid" "duid-only"])
+        (assertInt "IAID")
+        (assertValueOneOf "RequestBroadcast" boolValues)
+        (assertInt "RouteMetric")
+        (assertInt "RouteTable")
+        (assertRange "RouteTable" 0 4294967295)
+        (assertByteFormat "RouteMTUBytes")
+        (assertPort "ListenPort")
+        (assertValueOneOf "SendRelease" boolValues)
+        (assertValueOneOf "SendDecline" boolValues)
+      ];
+
+      sectionDHCPv6 = checkUnitConfig "DHCPv6" [
+        (assertOnlyFields [
+          "UseAddress"
+          "UseDNS"
+          "UseNTP"
+          "RouteMetric"
+          "RapidCommit"
+          "MUDURL"
+          "RequestOptions"
+          "SendVendorOption"
+          "ForceDHCPv6PDOtherInformation"
+          "PrefixDelegationHint"
+          "WithoutRA"
+          "SendOption"
+          "UserClass"
+          "VendorClass"
+          "DUIDType"
+          "DUIDRawData"
+          "IAID"
+        ])
+        (assertValueOneOf "UseAddress" boolValues)
+        (assertValueOneOf "UseDNS" boolValues)
+        (assertValueOneOf "UseNTP" boolValues)
+        (assertInt "RouteMetric")
+        (assertValueOneOf "RapidCommit" boolValues)
+        (assertValueOneOf "ForceDHCPv6PDOtherInformation" boolValues)
+        (assertValueOneOf "WithoutRA" ["solicit" "information-request"])
+        (assertRange "SendOption" 1 65536)
+        (assertInt "IAID")
+      ];
+
+      sectionDHCPv6PrefixDelegation = checkUnitConfig "DHCPv6PrefixDelegation" [
+        (assertOnlyFields [
+          "SubnetId"
+          "Announce"
+          "Assign"
+          "Token"
+        ])
+        (assertValueOneOf "Announce" boolValues)
+        (assertValueOneOf "Assign" boolValues)
+      ];
+
+      sectionIPv6AcceptRA = checkUnitConfig "IPv6AcceptRA" [
+        (assertOnlyFields [
+          "UseDNS"
+          "UseDomains"
+          "RouteTable"
+          "UseAutonomousPrefix"
+          "UseOnLinkPrefix"
+          "RouterDenyList"
+          "RouterAllowList"
+          "PrefixDenyList"
+          "PrefixAllowList"
+          "RouteDenyList"
+          "RouteAllowList"
+          "DHCPv6Client"
+        ])
+        (assertValueOneOf "UseDNS" boolValues)
+        (assertValueOneOf "UseDomains" (boolValues ++ ["route"]))
+        (assertRange "RouteTable" 0 4294967295)
+        (assertValueOneOf "UseAutonomousPrefix" boolValues)
+        (assertValueOneOf "UseOnLinkPrefix" boolValues)
+        (assertValueOneOf "DHCPv6Client" (boolValues ++ ["always"]))
+      ];
+
+      sectionDHCPServer = checkUnitConfig "DHCPServer" [
+        (assertOnlyFields [
+          "PoolOffset"
+          "PoolSize"
+          "DefaultLeaseTimeSec"
+          "MaxLeaseTimeSec"
+          "EmitDNS"
+          "DNS"
+          "EmitNTP"
+          "NTP"
+          "EmitSIP"
+          "SIP"
+          "EmitPOP3"
+          "POP3"
+          "EmitSMTP"
+          "SMTP"
+          "EmitLPR"
+          "LPR"
+          "EmitRouter"
+          "EmitTimezone"
+          "Timezone"
+          "SendOption"
+          "SendVendorOption"
+        ])
+        (assertInt "PoolOffset")
+        (assertMinimum "PoolOffset" 0)
+        (assertInt "PoolSize")
+        (assertMinimum "PoolSize" 0)
+        (assertValueOneOf "EmitDNS" boolValues)
+        (assertValueOneOf "EmitNTP" boolValues)
+        (assertValueOneOf "EmitSIP" boolValues)
+        (assertValueOneOf "EmitPOP3" boolValues)
+        (assertValueOneOf "EmitSMTP" boolValues)
+        (assertValueOneOf "EmitLPR" boolValues)
+        (assertValueOneOf "EmitRouter" boolValues)
+        (assertValueOneOf "EmitTimezone" boolValues)
+      ];
+
+      sectionIPv6SendRA = checkUnitConfig "IPv6SendRA" [
+        (assertOnlyFields [
+          "Managed"
+          "OtherInformation"
+          "RouterLifetimeSec"
+          "RouterPreference"
+          "EmitDNS"
+          "DNS"
+          "EmitDomains"
+          "Domains"
+          "DNSLifetimeSec"
+        ])
+        (assertValueOneOf "Managed" boolValues)
+        (assertValueOneOf "OtherInformation" boolValues)
+        (assertValueOneOf "RouterPreference" ["high" "medium" "low" "normal" "default"])
+        (assertValueOneOf "EmitDNS" boolValues)
+        (assertValueOneOf "EmitDomains" boolValues)
+      ];
+
+      sectionIPv6Prefix = checkUnitConfig "IPv6Prefix" [
+        (assertOnlyFields [
+          "AddressAutoconfiguration"
+          "OnLink"
+          "Prefix"
+          "PreferredLifetimeSec"
+          "ValidLifetimeSec"
+        ])
+        (assertValueOneOf "AddressAutoconfiguration" boolValues)
+        (assertValueOneOf "OnLink" boolValues)
+      ];
+
+      sectionDHCPServerStaticLease = checkUnitConfig "DHCPServerStaticLease" [
+        (assertOnlyFields [
+          "MACAddress"
+          "Address"
+        ])
+        (assertHasField "MACAddress")
+        (assertHasField "Address")
+        (assertMacAddress "MACAddress")
+      ];
+
+    };
+  };
+
+  commonNetworkOptions = {
+
+    enable = mkOption {
+      default = true;
+      type = types.bool;
+      description = ''
+        Whether to manage network configuration using <command>systemd-network</command>.
+      '';
+    };
+
+    matchConfig = mkOption {
+      default = {};
+      example = { Name = "eth0"; };
+      type = types.attrsOf unitOption;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[Match]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.link</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        <citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        for details.
+      '';
+    };
+
+    extraConfig = mkOption {
+      default = "";
+      type = types.lines;
+      description = "Extra configuration append to unit";
+    };
+  };
+
+  linkOptions = commonNetworkOptions // {
+    # overwrite enable option from above
+    enable = mkOption {
+      default = true;
+      type = types.bool;
+      description = ''
+        Whether to enable this .link unit. It's handled by udev no matter if <command>systemd-networkd</command> is enabled or not
+      '';
+    };
+
+    linkConfig = mkOption {
+      default = {};
+      example = { MACAddress = "00:ff:ee:aa:cc:dd"; };
+      type = types.addCheck (types.attrsOf unitOption) check.link.sectionLink;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[Link]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.link</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+  };
+
+  wireguardPeerOptions = {
+    options = {
+      wireguardPeerConfig = mkOption {
+        default = {};
+        type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionWireGuardPeer;
+        description = ''
+          Each attribute in this set specifies an option in the
+          <literal>[WireGuardPeer]</literal> section of the unit.  See
+          <citerefentry><refentrytitle>systemd.network</refentrytitle>
+          <manvolnum>5</manvolnum></citerefentry> for details.
+        '';
+      };
+    };
+  };
+
+  netdevOptions = commonNetworkOptions // {
+
+    netdevConfig = mkOption {
+      example = { Name = "mybridge"; Kind = "bridge"; };
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionNetdev;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[Netdev]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    vlanConfig = mkOption {
+      default = {};
+      example = { Id = 4; };
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionVLAN;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[VLAN]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    macvlanConfig = mkOption {
+      default = {};
+      example = { Mode = "private"; };
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionMACVLAN;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[MACVLAN]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    vxlanConfig = mkOption {
+      default = {};
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionVXLAN;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[VXLAN]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    tunnelConfig = mkOption {
+      default = {};
+      example = { Remote = "192.168.1.1"; };
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionTunnel;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[Tunnel]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    fooOverUDPConfig = mkOption {
+      default = { };
+      example = { Port = 9001; };
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionFooOverUDP;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[FooOverUDP]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    peerConfig = mkOption {
+      default = {};
+      example = { Name = "veth2"; };
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionPeer;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[Peer]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    tunConfig = mkOption {
+      default = {};
+      example = { User = "openvpn"; };
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionTun;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[Tun]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    tapConfig = mkOption {
+      default = {};
+      example = { User = "openvpn"; };
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionTap;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[Tap]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    wireguardConfig = mkOption {
+      default = {};
+      example = {
+        PrivateKeyFile = "/etc/wireguard/secret.key";
+        ListenPort = 51820;
+        FirewallMark = 42;
+      };
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionWireGuard;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[WireGuard]</literal> section of the unit. See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+        Use <literal>PrivateKeyFile</literal> instead of
+        <literal>PrivateKey</literal>: the nix store is
+        world-readable.
+      '';
+    };
+
+    wireguardPeers = mkOption {
+      default = [];
+      example = [ { wireguardPeerConfig={
+        Endpoint = "192.168.1.1:51820";
+        PublicKey = "27s0OvaBBdHoJYkH9osZpjpgSOVNw+RaKfboT/Sfq0g=";
+        PresharedKeyFile = "/etc/wireguard/psk.key";
+        AllowedIPs = [ "10.0.0.1/32" ];
+        PersistentKeepalive = 15;
+      };}];
+      type = with types; listOf (submodule wireguardPeerOptions);
+      description = ''
+        Each item in this array specifies an option in the
+        <literal>[WireGuardPeer]</literal> section of the unit. See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+        Use <literal>PresharedKeyFile</literal> instead of
+        <literal>PresharedKey</literal>: the nix store is
+        world-readable.
+      '';
+    };
+
+    bondConfig = mkOption {
+      default = {};
+      example = { Mode = "802.3ad"; };
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionBond;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[Bond]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    xfrmConfig = mkOption {
+      default = {};
+      example = { InterfaceId = 1; };
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionXfrm;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[Xfrm]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    vrfConfig = mkOption {
+      default = {};
+      example = { Table = 2342; };
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionVRF;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[VRF]</literal> section of the unit. See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+        A detailed explanation about how VRFs work can be found in the
+        <link xlink:href="https://www.kernel.org/doc/Documentation/networking/vrf.txt">kernel
+        docs</link>.
+      '';
+    };
+
+    batmanAdvancedConfig = mkOption {
+      default = {};
+      example = {
+        GatewayMode = "server";
+        RoutingAlgorithm = "batman-v";
+      };
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionBatmanAdvanced;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[BatmanAdvanced]</literal> section of the unit. See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+  };
+
+  addressOptions = {
+    options = {
+      addressConfig = mkOption {
+        example = { Address = "192.168.0.100/24"; };
+        type = types.addCheck (types.attrsOf unitOption) check.network.sectionAddress;
+        description = ''
+          Each attribute in this set specifies an option in the
+          <literal>[Address]</literal> section of the unit.  See
+          <citerefentry><refentrytitle>systemd.network</refentrytitle>
+          <manvolnum>5</manvolnum></citerefentry> for details.
+        '';
+      };
+    };
+  };
+
+  routingPolicyRulesOptions = {
+    options = {
+      routingPolicyRuleConfig = mkOption {
+        default = { };
+        example = { Table = 10; IncomingInterface = "eth1"; Family = "both"; };
+        type = types.addCheck (types.attrsOf unitOption) check.network.sectionRoutingPolicyRule;
+        description = ''
+          Each attribute in this set specifies an option in the
+          <literal>[RoutingPolicyRule]</literal> section of the unit.  See
+          <citerefentry><refentrytitle>systemd.network</refentrytitle>
+          <manvolnum>5</manvolnum></citerefentry> for details.
+        '';
+      };
+    };
+  };
+
+  routeOptions = {
+    options = {
+      routeConfig = mkOption {
+        default = {};
+        example = { Gateway = "192.168.0.1"; };
+        type = types.addCheck (types.attrsOf unitOption) check.network.sectionRoute;
+        description = ''
+          Each attribute in this set specifies an option in the
+          <literal>[Route]</literal> section of the unit.  See
+          <citerefentry><refentrytitle>systemd.network</refentrytitle>
+          <manvolnum>5</manvolnum></citerefentry> for details.
+        '';
+      };
+    };
+  };
+
+  ipv6PrefixOptions = {
+    options = {
+      ipv6PrefixConfig = mkOption {
+        default = {};
+        example = { Prefix = "fd00::/64"; };
+        type = types.addCheck (types.attrsOf unitOption) check.network.sectionIPv6Prefix;
+        description = ''
+          Each attribute in this set specifies an option in the
+          <literal>[IPv6Prefix]</literal> section of the unit.  See
+          <citerefentry><refentrytitle>systemd.network</refentrytitle>
+          <manvolnum>5</manvolnum></citerefentry> for details.
+        '';
+      };
+    };
+  };
+
+  dhcpServerStaticLeaseOptions = {
+    options = {
+      dhcpServerStaticLeaseConfig = mkOption {
+        default = {};
+        example = { MACAddress = "65:43:4a:5b:d8:5f"; Address = "192.168.1.42"; };
+        type = types.addCheck (types.attrsOf unitOption) check.network.sectionDHCPServerStaticLease;
+        description = ''
+          Each attribute in this set specifies an option in the
+          <literal>[DHCPServerStaticLease]</literal> section of the unit.  See
+          <citerefentry><refentrytitle>systemd.network</refentrytitle>
+          <manvolnum>5</manvolnum></citerefentry> for details.
+
+          Make sure to configure the corresponding client interface to use
+          <literal>ClientIdentifier=mac</literal>.
+        '';
+      };
+    };
+  };
+
+  networkOptions = commonNetworkOptions // {
+
+    linkConfig = mkOption {
+      default = {};
+      example = { Unmanaged = true; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionLink;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[Link]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    networkConfig = mkOption {
+      default = {};
+      example = { Description = "My Network"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionNetwork;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[Network]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    # systemd.network.networks.*.dhcpConfig has been deprecated in favor of ….dhcpV4Config
+    # Produce a nice warning message so users know it is gone.
+    dhcpConfig = mkOption {
+      visible = false;
+      apply = _: throw "The option `systemd.network.networks.*.dhcpConfig` can no longer be used since it's been removed. Please use `systemd.network.networks.*.dhcpV4Config` instead.";
+    };
+
+    dhcpV4Config = mkOption {
+      default = {};
+      example = { UseDNS = true; UseRoutes = true; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionDHCPv4;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[DHCPv4]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    dhcpV6Config = mkOption {
+      default = {};
+      example = { UseDNS = true; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionDHCPv6;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[DHCPv6]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    dhcpV6PrefixDelegationConfig = mkOption {
+      default = {};
+      example = { SubnetId = "auto"; Announce = true; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionDHCPv6PrefixDelegation;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[DHCPv6PrefixDelegation]</literal> section of the unit. See
+        <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    ipv6AcceptRAConfig = mkOption {
+      default = {};
+      example = { UseDNS = true; DHCPv6Client = "always"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionIPv6AcceptRA;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[IPv6AcceptRA]</literal> section of the unit. See
+        <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    dhcpServerConfig = mkOption {
+      default = {};
+      example = { PoolOffset = 50; EmitDNS = false; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionDHCPServer;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[DHCPServer]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    # systemd.network.networks.*.ipv6PrefixDelegationConfig has been deprecated
+    # in 247 in favor of systemd.network.networks.*.ipv6SendRAConfig.
+    ipv6PrefixDelegationConfig = mkOption {
+      visible = false;
+      apply = _: throw "The option `systemd.network.networks.*.ipv6PrefixDelegationConfig` has been replaced by `systemd.network.networks.*.ipv6SendRAConfig`.";
+    };
+
+    ipv6SendRAConfig = mkOption {
+      default = {};
+      example = { EmitDNS = true; Managed = true; OtherInformation = true; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionIPv6SendRA;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[IPv6SendRA]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    dhcpServerStaticLeases = mkOption {
+      default = [];
+      example = [ { MACAddress = "65:43:4a:5b:d8:5f"; Address = "192.168.1.42"; } ];
+      type = with types; listOf (submodule dhcpServerStaticLeaseOptions);
+      description = ''
+        A list of DHCPServerStaticLease sections to be added to the unit.  See
+        <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    ipv6Prefixes = mkOption {
+      default = [];
+      example = [ { AddressAutoconfiguration = true; OnLink = true; } ];
+      type = with types; listOf (submodule ipv6PrefixOptions);
+      description = ''
+        A list of ipv6Prefix sections to be added to the unit.  See
+        <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    name = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        The name of the network interface to match against.
+      '';
+    };
+
+    DHCP = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        Whether to enable DHCP on the interfaces matched.
+      '';
+    };
+
+    domains = mkOption {
+      type = types.nullOr (types.listOf types.str);
+      default = null;
+      description = ''
+        A list of domains to pass to the network config.
+      '';
+    };
+
+    address = mkOption {
+      default = [ ];
+      type = types.listOf types.str;
+      description = ''
+        A list of addresses to be added to the network section of the
+        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    gateway = mkOption {
+      default = [ ];
+      type = types.listOf types.str;
+      description = ''
+        A list of gateways to be added to the network section of the
+        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    dns = mkOption {
+      default = [ ];
+      type = types.listOf types.str;
+      description = ''
+        A list of dns servers to be added to the network section of the
+        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    ntp = mkOption {
+      default = [ ];
+      type = types.listOf types.str;
+      description = ''
+        A list of ntp servers to be added to the network section of the
+        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    bridge = mkOption {
+      default = [ ];
+      type = types.listOf types.str;
+      description = ''
+        A list of bridge interfaces to be added to the network section of the
+        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    bond = mkOption {
+      default = [ ];
+      type = types.listOf types.str;
+      description = ''
+        A list of bond interfaces to be added to the network section of the
+        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    vrf = mkOption {
+      default = [ ];
+      type = types.listOf types.str;
+      description = ''
+        A list of vrf interfaces to be added to the network section of the
+        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    vlan = mkOption {
+      default = [ ];
+      type = types.listOf types.str;
+      description = ''
+        A list of vlan interfaces to be added to the network section of the
+        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    macvlan = mkOption {
+      default = [ ];
+      type = types.listOf types.str;
+      description = ''
+        A list of macvlan interfaces to be added to the network section of the
+        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    vxlan = mkOption {
+      default = [ ];
+      type = types.listOf types.str;
+      description = ''
+        A list of vxlan interfaces to be added to the network section of the
+        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    tunnel = mkOption {
+      default = [ ];
+      type = types.listOf types.str;
+      description = ''
+        A list of tunnel interfaces to be added to the network section of the
+        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    xfrm = mkOption {
+      default = [ ];
+      type = types.listOf types.str;
+      description = ''
+        A list of xfrm interfaces to be added to the network section of the
+        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    addresses = mkOption {
+      default = [ ];
+      type = with types; listOf (submodule addressOptions);
+      description = ''
+        A list of address sections to be added to the unit.  See
+        <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    routingPolicyRules = mkOption {
+      default = [ ];
+      type = with types; listOf (submodule routingPolicyRulesOptions);
+      description = ''
+        A list of routing policy rules sections to be added to the unit.  See
+        <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    routes = mkOption {
+      default = [ ];
+      type = with types; listOf (submodule routeOptions);
+      description = ''
+        A list of route sections to be added to the unit.  See
+        <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+  };
+
+  networkConfig = { config, ... }: {
+    config = {
+      matchConfig = optionalAttrs (config.name != null) {
+        Name = config.name;
+      };
+      networkConfig = optionalAttrs (config.DHCP != null) {
+        DHCP = config.DHCP;
+      } // optionalAttrs (config.domains != null) {
+        Domains = concatStringsSep " " config.domains;
+      };
+    };
+  };
+
+  commonMatchText = def: optionalString (def.matchConfig != { }) ''
+    [Match]
+    ${attrsToSection def.matchConfig}
+  '';
+
+  linkToUnit = name: def:
+    { inherit (def) enable;
+      text = commonMatchText def
+        + ''
+          [Link]
+          ${attrsToSection def.linkConfig}
+        ''
+        + def.extraConfig;
+    };
+
+  netdevToUnit = name: def:
+    { inherit (def) enable;
+      text = commonMatchText def
+        + ''
+          [NetDev]
+          ${attrsToSection def.netdevConfig}
+        ''
+        + optionalString (def.vlanConfig != { }) ''
+          [VLAN]
+          ${attrsToSection def.vlanConfig}
+        ''
+        + optionalString (def.macvlanConfig != { }) ''
+          [MACVLAN]
+          ${attrsToSection def.macvlanConfig}
+        ''
+        + optionalString (def.vxlanConfig != { }) ''
+          [VXLAN]
+          ${attrsToSection def.vxlanConfig}
+        ''
+        + optionalString (def.tunnelConfig != { }) ''
+          [Tunnel]
+          ${attrsToSection def.tunnelConfig}
+        ''
+        + optionalString (def.fooOverUDPConfig != { }) ''
+          [FooOverUDP]
+          ${attrsToSection def.fooOverUDPConfig}
+        ''
+        + optionalString (def.peerConfig != { }) ''
+          [Peer]
+          ${attrsToSection def.peerConfig}
+        ''
+        + optionalString (def.tunConfig != { }) ''
+          [Tun]
+          ${attrsToSection def.tunConfig}
+        ''
+        + optionalString (def.tapConfig != { }) ''
+          [Tap]
+          ${attrsToSection def.tapConfig}
+        ''
+        + optionalString (def.wireguardConfig != { }) ''
+          [WireGuard]
+          ${attrsToSection def.wireguardConfig}
+        ''
+        + flip concatMapStrings def.wireguardPeers (x: ''
+          [WireGuardPeer]
+          ${attrsToSection x.wireguardPeerConfig}
+        '')
+        + optionalString (def.bondConfig != { }) ''
+          [Bond]
+          ${attrsToSection def.bondConfig}
+        ''
+        + optionalString (def.xfrmConfig != { }) ''
+          [Xfrm]
+          ${attrsToSection def.xfrmConfig}
+        ''
+        + optionalString (def.vrfConfig != { }) ''
+          [VRF]
+          ${attrsToSection def.vrfConfig}
+        ''
+        + optionalString (def.batmanAdvancedConfig != { }) ''
+          [BatmanAdvanced]
+          ${attrsToSection def.batmanAdvancedConfig}
+        ''
+        + def.extraConfig;
+    };
+
+  networkToUnit = name: def:
+    { inherit (def) enable;
+      text = commonMatchText def
+        + optionalString (def.linkConfig != { }) ''
+          [Link]
+          ${attrsToSection def.linkConfig}
+        ''
+        + ''
+          [Network]
+        ''
+        + attrsToSection def.networkConfig
+        + optionalString (def.address != [ ]) ''
+          ${concatStringsSep "\n" (map (s: "Address=${s}") def.address)}
+        ''
+        + optionalString (def.gateway != [ ]) ''
+          ${concatStringsSep "\n" (map (s: "Gateway=${s}") def.gateway)}
+        ''
+        + optionalString (def.dns != [ ]) ''
+          ${concatStringsSep "\n" (map (s: "DNS=${s}") def.dns)}
+        ''
+        + optionalString (def.ntp != [ ]) ''
+          ${concatStringsSep "\n" (map (s: "NTP=${s}") def.ntp)}
+        ''
+        + optionalString (def.bridge != [ ]) ''
+          ${concatStringsSep "\n" (map (s: "Bridge=${s}") def.bridge)}
+        ''
+        + optionalString (def.bond != [ ]) ''
+          ${concatStringsSep "\n" (map (s: "Bond=${s}") def.bond)}
+        ''
+        + optionalString (def.vrf != [ ]) ''
+          ${concatStringsSep "\n" (map (s: "VRF=${s}") def.vrf)}
+        ''
+        + optionalString (def.vlan != [ ]) ''
+          ${concatStringsSep "\n" (map (s: "VLAN=${s}") def.vlan)}
+        ''
+        + optionalString (def.macvlan != [ ]) ''
+          ${concatStringsSep "\n" (map (s: "MACVLAN=${s}") def.macvlan)}
+        ''
+        + optionalString (def.vxlan != [ ]) ''
+          ${concatStringsSep "\n" (map (s: "VXLAN=${s}") def.vxlan)}
+        ''
+        + optionalString (def.tunnel != [ ]) ''
+          ${concatStringsSep "\n" (map (s: "Tunnel=${s}") def.tunnel)}
+        ''
+        + optionalString (def.xfrm != [ ]) ''
+          ${concatStringsSep "\n" (map (s: "Xfrm=${s}") def.xfrm)}
+        ''
+        + ''
+
+        ''
+        + flip concatMapStrings def.addresses (x: ''
+          [Address]
+          ${attrsToSection x.addressConfig}
+        '')
+        + flip concatMapStrings def.routingPolicyRules (x: ''
+          [RoutingPolicyRule]
+          ${attrsToSection x.routingPolicyRuleConfig}
+        '')
+        + flip concatMapStrings def.routes (x: ''
+          [Route]
+          ${attrsToSection x.routeConfig}
+        '')
+        + optionalString (def.dhcpV4Config != { }) ''
+          [DHCPv4]
+          ${attrsToSection def.dhcpV4Config}
+        ''
+        + optionalString (def.dhcpV6Config != { }) ''
+          [DHCPv6]
+          ${attrsToSection def.dhcpV6Config}
+        ''
+        + optionalString (def.dhcpV6PrefixDelegationConfig != { }) ''
+          [DHCPv6PrefixDelegation]
+          ${attrsToSection def.dhcpV6PrefixDelegationConfig}
+        ''
+        + optionalString (def.ipv6AcceptRAConfig != { }) ''
+          [IPv6AcceptRA]
+          ${attrsToSection def.ipv6AcceptRAConfig}
+        ''
+        + optionalString (def.dhcpServerConfig != { }) ''
+          [DHCPServer]
+          ${attrsToSection def.dhcpServerConfig}
+        ''
+        + optionalString (def.ipv6SendRAConfig != { }) ''
+          [IPv6SendRA]
+          ${attrsToSection def.ipv6SendRAConfig}
+        ''
+        + flip concatMapStrings def.ipv6Prefixes (x: ''
+          [IPv6Prefix]
+          ${attrsToSection x.ipv6PrefixConfig}
+        '')
+        + flip concatMapStrings def.dhcpServerStaticLeases (x: ''
+          [DHCPServerStaticLease]
+          ${attrsToSection x.dhcpServerStaticLeaseConfig}
+        '')
+        + def.extraConfig;
+    };
+
+  unitFiles = listToAttrs (map (name: {
+    name = "systemd/network/${name}";
+    value.source = "${cfg.units.${name}.unit}/${name}";
+  }) (attrNames cfg.units));
+in
+
+{
+  options = {
+
+    systemd.network.enable = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Whether to enable networkd or not.
+      '';
+    };
+
+    systemd.network.links = mkOption {
+      default = {};
+      type = with types; attrsOf (submodule [ { options = linkOptions; } ]);
+      description = "Definition of systemd network links.";
+    };
+
+    systemd.network.netdevs = mkOption {
+      default = {};
+      type = with types; attrsOf (submodule [ { options = netdevOptions; } ]);
+      description = "Definition of systemd network devices.";
+    };
+
+    systemd.network.networks = mkOption {
+      default = {};
+      type = with types; attrsOf (submodule [ { options = networkOptions; } networkConfig ]);
+      description = "Definition of systemd networks.";
+    };
+
+    systemd.network.units = mkOption {
+      description = "Definition of networkd units.";
+      default = {};
+      internal = true;
+      type = with types; attrsOf (submodule (
+        { name, config, ... }:
+        { options = mapAttrs (_: x: x // { internal = true; }) concreteUnitOptions;
+          config = {
+            unit = mkDefault (makeUnit name config);
+          };
+        }));
+    };
+
+  };
+
+  config = mkMerge [
+
+    # .link units are honored by udev, no matter if systemd-networkd is enabled or not.
+    {
+      systemd.network.units = mapAttrs' (n: v: nameValuePair "${n}.link" (linkToUnit n v)) cfg.links;
+      environment.etc = unitFiles;
+    }
+
+    (mkIf config.systemd.network.enable {
+
+      users.users.systemd-network.group = "systemd-network";
+
+      systemd.additionalUpstreamSystemUnits = [
+        "systemd-networkd-wait-online.service"
+        "systemd-networkd.service"
+        "systemd-networkd.socket"
+      ];
+
+      systemd.network.units = mapAttrs' (n: v: nameValuePair "${n}.netdev" (netdevToUnit n v)) cfg.netdevs
+        // mapAttrs' (n: v: nameValuePair "${n}.network" (networkToUnit n v)) cfg.networks;
+
+      # systemd-networkd is socket-activated by kernel netlink route change
+      # messages. It is important to have systemd buffer those on behalf of
+      # networkd.
+      systemd.sockets.systemd-networkd.wantedBy = [ "sockets.target" ];
+
+      systemd.services.systemd-networkd = {
+        wantedBy = [ "multi-user.target" ];
+        aliases = [ "dbus-org.freedesktop.network1.service" ];
+        restartTriggers = map (x: x.source) (attrValues unitFiles);
+      };
+
+      systemd.services.systemd-networkd-wait-online = {
+        wantedBy = [ "network-online.target" ];
+      };
+
+      systemd.services."systemd-network-wait-online@" = {
+        description = "Wait for Network Interface %I to be Configured";
+        conflicts = [ "shutdown.target" ];
+        requisite = [ "systemd-networkd.service" ];
+        after = [ "systemd-networkd.service" ];
+        serviceConfig = {
+          Type = "oneshot";
+          RemainAfterExit = true;
+          ExecStart = "${config.systemd.package}/lib/systemd/systemd-networkd-wait-online -i %I";
+        };
+      };
+
+      services.resolved.enable = mkDefault true;
+    })
+  ];
+}
diff --git a/nixos/modules/system/boot/pbkdf2-sha512.c b/nixos/modules/system/boot/pbkdf2-sha512.c
new file mode 100644
index 00000000000..67e989957ba
--- /dev/null
+++ b/nixos/modules/system/boot/pbkdf2-sha512.c
@@ -0,0 +1,38 @@
+#include <stdint.h>
+#include <string.h>
+#include <stdio.h>
+#include <openssl/evp.h>
+
+void hextorb(uint8_t* hex, uint8_t* rb)
+{
+	while(sscanf(hex, "%2x", rb) == 1)
+	{
+		hex += 2;
+		rb += 1;
+	}
+	*rb = '\0';
+}
+
+int main(int argc, char** argv)
+{
+	uint8_t k_user[2048];
+	uint8_t salt[2048];
+	uint8_t key[4096];
+
+	uint32_t key_length = atoi(argv[1]);
+	uint32_t iteration_count = atoi(argv[2]);
+
+	hextorb(argv[3], salt);
+	uint32_t salt_length = strlen(argv[3]) / 2;
+
+	fgets(k_user, 2048, stdin);
+	uint32_t k_user_length = strlen(k_user);
+	if(k_user[k_user_length - 1] == '\n') {
+			k_user[k_user_length - 1] = '\0';
+	}
+
+	PKCS5_PBKDF2_HMAC(k_user, k_user_length, salt, salt_length, iteration_count, EVP_sha512(), key_length, key);
+	fwrite(key, 1, key_length, stdout);
+
+	return 0;
+}
diff --git a/nixos/modules/system/boot/plymouth.nix b/nixos/modules/system/boot/plymouth.nix
new file mode 100644
index 00000000000..78ae8e9d20b
--- /dev/null
+++ b/nixos/modules/system/boot/plymouth.nix
@@ -0,0 +1,237 @@
+{ config, lib, options, pkgs, ... }:
+
+with lib;
+
+let
+
+  inherit (pkgs) plymouth nixos-icons;
+
+  cfg = config.boot.plymouth;
+  opt = options.boot.plymouth;
+
+  nixosBreezePlymouth = pkgs.plasma5Packages.breeze-plymouth.override {
+    logoFile = cfg.logo;
+    logoName = "nixos";
+    osName = "NixOS";
+    osVersion = config.system.nixos.release;
+  };
+
+  plymouthLogos = pkgs.runCommand "plymouth-logos" { inherit (cfg) logo; } ''
+    mkdir -p $out
+
+    # For themes that are compiled with PLYMOUTH_LOGO_FILE
+    mkdir -p $out/etc/plymouth
+    ln -s $logo $out/etc/plymouth/logo.png
+
+    # Logo for bgrt theme
+    # Note this is technically an abuse of watermark for the bgrt theme
+    # See: https://gitlab.freedesktop.org/plymouth/plymouth/-/issues/95#note_813768
+    mkdir -p $out/share/plymouth/themes/spinner
+    ln -s $logo $out/share/plymouth/themes/spinner/watermark.png
+
+    # Logo for spinfinity theme
+    # See: https://gitlab.freedesktop.org/plymouth/plymouth/-/issues/106
+    mkdir -p $out/share/plymouth/themes/spinfinity
+    ln -s $logo $out/share/plymouth/themes/spinfinity/header-image.png
+  '';
+
+  themesEnv = pkgs.buildEnv {
+    name = "plymouth-themes";
+    paths = [
+      plymouth
+      plymouthLogos
+    ] ++ cfg.themePackages;
+  };
+
+  configFile = pkgs.writeText "plymouthd.conf" ''
+    [Daemon]
+    ShowDelay=0
+    DeviceTimeout=8
+    Theme=${cfg.theme}
+    ${cfg.extraConfig}
+  '';
+
+in
+
+{
+
+  options = {
+
+    boot.plymouth = {
+
+      enable = mkEnableOption "Plymouth boot splash screen";
+
+      font = mkOption {
+        default = "${pkgs.dejavu_fonts.minimal}/share/fonts/truetype/DejaVuSans.ttf";
+        defaultText = literalExpression ''"''${pkgs.dejavu_fonts.minimal}/share/fonts/truetype/DejaVuSans.ttf"'';
+        type = types.path;
+        description = ''
+          Font file made available for displaying text on the splash screen.
+        '';
+      };
+
+      themePackages = mkOption {
+        default = lib.optional (cfg.theme == "breeze") nixosBreezePlymouth;
+        defaultText = literalDocBook ''
+          A NixOS branded variant of the breeze theme when
+          <literal>config.${opt.theme} == "breeze"</literal>, otherwise
+          <literal>[ ]</literal>.
+        '';
+        type = types.listOf types.package;
+        description = ''
+          Extra theme packages for plymouth.
+        '';
+      };
+
+      theme = mkOption {
+        default = "bgrt";
+        type = types.str;
+        description = ''
+          Splash screen theme.
+        '';
+      };
+
+      logo = mkOption {
+        type = types.path;
+        # Dimensions are 48x48 to match GDM logo
+        default = "${nixos-icons}/share/icons/hicolor/48x48/apps/nix-snowflake-white.png";
+        defaultText = literalExpression ''pkgs.fetchurl {
+          url = "https://nixos.org/logo/nixos-hires.png";
+          sha256 = "1ivzgd7iz0i06y36p8m5w48fd8pjqwxhdaavc0pxs7w1g7mcy5si";
+        }'';
+        description = ''
+          Logo which is displayed on the splash screen.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Literal string to append to <literal>configFile</literal>
+          and the config file generated by the plymouth module.
+        '';
+      };
+
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    boot.kernelParams = [ "splash" ];
+
+    # To be discoverable by systemd.
+    environment.systemPackages = [ plymouth ];
+
+    environment.etc."plymouth/plymouthd.conf".source = configFile;
+    environment.etc."plymouth/plymouthd.defaults".source = "${plymouth}/share/plymouth/plymouthd.defaults";
+    environment.etc."plymouth/logo.png".source = cfg.logo;
+    environment.etc."plymouth/themes".source = "${themesEnv}/share/plymouth/themes";
+    # XXX: Needed because we supply a different set of plugins in initrd.
+    environment.etc."plymouth/plugins".source = "${plymouth}/lib/plymouth";
+
+    systemd.packages = [ plymouth ];
+
+    systemd.services.plymouth-kexec.wantedBy = [ "kexec.target" ];
+    systemd.services.plymouth-halt.wantedBy = [ "halt.target" ];
+    systemd.services.plymouth-quit-wait.wantedBy = [ "multi-user.target" ];
+    systemd.services.plymouth-quit.wantedBy = [ "multi-user.target" ];
+    systemd.services.plymouth-poweroff.wantedBy = [ "poweroff.target" ];
+    systemd.services.plymouth-reboot.wantedBy = [ "reboot.target" ];
+    systemd.services.plymouth-read-write.wantedBy = [ "sysinit.target" ];
+    systemd.services.systemd-ask-password-plymouth.wantedBy = [ "multi-user.target" ];
+    systemd.paths.systemd-ask-password-plymouth.wantedBy = [ "multi-user.target" ];
+
+    boot.initrd.extraUtilsCommands = ''
+      copy_bin_and_libs ${plymouth}/bin/plymouth
+      copy_bin_and_libs ${plymouth}/bin/plymouthd
+
+      # Check if the actual requested theme is here
+      if [[ ! -d ${themesEnv}/share/plymouth/themes/${cfg.theme} ]]; then
+          echo "The requested theme: ${cfg.theme} is not provided by any of the packages in boot.plymouth.themePackages"
+          exit 1
+      fi
+
+      moduleName="$(sed -n 's,ModuleName *= *,,p' ${themesEnv}/share/plymouth/themes/${cfg.theme}/${cfg.theme}.plymouth)"
+
+      mkdir -p $out/lib/plymouth/renderers
+      # module might come from a theme
+      cp ${themesEnv}/lib/plymouth/{text,details,label,$moduleName}.so $out/lib/plymouth
+      cp ${plymouth}/lib/plymouth/renderers/{drm,frame-buffer}.so $out/lib/plymouth/renderers
+
+      mkdir -p $out/share/plymouth/themes
+      cp ${plymouth}/share/plymouth/plymouthd.defaults $out/share/plymouth
+
+      # Copy themes into working directory for patching
+      mkdir themes
+
+      # Use -L to copy the directories proper, not the symlinks to them.
+      # Copy all themes because they're not large assets, and bgrt depends on the ImageDir of
+      # the spinner theme.
+      cp -r -L ${themesEnv}/share/plymouth/themes/* themes
+
+      # Patch out any attempted references to the theme or plymouth's themes directory
+      chmod -R +w themes
+      find themes -type f | while read file
+      do
+        sed -i "s,/nix/.*/share/plymouth/themes,$out/share/plymouth/themes,g" $file
+      done
+
+      # Install themes
+      cp -r themes/* $out/share/plymouth/themes
+
+      # Install logo
+      mkdir -p $out/etc/plymouth
+      cp -r -L ${themesEnv}/etc/plymouth $out
+
+      # Setup font
+      mkdir -p $out/share/fonts
+      cp ${cfg.font} $out/share/fonts
+      mkdir -p $out/etc/fonts
+      cat > $out/etc/fonts/fonts.conf <<EOF
+      <?xml version="1.0"?>
+      <!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
+      <fontconfig>
+          <dir>$out/share/fonts</dir>
+      </fontconfig>
+      EOF
+    '';
+
+    boot.initrd.extraUtilsCommandsTest = ''
+      $out/bin/plymouthd --help >/dev/null
+      $out/bin/plymouth --help >/dev/null
+    '';
+
+    boot.initrd.extraUdevRulesCommands = ''
+      cp ${config.systemd.package}/lib/udev/rules.d/{70-uaccess,71-seat}.rules $out
+      sed -i '/loginctl/d' $out/71-seat.rules
+    '';
+
+    # We use `mkAfter` to ensure that LUKS password prompt would be shown earlier than the splash screen.
+    boot.initrd.preLVMCommands = mkAfter ''
+      mkdir -p /etc/plymouth
+      mkdir -p /run/plymouth
+      ln -s ${configFile} /etc/plymouth/plymouthd.conf
+      ln -s $extraUtils/share/plymouth/plymouthd.defaults /etc/plymouth/plymouthd.defaults
+      ln -s $extraUtils/share/plymouth/logo.png /etc/plymouth/logo.png
+      ln -s $extraUtils/share/plymouth/themes /etc/plymouth/themes
+      ln -s $extraUtils/lib/plymouth /etc/plymouth/plugins
+      ln -s $extraUtils/etc/fonts /etc/fonts
+
+      plymouthd --mode=boot --pid-file=/run/plymouth/pid --attach-to-session
+      plymouth show-splash
+    '';
+
+    boot.initrd.postMountCommands = ''
+      plymouth update-root-fs --new-root-dir="$targetRoot"
+    '';
+
+    # `mkBefore` to ensure that any custom prompts would be visible.
+    boot.initrd.preFailCommands = mkBefore ''
+      plymouth quit --wait
+    '';
+
+  };
+
+}
diff --git a/nixos/modules/system/boot/resolved.nix b/nixos/modules/system/boot/resolved.nix
new file mode 100644
index 00000000000..21d3fab2f35
--- /dev/null
+++ b/nixos/modules/system/boot/resolved.nix
@@ -0,0 +1,183 @@
+{ config, lib, ... }:
+
+with lib;
+let
+  cfg = config.services.resolved;
+
+  dnsmasqResolve = config.services.dnsmasq.enable &&
+                   config.services.dnsmasq.resolveLocalQueries;
+
+in
+{
+
+  options = {
+
+    services.resolved.enable = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Whether to enable the systemd DNS resolver daemon.
+      '';
+    };
+
+    services.resolved.fallbackDns = mkOption {
+      default = [ ];
+      example = [ "8.8.8.8" "2001:4860:4860::8844" ];
+      type = types.listOf types.str;
+      description = ''
+        A list of IPv4 and IPv6 addresses to use as the fallback DNS servers.
+        If this option is empty, a compiled-in list of DNS servers is used instead.
+      '';
+    };
+
+    services.resolved.domains = mkOption {
+      default = config.networking.search;
+      defaultText = literalExpression "config.networking.search";
+      example = [ "example.com" ];
+      type = types.listOf types.str;
+      description = ''
+        A list of domains. These domains are used as search suffixes
+        when resolving single-label host names (domain names which
+        contain no dot), in order to qualify them into fully-qualified
+        domain names (FQDNs).
+
+        For compatibility reasons, if this setting is not specified,
+        the search domains listed in
+        <filename>/etc/resolv.conf</filename> are used instead, if
+        that file exists and any domains are configured in it.
+      '';
+    };
+
+    services.resolved.llmnr = mkOption {
+      default = "true";
+      example = "false";
+      type = types.enum [ "true" "resolve" "false" ];
+      description = ''
+        Controls Link-Local Multicast Name Resolution support
+        (RFC 4795) on the local host.
+
+        If set to
+
+        <variablelist>
+        <varlistentry>
+          <term><literal>"true"</literal></term>
+          <listitem><para>
+            Enables full LLMNR responder and resolver support.
+          </para></listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>"false"</literal></term>
+          <listitem><para>
+            Disables both.
+          </para></listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>"resolve"</literal></term>
+          <listitem><para>
+            Only resolution support is enabled, but responding is disabled.
+          </para></listitem>
+        </varlistentry>
+        </variablelist>
+      '';
+    };
+
+    services.resolved.dnssec = mkOption {
+      default = "allow-downgrade";
+      example = "true";
+      type = types.enum [ "true" "allow-downgrade" "false" ];
+      description = ''
+        If set to
+        <variablelist>
+        <varlistentry>
+          <term><literal>"true"</literal></term>
+          <listitem><para>
+            all DNS lookups are DNSSEC-validated locally (excluding
+            LLMNR and Multicast DNS). Note that this mode requires a
+            DNS server that supports DNSSEC. If the DNS server does
+            not properly support DNSSEC all validations will fail.
+          </para></listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>"allow-downgrade"</literal></term>
+          <listitem><para>
+            DNSSEC validation is attempted, but if the server does not
+            support DNSSEC properly, DNSSEC mode is automatically
+            disabled. Note that this mode makes DNSSEC validation
+            vulnerable to "downgrade" attacks, where an attacker might
+            be able to trigger a downgrade to non-DNSSEC mode by
+            synthesizing a DNS response that suggests DNSSEC was not
+            supported.
+          </para></listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>"false"</literal></term>
+          <listitem><para>
+            DNS lookups are not DNSSEC validated.
+          </para></listitem>
+        </varlistentry>
+        </variablelist>
+      '';
+    };
+
+    services.resolved.extraConfig = mkOption {
+      default = "";
+      type = types.lines;
+      description = ''
+        Extra config to append to resolved.conf.
+      '';
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion = !config.networking.useHostResolvConf;
+        message = "Using host resolv.conf is not supported with systemd-resolved";
+      }
+    ];
+
+    users.users.systemd-resolve.group = "systemd-resolve";
+
+    # add resolve to nss hosts database if enabled and nscd enabled
+    # system.nssModules is configured in nixos/modules/system/boot/systemd.nix
+    # added with order 501 to allow modules to go before with mkBefore
+    system.nssDatabases.hosts = (mkOrder 501 ["resolve [!UNAVAIL=return]"]);
+
+    systemd.additionalUpstreamSystemUnits = [
+      "systemd-resolved.service"
+    ];
+
+    systemd.services.systemd-resolved = {
+      wantedBy = [ "multi-user.target" ];
+      aliases = [ "dbus-org.freedesktop.resolve1.service" ];
+      restartTriggers = [ config.environment.etc."systemd/resolved.conf".source ];
+    };
+
+    environment.etc = {
+      "systemd/resolved.conf".text = ''
+        [Resolve]
+        ${optionalString (config.networking.nameservers != [])
+          "DNS=${concatStringsSep " " config.networking.nameservers}"}
+        ${optionalString (cfg.fallbackDns != [])
+          "FallbackDNS=${concatStringsSep " " cfg.fallbackDns}"}
+        ${optionalString (cfg.domains != [])
+          "Domains=${concatStringsSep " " cfg.domains}"}
+        LLMNR=${cfg.llmnr}
+        DNSSEC=${cfg.dnssec}
+        ${config.services.resolved.extraConfig}
+      '';
+
+      # symlink the dynamic stub resolver of resolv.conf as recommended by upstream:
+      # https://www.freedesktop.org/software/systemd/man/systemd-resolved.html#/etc/resolv.conf
+      "resolv.conf".source = "/run/systemd/resolve/stub-resolv.conf";
+    } // optionalAttrs dnsmasqResolve {
+      "dnsmasq-resolv.conf".source = "/run/systemd/resolve/resolv.conf";
+    };
+
+    # If networkmanager is enabled, ask it to interface with resolved.
+    networking.networkmanager.dns = "systemd-resolved";
+
+  };
+
+}
diff --git a/nixos/modules/system/boot/shutdown.nix b/nixos/modules/system/boot/shutdown.nix
new file mode 100644
index 00000000000..8cda7b3aabe
--- /dev/null
+++ b/nixos/modules/system/boot/shutdown.nix
@@ -0,0 +1,27 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+  # This unit saves the value of the system clock to the hardware
+  # clock on shutdown.
+  systemd.services.save-hwclock =
+    { description = "Save Hardware Clock";
+
+      wantedBy = [ "shutdown.target" ];
+
+      unitConfig = {
+        DefaultDependencies = false;
+        ConditionPathExists = "/dev/rtc";
+      };
+
+      serviceConfig = {
+        Type = "oneshot";
+        ExecStart = "${pkgs.util-linux}/sbin/hwclock --systohc ${if config.time.hardwareClockInLocalTime then "--localtime" else "--utc"}";
+      };
+    };
+
+  boot.kernel.sysctl."kernel.poweroff_cmd" = "${config.systemd.package}/sbin/poweroff";
+
+}
diff --git a/nixos/modules/system/boot/stage-1-init.sh b/nixos/modules/system/boot/stage-1-init.sh
new file mode 100644
index 00000000000..8fcc1f02972
--- /dev/null
+++ b/nixos/modules/system/boot/stage-1-init.sh
@@ -0,0 +1,642 @@
+#! @shell@
+
+targetRoot=/mnt-root
+console=tty1
+verbose="@verbose@"
+
+info() {
+    if [[ -n "$verbose" ]]; then
+        echo "$@"
+    fi
+}
+
+extraUtils="@extraUtils@"
+export LD_LIBRARY_PATH=@extraUtils@/lib
+export PATH=@extraUtils@/bin
+ln -s @extraUtils@/bin /bin
+
+# Copy the secrets to their needed location
+if [ -d "@extraUtils@/secrets" ]; then
+    for secret in $(cd "@extraUtils@/secrets"; find . -type f); do
+        mkdir -p $(dirname "/$secret")
+        ln -s "@extraUtils@/secrets/$secret" "$secret"
+    done
+fi
+
+# Stop LVM complaining about fd3
+export LVM_SUPPRESS_FD_WARNINGS=true
+
+fail() {
+    if [ -n "$panicOnFail" ]; then exit 1; fi
+
+    @preFailCommands@
+
+    # If starting stage 2 failed, allow the user to repair the problem
+    # in an interactive shell.
+    cat <<EOF
+
+An error occurred in stage 1 of the boot process, which must mount the
+root filesystem on \`$targetRoot' and then start stage 2.  Press one
+of the following keys:
+
+EOF
+    if [ -n "$allowShell" ]; then cat <<EOF
+  i) to launch an interactive shell
+  f) to start an interactive shell having pid 1 (needed if you want to
+     start stage 2's init manually)
+EOF
+    fi
+    cat <<EOF
+  r) to reboot immediately
+  *) to ignore the error and continue
+EOF
+
+    read -n 1 reply
+
+    if [ -n "$allowShell" -a "$reply" = f ]; then
+        exec setsid @shell@ -c "exec @shell@ < /dev/$console >/dev/$console 2>/dev/$console"
+    elif [ -n "$allowShell" -a "$reply" = i ]; then
+        echo "Starting interactive shell..."
+        setsid @shell@ -c "exec @shell@ < /dev/$console >/dev/$console 2>/dev/$console" || fail
+    elif [ "$reply" = r ]; then
+        echo "Rebooting..."
+        reboot -f
+    else
+        info "Continuing..."
+    fi
+}
+
+trap 'fail' 0
+
+
+# Print a greeting.
+info
+info "<<< NixOS Stage 1 >>>"
+info
+
+# Make several required directories.
+mkdir -p /etc/udev
+touch /etc/fstab # to shut up mount
+ln -s /proc/mounts /etc/mtab # to shut up mke2fs
+touch /etc/udev/hwdb.bin # to shut up udev
+touch /etc/initrd-release
+
+# Function for waiting for device(s) to appear.
+waitDevice() {
+    local device="$1"
+    # Split device string using ':' as a delimiter as bcachefs
+    # uses this for multi-device filesystems, i.e. /dev/sda1:/dev/sda2:/dev/sda3
+    local IFS=':'
+
+    # USB storage devices tend to appear with some delay.  It would be
+    # great if we had a way to synchronously wait for them, but
+    # alas...  So just wait for a few seconds for the device to
+    # appear.
+    for dev in $device; do
+        if test ! -e $dev; then
+            echo -n "waiting for device $dev to appear..."
+            try=20
+            while [ $try -gt 0 ]; do
+                sleep 1
+                # also re-try lvm activation now that new block devices might have appeared
+                lvm vgchange -ay
+                # and tell udev to create nodes for the new LVs
+                udevadm trigger --action=add
+                if test -e $dev; then break; fi
+                echo -n "."
+                try=$((try - 1))
+            done
+            echo
+            [ $try -ne 0 ]
+        fi
+    done
+}
+
+# Mount special file systems.
+specialMount() {
+  local device="$1"
+  local mountPoint="$2"
+  local options="$3"
+  local fsType="$4"
+
+  mkdir -m 0755 -p "$mountPoint"
+  mount -n -t "$fsType" -o "$options" "$device" "$mountPoint"
+}
+source @earlyMountScript@
+
+# Copy initrd secrets from /.initrd-secrets to their actual destinations
+if [ -d "/.initrd-secrets" ]; then
+    #
+    # Secrets are named by their full destination pathname and stored
+    # under /.initrd-secrets/
+    #
+    for secret in $(cd "/.initrd-secrets"; find . -type f); do
+        mkdir -p $(dirname "/$secret")
+        cp "/.initrd-secrets/$secret" "$secret"
+    done
+fi
+
+# Log the script output to /dev/kmsg or /run/log/stage-1-init.log.
+mkdir -p /tmp
+mkfifo /tmp/stage-1-init.log.fifo
+logOutFd=8 && logErrFd=9
+eval "exec $logOutFd>&1 $logErrFd>&2"
+if test -w /dev/kmsg; then
+    tee -i < /tmp/stage-1-init.log.fifo /proc/self/fd/"$logOutFd" | while read -r line; do
+        if test -n "$line"; then
+            echo "<7>stage-1-init: [$(date)] $line" > /dev/kmsg
+        fi
+    done &
+else
+    mkdir -p /run/log
+    tee -i < /tmp/stage-1-init.log.fifo /run/log/stage-1-init.log &
+fi
+exec > /tmp/stage-1-init.log.fifo 2>&1
+
+
+# Process the kernel command line.
+export stage2Init=/init
+for o in $(cat /proc/cmdline); do
+    case $o in
+        console=*)
+            set -- $(IFS==; echo $o)
+            params=$2
+            set -- $(IFS=,; echo $params)
+            console=$1
+            ;;
+        init=*)
+            set -- $(IFS==; echo $o)
+            stage2Init=$2
+            ;;
+        boot.persistence=*)
+            set -- $(IFS==; echo $o)
+            persistence=$2
+            ;;
+        boot.persistence.opt=*)
+            set -- $(IFS==; echo $o)
+            persistence_opt=$2
+            ;;
+        boot.trace|debugtrace)
+            # Show each command.
+            set -x
+            ;;
+        boot.shell_on_fail)
+            allowShell=1
+            ;;
+        boot.debug1|debug1) # stop right away
+            allowShell=1
+            fail
+            ;;
+        boot.debug1devices) # stop after loading modules and creating device nodes
+            allowShell=1
+            debug1devices=1
+            ;;
+        boot.debug1mounts) # stop after mounting file systems
+            allowShell=1
+            debug1mounts=1
+            ;;
+        boot.panic_on_fail|stage1panic=1)
+            panicOnFail=1
+            ;;
+        root=*)
+            # If a root device is specified on the kernel command
+            # line, make it available through the symlink /dev/root.
+            # Recognise LABEL= and UUID= to support UNetbootin.
+            set -- $(IFS==; echo $o)
+            if [ $2 = "LABEL" ]; then
+                root="/dev/disk/by-label/$3"
+            elif [ $2 = "UUID" ]; then
+                root="/dev/disk/by-uuid/$3"
+            else
+                root=$2
+            fi
+            ln -s "$root" /dev/root
+            ;;
+        copytoram)
+            copytoram=1
+            ;;
+        findiso=*)
+            # if an iso name is supplied, try to find the device where
+            # the iso resides on
+            set -- $(IFS==; echo $o)
+            isoPath=$2
+            ;;
+    esac
+done
+
+# Set hostid before modules are loaded.
+# This is needed by the spl/zfs modules.
+@setHostId@
+
+# Load the required kernel modules.
+mkdir -p /lib
+ln -s @modulesClosure@/lib/modules /lib/modules
+ln -s @modulesClosure@/lib/firmware /lib/firmware
+echo @extraUtils@/bin/modprobe > /proc/sys/kernel/modprobe
+for i in @kernelModules@; do
+    info "loading module $(basename $i)..."
+    modprobe $i
+done
+
+
+# Create device nodes in /dev.
+@preDeviceCommands@
+info "running udev..."
+ln -sfn /proc/self/fd /dev/fd
+ln -sfn /proc/self/fd/0 /dev/stdin
+ln -sfn /proc/self/fd/1 /dev/stdout
+ln -sfn /proc/self/fd/2 /dev/stderr
+mkdir -p /etc/systemd
+ln -sfn @linkUnits@ /etc/systemd/network
+mkdir -p /etc/udev
+ln -sfn @udevRules@ /etc/udev/rules.d
+mkdir -p /dev/.mdadm
+systemd-udevd --daemon
+udevadm trigger --action=add
+udevadm settle
+
+
+# XXX: Use case usb->lvm will still fail, usb->luks->lvm is covered
+@preLVMCommands@
+
+info "starting device mapper and LVM..."
+lvm vgchange -ay
+
+if test -n "$debug1devices"; then fail; fi
+
+
+@postDeviceCommands@
+
+
+# Check the specified file system, if appropriate.
+checkFS() {
+    local device="$1"
+    local fsType="$2"
+
+    # Only check block devices.
+    if [ ! -b "$device" ]; then return 0; fi
+
+    # Don't check ROM filesystems.
+    if [ "$fsType" = iso9660 -o "$fsType" = udf ]; then return 0; fi
+
+    # Don't check resilient COWs as they validate the fs structures at mount time
+    if [ "$fsType" = btrfs -o "$fsType" = zfs -o "$fsType" = bcachefs ]; then return 0; fi
+
+    # Skip fsck for apfs as the fsck utility does not support repairing the filesystem (no -a option)
+    if [ "$fsType" = apfs ]; then return 0; fi
+
+    # Skip fsck for nilfs2 - not needed by design and no fsck tool for this filesystem.
+    if [ "$fsType" = nilfs2 ]; then return 0; fi
+
+    # Skip fsck for inherently readonly filesystems.
+    if [ "$fsType" = squashfs ]; then return 0; fi
+
+    # If we couldn't figure out the FS type, then skip fsck.
+    if [ "$fsType" = auto ]; then
+        echo 'cannot check filesystem with type "auto"!'
+        return 0
+    fi
+
+    # Device might be already mounted manually
+    # e.g. NBD-device or the host filesystem of the file which contains encrypted root fs
+    if mount | grep -q "^$device on "; then
+        echo "skip checking already mounted $device"
+        return 0
+    fi
+
+    # Optionally, skip fsck on journaling filesystems.  This option is
+    # a hack - it's mostly because e2fsck on ext3 takes much longer to
+    # recover the journal than the ext3 implementation in the kernel
+    # does (minutes versus seconds).
+    if test -z "@checkJournalingFS@" -a \
+        \( "$fsType" = ext3 -o "$fsType" = ext4 -o "$fsType" = reiserfs \
+        -o "$fsType" = xfs -o "$fsType" = jfs -o "$fsType" = f2fs \)
+    then
+        return 0
+    fi
+
+    echo "checking $device..."
+
+    fsckFlags=
+    if test "$fsType" != "btrfs"; then
+        fsckFlags="-V -a"
+    fi
+    fsck $fsckFlags "$device"
+    fsckResult=$?
+
+    if test $(($fsckResult | 2)) = $fsckResult; then
+        echo "fsck finished, rebooting..."
+        sleep 3
+        reboot -f
+    fi
+
+    if test $(($fsckResult | 4)) = $fsckResult; then
+        echo "$device has unrepaired errors, please fix them manually."
+        fail
+    fi
+
+    if test $fsckResult -ge 8; then
+        echo "fsck on $device failed."
+        fail
+    fi
+
+    return 0
+}
+
+
+# Function for mounting a file system.
+mountFS() {
+    local device="$1"
+    local mountPoint="$2"
+    local options="$3"
+    local fsType="$4"
+
+    if [ "$fsType" = auto ]; then
+        fsType=$(blkid -o value -s TYPE "$device")
+        if [ -z "$fsType" ]; then fsType=auto; fi
+    fi
+
+    # Filter out x- options, which busybox doesn't do yet.
+    local optionsFiltered="$(IFS=,; for i in $options; do if [ "${i:0:2}" != "x-" ]; then echo -n $i,; fi; done)"
+    # Prefix (lower|upper|work)dir with /mnt-root (overlayfs)
+    local optionsPrefixed="$( echo "$optionsFiltered" | sed -E 's#\<(lowerdir|upperdir|workdir)=#\1=/mnt-root#g' )"
+
+    echo "$device /mnt-root$mountPoint $fsType $optionsPrefixed" >> /etc/fstab
+
+    checkFS "$device" "$fsType"
+
+    # Optionally resize the filesystem.
+    case $options in
+        *x-nixos.autoresize*)
+            if [ "$fsType" = ext2 -o "$fsType" = ext3 -o "$fsType" = ext4 ]; then
+                modprobe "$fsType"
+                echo "resizing $device..."
+                e2fsck -fp "$device"
+                resize2fs "$device"
+            elif [ "$fsType" = f2fs ]; then
+                echo "resizing $device..."
+                fsck.f2fs -fp "$device"
+                resize.f2fs "$device"
+            fi
+            ;;
+    esac
+
+    # Create backing directories for overlayfs
+    if [ "$fsType" = overlay ]; then
+        for i in upper work; do
+             dir="$( echo "$optionsPrefixed" | grep -o "${i}dir=[^,]*" )"
+             mkdir -m 0700 -p "${dir##*=}"
+        done
+    fi
+
+    info "mounting $device on $mountPoint..."
+
+    mkdir -p "/mnt-root$mountPoint"
+
+    # For ZFS and CIFS mounts, retry a few times before giving up.
+    # We do this for ZFS as a workaround for issue NixOS/nixpkgs#25383.
+    local n=0
+    while true; do
+        mount "/mnt-root$mountPoint" && break
+        if [ \( "$fsType" != cifs -a "$fsType" != zfs \) -o "$n" -ge 10 ]; then fail; break; fi
+        echo "retrying..."
+        sleep 1
+        n=$((n + 1))
+    done
+
+    [ "$mountPoint" == "/" ] &&
+        [ -f "/mnt-root/etc/NIXOS_LUSTRATE" ] &&
+        lustrateRoot "/mnt-root"
+
+    true
+}
+
+lustrateRoot () {
+    local root="$1"
+
+    echo
+    echo -e "\e[1;33m<<< NixOS is now lustrating the root filesystem (cruft goes to /old-root) >>>\e[0m"
+    echo
+
+    mkdir -m 0755 -p "$root/old-root.tmp"
+
+    echo
+    echo "Moving impurities out of the way:"
+    for d in "$root"/*
+    do
+        [ "$d" == "$root/nix"          ] && continue
+        [ "$d" == "$root/boot"         ] && continue # Don't render the system unbootable
+        [ "$d" == "$root/old-root.tmp" ] && continue
+
+        mv -v "$d" "$root/old-root.tmp"
+    done
+
+    # Use .tmp to make sure subsequent invokations don't clash
+    mv -v "$root/old-root.tmp" "$root/old-root"
+
+    mkdir -m 0755 -p "$root/etc"
+    touch "$root/etc/NIXOS"
+
+    exec 4< "$root/old-root/etc/NIXOS_LUSTRATE"
+
+    echo
+    echo "Restoring selected impurities:"
+    while read -u 4 keeper; do
+        dirname="$(dirname "$keeper")"
+        mkdir -m 0755 -p "$root/$dirname"
+        cp -av "$root/old-root/$keeper" "$root/$keeper"
+    done
+
+    exec 4>&-
+}
+
+
+
+if test -e /sys/power/resume -a -e /sys/power/disk; then
+    if test -n "@resumeDevice@" && waitDevice "@resumeDevice@"; then
+        resumeDev="@resumeDevice@"
+        resumeInfo="$(udevadm info -q property "$resumeDev" )"
+    else
+        for sd in @resumeDevices@; do
+            # Try to detect resume device. According to Ubuntu bug:
+            # https://bugs.launchpad.net/ubuntu/+source/pm-utils/+bug/923326/comments/1
+            # when there are multiple swap devices, we can't know where the hibernate
+            # image will reside. We can check all of them for swsuspend blkid.
+            if waitDevice "$sd"; then
+                resumeInfo="$(udevadm info -q property "$sd")"
+                if [ "$(echo "$resumeInfo" | sed -n 's/^ID_FS_TYPE=//p')" = "swsuspend" ]; then
+                    resumeDev="$sd"
+                    break
+                fi
+            fi
+        done
+    fi
+    if test -n "$resumeDev"; then
+        resumeMajor="$(echo "$resumeInfo" | sed -n 's/^MAJOR=//p')"
+        resumeMinor="$(echo "$resumeInfo" | sed -n 's/^MINOR=//p')"
+        echo "$resumeMajor:$resumeMinor" > /sys/power/resume 2> /dev/null || echo "failed to resume..."
+    fi
+fi
+
+# If we have a path to an iso file, find the iso and link it to /dev/root
+if [ -n "$isoPath" ]; then
+  mkdir -p /findiso
+
+  for delay in 5 10; do
+    blkid | while read -r line; do
+      device=$(echo "$line" | sed 's/:.*//')
+      type=$(echo "$line" | sed 's/.*TYPE="\([^"]*\)".*/\1/')
+
+      mount -t "$type" "$device" /findiso
+      if [ -e "/findiso$isoPath" ]; then
+        ln -sf "/findiso$isoPath" /dev/root
+        break 2
+      else
+        umount /findiso
+      fi
+    done
+
+    sleep "$delay"
+  done
+fi
+
+# Try to find and mount the root device.
+mkdir -p $targetRoot
+
+exec 3< @fsInfo@
+
+while read -u 3 mountPoint; do
+    read -u 3 device
+    read -u 3 fsType
+    read -u 3 options
+
+    # !!! Really quick hack to support bind mounts, i.e., where the
+    # "device" should be taken relative to /mnt-root, not /.  Assume
+    # that every device that starts with / but doesn't start with /dev
+    # is a bind mount.
+    pseudoDevice=
+    case $device in
+        /dev/*)
+            ;;
+        //*)
+            # Don't touch SMB/CIFS paths.
+            pseudoDevice=1
+            ;;
+        /*)
+            device=/mnt-root$device
+            ;;
+        *)
+            # Not an absolute path; assume that it's a pseudo-device
+            # like an NFS path (e.g. "server:/path").
+            pseudoDevice=1
+            ;;
+    esac
+
+    if test -z "$pseudoDevice" && ! waitDevice "$device"; then
+        # If it doesn't appear, try to mount it anyway (and
+        # probably fail).  This is a fallback for non-device "devices"
+        # that we don't properly recognise.
+        echo "Timed out waiting for device $device, trying to mount anyway."
+    fi
+
+    # Wait once more for the udev queue to empty, just in case it's
+    # doing something with $device right now.
+    udevadm settle
+
+    # If copytoram is enabled: skip mounting the ISO and copy its content to a tmpfs.
+    if [ -n "$copytoram" ] && [ "$device" = /dev/root ] && [ "$mountPoint" = /iso ]; then
+      fsType=$(blkid -o value -s TYPE "$device")
+      fsSize=$(blockdev --getsize64 "$device" || stat -Lc '%s' "$device")
+
+      mkdir -p /tmp-iso
+      mount -t "$fsType" /dev/root /tmp-iso
+      mountFS tmpfs /iso size="$fsSize" tmpfs
+
+      cp -r /tmp-iso/* /mnt-root/iso/
+
+      umount /tmp-iso
+      rmdir /tmp-iso
+      continue
+    fi
+
+    if [ "$mountPoint" = / ] && [ "$device" = tmpfs ] && [ ! -z "$persistence" ]; then
+        echo persistence...
+        waitDevice "$persistence"
+        echo enabling persistence...
+        mountFS "$persistence" "$mountPoint" "$persistence_opt" "auto"
+        continue
+    fi
+
+    mountFS "$device" "$mountPoint" "$options" "$fsType"
+done
+
+exec 3>&-
+
+
+@postMountCommands@
+
+
+# Emit a udev rule for /dev/root to prevent systemd from complaining.
+if [ -e /mnt-root/iso ]; then
+    eval $(udevadm info --export --export-prefix=ROOT_ --device-id-of-file=/mnt-root/iso)
+else
+    eval $(udevadm info --export --export-prefix=ROOT_ --device-id-of-file=$targetRoot)
+fi
+if [ "$ROOT_MAJOR" -a "$ROOT_MINOR" -a "$ROOT_MAJOR" != 0 ]; then
+    mkdir -p /run/udev/rules.d
+    echo 'ACTION=="add|change", SUBSYSTEM=="block", ENV{MAJOR}=="'$ROOT_MAJOR'", ENV{MINOR}=="'$ROOT_MINOR'", SYMLINK+="root"' > /run/udev/rules.d/61-dev-root-link.rules
+fi
+
+
+# Stop udevd.
+udevadm control --exit
+
+# Reset the logging file descriptors.
+# Do this just before pkill, which will kill the tee process.
+exec 1>&$logOutFd 2>&$logErrFd
+eval "exec $logOutFd>&- $logErrFd>&-"
+
+# Kill any remaining processes, just to be sure we're not taking any
+# with us into stage 2. But keep storage daemons like unionfs-fuse.
+#
+# Storage daemons are distinguished by an @ in front of their command line:
+# https://www.freedesktop.org/wiki/Software/systemd/RootStorageDaemons/
+for pid in $(pgrep -v -f '^@'); do
+    # Make sure we don't kill kernel processes, see #15226 and:
+    # http://stackoverflow.com/questions/12213445/identifying-kernel-threads
+    readlink "/proc/$pid/exe" &> /dev/null || continue
+    # Try to avoid killing ourselves.
+    [ $pid -eq $$ ] && continue
+    kill -9 "$pid"
+done
+
+if test -n "$debug1mounts"; then fail; fi
+
+
+# Restore /proc/sys/kernel/modprobe to its original value.
+echo /sbin/modprobe > /proc/sys/kernel/modprobe
+
+
+# Start stage 2.  `switch_root' deletes all files in the ramfs on the
+# current root.  The path has to be valid in the chroot not outside.
+if [ ! -e "$targetRoot/$stage2Init" ]; then
+    stage2Check=${stage2Init}
+    while [ "$stage2Check" != "${stage2Check%/*}" ] && [ ! -L "$targetRoot/$stage2Check" ]; do
+        stage2Check=${stage2Check%/*}
+    done
+    if [ ! -L "$targetRoot/$stage2Check" ]; then
+        echo "stage 2 init script ($targetRoot/$stage2Init) not found"
+        fail
+    fi
+fi
+
+mkdir -m 0755 -p $targetRoot/proc $targetRoot/sys $targetRoot/dev $targetRoot/run
+
+mount --move /proc $targetRoot/proc
+mount --move /sys $targetRoot/sys
+mount --move /dev $targetRoot/dev
+mount --move /run $targetRoot/run
+
+exec env -i $(type -P switch_root) "$targetRoot" "$stage2Init"
+
+fail # should never be reached
diff --git a/nixos/modules/system/boot/stage-1.nix b/nixos/modules/system/boot/stage-1.nix
new file mode 100644
index 00000000000..8b011d91563
--- /dev/null
+++ b/nixos/modules/system/boot/stage-1.nix
@@ -0,0 +1,717 @@
+# This module builds the initial ramdisk, which contains an init
+# script that performs the first stage of booting the system: it loads
+# the modules necessary to mount the root file system, then calls the
+# init in the root file system to start the second boot stage.
+
+{ config, lib, utils, pkgs, ... }:
+
+with lib;
+
+let
+
+  udev = config.systemd.package;
+
+  kernel-name = config.boot.kernelPackages.kernel.name or "kernel";
+
+  modulesTree = config.system.modulesTree.override { name = kernel-name + "-modules"; };
+  firmware = config.hardware.firmware;
+
+
+  # Determine the set of modules that we need to mount the root FS.
+  modulesClosure = pkgs.makeModulesClosure {
+    rootModules = config.boot.initrd.availableKernelModules ++ config.boot.initrd.kernelModules;
+    kernel = modulesTree;
+    firmware = firmware;
+    allowMissing = false;
+  };
+
+
+  # The initrd only has to mount `/` or any FS marked as necessary for
+  # booting (such as the FS containing `/nix/store`, or an FS needed for
+  # mounting `/`, like `/` on a loopback).
+  fileSystems = filter utils.fsNeededForBoot config.system.build.fileSystems;
+
+  # A utility for enumerating the shared-library dependencies of a program
+  findLibs = pkgs.buildPackages.writeShellScriptBin "find-libs" ''
+    set -euo pipefail
+
+    declare -A seen
+    left=()
+
+    patchelf="${pkgs.buildPackages.patchelf}/bin/patchelf"
+
+    function add_needed {
+      rpath="$($patchelf --print-rpath $1)"
+      dir="$(dirname $1)"
+      for lib in $($patchelf --print-needed $1); do
+        left+=("$lib" "$rpath" "$dir")
+      done
+    }
+
+    add_needed "$1"
+
+    while [ ''${#left[@]} -ne 0 ]; do
+      next=''${left[0]}
+      rpath=''${left[1]}
+      ORIGIN=''${left[2]}
+      left=("''${left[@]:3}")
+      if [ -z ''${seen[$next]+x} ]; then
+        seen[$next]=1
+
+        # Ignore the dynamic linker which for some reason appears as a DT_NEEDED of glibc but isn't in glibc's RPATH.
+        case "$next" in
+          ld*.so.?) continue;;
+        esac
+
+        IFS=: read -ra paths <<< $rpath
+        res=
+        for path in "''${paths[@]}"; do
+          path=$(eval "echo $path")
+          if [ -f "$path/$next" ]; then
+              res="$path/$next"
+              echo "$res"
+              add_needed "$res"
+              break
+          fi
+        done
+        if [ -z "$res" ]; then
+          echo "Couldn't satisfy dependency $next" >&2
+          exit 1
+        fi
+      fi
+    done
+  '';
+
+  # Some additional utilities needed in stage 1, like mount, lvm, fsck
+  # etc.  We don't want to bring in all of those packages, so we just
+  # copy what we need.  Instead of using statically linked binaries,
+  # we just copy what we need from Glibc and use patchelf to make it
+  # work.
+  extraUtils = pkgs.runCommandCC "extra-utils"
+    { nativeBuildInputs = [pkgs.buildPackages.nukeReferences];
+      allowedReferences = [ "out" ]; # prevent accidents like glibc being included in the initrd
+    }
+    ''
+      set +o pipefail
+
+      mkdir -p $out/bin $out/lib
+      ln -s $out/bin $out/sbin
+
+      copy_bin_and_libs () {
+        [ -f "$out/bin/$(basename $1)" ] && rm "$out/bin/$(basename $1)"
+        cp -pdv $1 $out/bin
+      }
+
+      # Copy BusyBox.
+      for BIN in ${pkgs.busybox}/{s,}bin/*; do
+        copy_bin_and_libs $BIN
+      done
+
+      # Copy some util-linux stuff.
+      copy_bin_and_libs ${pkgs.util-linux}/sbin/blkid
+
+      # Copy dmsetup and lvm.
+      copy_bin_and_libs ${getBin pkgs.lvm2}/bin/dmsetup
+      copy_bin_and_libs ${getBin pkgs.lvm2}/bin/lvm
+
+      # Add RAID mdadm tool.
+      copy_bin_and_libs ${pkgs.mdadm}/sbin/mdadm
+      copy_bin_and_libs ${pkgs.mdadm}/sbin/mdmon
+
+      # Copy udev.
+      copy_bin_and_libs ${udev}/bin/udevadm
+      copy_bin_and_libs ${udev}/lib/systemd/systemd-sysctl
+      for BIN in ${udev}/lib/udev/*_id; do
+        copy_bin_and_libs $BIN
+      done
+      # systemd-udevd is only a symlink to udevadm these days
+      ln -sf udevadm $out/bin/systemd-udevd
+
+      # Copy modprobe.
+      copy_bin_and_libs ${pkgs.kmod}/bin/kmod
+      ln -sf kmod $out/bin/modprobe
+
+      # Copy resize2fs if any ext* filesystems are to be resized
+      ${optionalString (any (fs: fs.autoResize && (lib.hasPrefix "ext" fs.fsType)) fileSystems) ''
+        # We need mke2fs in the initrd.
+        copy_bin_and_libs ${pkgs.e2fsprogs}/sbin/resize2fs
+      ''}
+
+      # Copy multipath.
+      ${optionalString config.services.multipath.enable ''
+        copy_bin_and_libs ${config.services.multipath.package}/bin/multipath
+        copy_bin_and_libs ${config.services.multipath.package}/bin/multipathd
+        # Copy lib/multipath manually.
+        cp -rpv ${config.services.multipath.package}/lib/multipath $out/lib
+      ''}
+
+      # Copy secrets if needed.
+      #
+      # TODO: move out to a separate script; see #85000.
+      ${optionalString (!config.boot.loader.supportsInitrdSecrets)
+          (concatStringsSep "\n" (mapAttrsToList (dest: source:
+             let source' = if source == null then dest else source; in
+               ''
+                  mkdir -p $(dirname "$out/secrets/${dest}")
+                  # Some programs (e.g. ssh) doesn't like secrets to be
+                  # symlinks, so we use `cp -L` here to match the
+                  # behaviour when secrets are natively supported.
+                  cp -Lr ${source'} "$out/secrets/${dest}"
+                ''
+          ) config.boot.initrd.secrets))
+       }
+
+      ${config.boot.initrd.extraUtilsCommands}
+
+      # Copy ld manually since it isn't detected correctly
+      cp -pv ${pkgs.stdenv.cc.libc.out}/lib/ld*.so.? $out/lib
+
+      # Copy all of the needed libraries
+      find $out/bin $out/lib -type f | while read BIN; do
+        echo "Copying libs for executable $BIN"
+        for LIB in $(${findLibs}/bin/find-libs $BIN); do
+          TGT="$out/lib/$(basename $LIB)"
+          if [ ! -f "$TGT" ]; then
+            SRC="$(readlink -e $LIB)"
+            cp -pdv "$SRC" "$TGT"
+          fi
+        done
+      done
+
+      # Strip binaries further than normal.
+      chmod -R u+w $out
+      stripDirs "$STRIP" "lib bin" "-s"
+
+      # Run patchelf to make the programs refer to the copied libraries.
+      find $out/bin $out/lib -type f | while read i; do
+        if ! test -L $i; then
+          nuke-refs -e $out $i
+        fi
+      done
+
+      find $out/bin -type f | while read i; do
+        if ! test -L $i; then
+          echo "patching $i..."
+          patchelf --set-interpreter $out/lib/ld*.so.? --set-rpath $out/lib $i || true
+        fi
+      done
+
+      if [ -z "${toString (pkgs.stdenv.hostPlatform != pkgs.stdenv.buildPlatform)}" ]; then
+      # Make sure that the patchelf'ed binaries still work.
+      echo "testing patched programs..."
+      $out/bin/ash -c 'echo hello world' | grep "hello world"
+      export LD_LIBRARY_PATH=$out/lib
+      $out/bin/mount --help 2>&1 | grep -q "BusyBox"
+      $out/bin/blkid -V 2>&1 | grep -q 'libblkid'
+      $out/bin/udevadm --version
+      $out/bin/dmsetup --version 2>&1 | tee -a log | grep -q "version:"
+      LVM_SYSTEM_DIR=$out $out/bin/lvm version 2>&1 | tee -a log | grep -q "LVM"
+      $out/bin/mdadm --version
+      ${optionalString config.services.multipath.enable ''
+        ($out/bin/multipath || true) 2>&1 | grep -q 'need to be root'
+        ($out/bin/multipathd || true) 2>&1 | grep -q 'need to be root'
+      ''}
+
+      ${config.boot.initrd.extraUtilsCommandsTest}
+      fi
+    ''; # */
+
+
+  # Networkd link files are used early by udev to set up interfaces early.
+  # This must be done in stage 1 to avoid race conditions between udev and
+  # network daemons.
+  linkUnits = pkgs.runCommand "link-units" {
+      allowedReferences = [ extraUtils ];
+      preferLocalBuild = true;
+    } (''
+      mkdir -p $out
+      cp -v ${udev}/lib/systemd/network/*.link $out/
+      '' + (
+      let
+        links = filterAttrs (n: v: hasSuffix ".link" n) config.systemd.network.units;
+        files = mapAttrsToList (n: v: "${v.unit}/${n}") links;
+      in
+        concatMapStringsSep "\n" (file: "cp -v ${file} $out/") files
+      ));
+
+  udevRules = pkgs.runCommand "udev-rules" {
+      allowedReferences = [ extraUtils ];
+      preferLocalBuild = true;
+    } ''
+      mkdir -p $out
+
+      echo 'ENV{LD_LIBRARY_PATH}="${extraUtils}/lib"' > $out/00-env.rules
+
+      cp -v ${udev}/lib/udev/rules.d/60-cdrom_id.rules $out/
+      cp -v ${udev}/lib/udev/rules.d/60-persistent-storage.rules $out/
+      cp -v ${udev}/lib/udev/rules.d/75-net-description.rules $out/
+      cp -v ${udev}/lib/udev/rules.d/80-drivers.rules $out/
+      cp -v ${udev}/lib/udev/rules.d/80-net-setup-link.rules $out/
+      cp -v ${pkgs.lvm2}/lib/udev/rules.d/*.rules $out/
+      ${config.boot.initrd.extraUdevRulesCommands}
+
+      for i in $out/*.rules; do
+          substituteInPlace $i \
+            --replace ata_id ${extraUtils}/bin/ata_id \
+            --replace scsi_id ${extraUtils}/bin/scsi_id \
+            --replace cdrom_id ${extraUtils}/bin/cdrom_id \
+            --replace ${pkgs.coreutils}/bin/basename ${extraUtils}/bin/basename \
+            --replace ${pkgs.util-linux}/bin/blkid ${extraUtils}/bin/blkid \
+            --replace ${getBin pkgs.lvm2}/bin ${extraUtils}/bin \
+            --replace ${pkgs.mdadm}/sbin ${extraUtils}/sbin \
+            --replace ${pkgs.bash}/bin/sh ${extraUtils}/bin/sh \
+            --replace ${udev} ${extraUtils}
+      done
+
+      # Work around a bug in QEMU, which doesn't implement the "READ
+      # DISC INFORMATION" SCSI command:
+      #   https://bugzilla.redhat.com/show_bug.cgi?id=609049
+      # As a result, `cdrom_id' doesn't print
+      # ID_CDROM_MEDIA_TRACK_COUNT_DATA, which in turn prevents the
+      # /dev/disk/by-label symlinks from being created.  We need these
+      # in the NixOS installation CD, so use ID_CDROM_MEDIA in the
+      # corresponding udev rules for now.  This was the behaviour in
+      # udev <= 154.  See also
+      #   http://www.spinics.net/lists/hotplug/msg03935.html
+      substituteInPlace $out/60-persistent-storage.rules \
+        --replace ID_CDROM_MEDIA_TRACK_COUNT_DATA ID_CDROM_MEDIA
+    ''; # */
+
+
+  # The init script of boot stage 1 (loading kernel modules for
+  # mounting the root FS).
+  bootStage1 = pkgs.substituteAll {
+    src = ./stage-1-init.sh;
+
+    shell = "${extraUtils}/bin/ash";
+
+    isExecutable = true;
+
+    postInstall = ''
+      echo checking syntax
+      # check both with bash
+      ${pkgs.buildPackages.bash}/bin/sh -n $target
+      # and with ash shell, just in case
+      ${pkgs.buildPackages.busybox}/bin/ash -n $target
+    '';
+
+    inherit linkUnits udevRules extraUtils modulesClosure;
+
+    inherit (config.boot) resumeDevice;
+
+    inherit (config.system.build) earlyMountScript;
+
+    inherit (config.boot.initrd) checkJournalingFS verbose
+      preLVMCommands preDeviceCommands postDeviceCommands postMountCommands preFailCommands kernelModules;
+
+    resumeDevices = map (sd: if sd ? device then sd.device else "/dev/disk/by-label/${sd.label}")
+                    (filter (sd: hasPrefix "/dev/" sd.device && !sd.randomEncryption.enable
+                             # Don't include zram devices
+                             && !(hasPrefix "/dev/zram" sd.device)
+                            ) config.swapDevices);
+
+    fsInfo =
+      let f = fs: [ fs.mountPoint (if fs.device != null then fs.device else "/dev/disk/by-label/${fs.label}") fs.fsType (builtins.concatStringsSep "," fs.options) ];
+      in pkgs.writeText "initrd-fsinfo" (concatStringsSep "\n" (concatMap f fileSystems));
+
+    setHostId = optionalString (config.networking.hostId != null) ''
+      hi="${config.networking.hostId}"
+      ${if pkgs.stdenv.isBigEndian then ''
+        echo -ne "\x''${hi:0:2}\x''${hi:2:2}\x''${hi:4:2}\x''${hi:6:2}" > /etc/hostid
+      '' else ''
+        echo -ne "\x''${hi:6:2}\x''${hi:4:2}\x''${hi:2:2}\x''${hi:0:2}" > /etc/hostid
+      ''}
+    '';
+  };
+
+
+  # The closure of the init script of boot stage 1 is what we put in
+  # the initial RAM disk.
+  initialRamdisk = pkgs.makeInitrd {
+    name = "initrd-${kernel-name}";
+    inherit (config.boot.initrd) compressor compressorArgs prepend;
+
+    contents =
+      [ { object = bootStage1;
+          symlink = "/init";
+        }
+        { object = pkgs.writeText "mdadm.conf" config.boot.initrd.mdadmConf;
+          symlink = "/etc/mdadm.conf";
+        }
+        { object = pkgs.runCommand "initrd-kmod-blacklist-ubuntu" {
+              src = "${pkgs.kmod-blacklist-ubuntu}/modprobe.conf";
+              preferLocalBuild = true;
+            } ''
+              target=$out
+              ${pkgs.buildPackages.perl}/bin/perl -0pe 's/## file: iwlwifi.conf(.+?)##/##/s;' $src > $out
+            '';
+          symlink = "/etc/modprobe.d/ubuntu.conf";
+        }
+        { object = config.environment.etc."modprobe.d/nixos.conf".source;
+          symlink = "/etc/modprobe.d/nixos.conf";
+        }
+        { object = pkgs.kmod-debian-aliases;
+          symlink = "/etc/modprobe.d/debian.conf";
+        }
+      ] ++ lib.optionals config.services.multipath.enable [
+        { object = pkgs.runCommand "multipath.conf" {
+              src = config.environment.etc."multipath.conf".text;
+              preferLocalBuild = true;
+            } ''
+              target=$out
+              printf "$src" > $out
+              substituteInPlace $out \
+                --replace ${config.services.multipath.package}/lib ${extraUtils}/lib
+            '';
+          symlink = "/etc/multipath.conf";
+        }
+      ] ++ (lib.mapAttrsToList
+        (symlink: options:
+          {
+            inherit symlink;
+            object = options.source;
+          }
+        )
+        config.boot.initrd.extraFiles);
+  };
+
+  # Script to add secret files to the initrd at bootloader update time
+  initialRamdiskSecretAppender =
+    let
+      compressorExe = initialRamdisk.compressorExecutableFunction pkgs;
+    in pkgs.writeScriptBin "append-initrd-secrets"
+      ''
+        #!${pkgs.bash}/bin/bash -e
+        function usage {
+          echo "USAGE: $0 INITRD_FILE" >&2
+          echo "Appends this configuration's secrets to INITRD_FILE" >&2
+        }
+
+        if [ $# -ne 1 ]; then
+          usage
+          exit 1
+        fi
+
+        if [ "$1"x = "--helpx" ]; then
+          usage
+          exit 0
+        fi
+
+        ${lib.optionalString (config.boot.initrd.secrets == {})
+            "exit 0"}
+
+        export PATH=${pkgs.coreutils}/bin:${pkgs.cpio}/bin:${pkgs.gzip}/bin:${pkgs.findutils}/bin
+
+        function cleanup {
+          if [ -n "$tmp" -a -d "$tmp" ]; then
+            rm -fR "$tmp"
+          fi
+        }
+        trap cleanup EXIT
+
+        tmp=$(mktemp -d ''${TMPDIR:-/tmp}/initrd-secrets.XXXXXXXXXX)
+
+        ${lib.concatStringsSep "\n" (mapAttrsToList (dest: source:
+            let source' = if source == null then dest else toString source; in
+              ''
+                mkdir -p $(dirname "$tmp/.initrd-secrets/${dest}")
+                cp -a ${source'} "$tmp/.initrd-secrets/${dest}"
+              ''
+          ) config.boot.initrd.secrets)
+         }
+
+        (cd "$tmp" && find . -print0 | sort -z | cpio --quiet -o -H newc -R +0:+0 --reproducible --null) | \
+          ${compressorExe} ${lib.escapeShellArgs initialRamdisk.compressorArgs} >> "$1"
+      '';
+
+in
+
+{
+  options = {
+
+    boot.resumeDevice = mkOption {
+      type = types.str;
+      default = "";
+      example = "/dev/sda3";
+      description = ''
+        Device for manual resume attempt during boot. This should be used primarily
+        if you want to resume from file. If left empty, the swap partitions are used.
+        Specify here the device where the file resides.
+        You should also use <varname>boot.kernelParams</varname> to specify
+        <literal><replaceable>resume_offset</replaceable></literal>.
+      '';
+    };
+
+    boot.initrd.enable = mkOption {
+      type = types.bool;
+      default = !config.boot.isContainer;
+      defaultText = literalExpression "!config.boot.isContainer";
+      description = ''
+        Whether to enable the NixOS initial RAM disk (initrd). This may be
+        needed to perform some initialisation tasks (like mounting
+        network/encrypted file systems) before continuing the boot process.
+      '';
+    };
+
+    boot.initrd.extraFiles = mkOption {
+      default = { };
+      type = types.attrsOf
+        (types.submodule {
+          options = {
+            source = mkOption {
+              type = types.package;
+              description = "The object to make available inside the initrd.";
+            };
+          };
+        });
+      description = ''
+        Extra files to link and copy in to the initrd.
+      '';
+    };
+
+    boot.initrd.prepend = mkOption {
+      default = [ ];
+      type = types.listOf types.str;
+      description = ''
+        Other initrd files to prepend to the final initrd we are building.
+      '';
+    };
+
+    boot.initrd.checkJournalingFS = mkOption {
+      default = true;
+      type = types.bool;
+      description = ''
+        Whether to run <command>fsck</command> on journaling filesystems such as ext3.
+      '';
+    };
+
+    boot.initrd.mdadmConf = mkOption {
+      default = "";
+      type = types.lines;
+      description = ''
+        Contents of <filename>/etc/mdadm.conf</filename> in stage 1.
+      '';
+    };
+
+    boot.initrd.preLVMCommands = mkOption {
+      default = "";
+      type = types.lines;
+      description = ''
+        Shell commands to be executed immediately before LVM discovery.
+      '';
+    };
+
+    boot.initrd.preDeviceCommands = mkOption {
+      default = "";
+      type = types.lines;
+      description = ''
+        Shell commands to be executed before udev is started to create
+        device nodes.
+      '';
+    };
+
+    boot.initrd.postDeviceCommands = mkOption {
+      default = "";
+      type = types.lines;
+      description = ''
+        Shell commands to be executed immediately after stage 1 of the
+        boot has loaded kernel modules and created device nodes in
+        <filename>/dev</filename>.
+      '';
+    };
+
+    boot.initrd.postMountCommands = mkOption {
+      default = "";
+      type = types.lines;
+      description = ''
+        Shell commands to be executed immediately after the stage 1
+        filesystems have been mounted.
+      '';
+    };
+
+    boot.initrd.preFailCommands = mkOption {
+      default = "";
+      type = types.lines;
+      description = ''
+        Shell commands to be executed before the failure prompt is shown.
+      '';
+    };
+
+    boot.initrd.extraUtilsCommands = mkOption {
+      internal = true;
+      default = "";
+      type = types.lines;
+      description = ''
+        Shell commands to be executed in the builder of the
+        extra-utils derivation.  This can be used to provide
+        additional utilities in the initial ramdisk.
+      '';
+    };
+
+    boot.initrd.extraUtilsCommandsTest = mkOption {
+      internal = true;
+      default = "";
+      type = types.lines;
+      description = ''
+        Shell commands to be executed in the builder of the
+        extra-utils derivation after patchelf has done its
+        job.  This can be used to test additional utilities
+        copied in extraUtilsCommands.
+      '';
+    };
+
+    boot.initrd.extraUdevRulesCommands = mkOption {
+      internal = true;
+      default = "";
+      type = types.lines;
+      description = ''
+        Shell commands to be executed in the builder of the
+        udev-rules derivation.  This can be used to add
+        additional udev rules in the initial ramdisk.
+      '';
+    };
+
+    boot.initrd.compressor = mkOption {
+      default = (
+        if lib.versionAtLeast config.boot.kernelPackages.kernel.version "5.9"
+        then "zstd"
+        else "gzip"
+      );
+      defaultText = literalDocBook "<literal>zstd</literal> if the kernel supports it (5.9+), <literal>gzip</literal> if not";
+      type = types.either types.str (types.functionTo types.str);
+      description = ''
+        The compressor to use on the initrd image. May be any of:
+
+        <itemizedlist>
+         <listitem><para>The name of one of the predefined compressors, see <filename>pkgs/build-support/kernel/initrd-compressor-meta.nix</filename> for the definitions.</para></listitem>
+         <listitem><para>A function which, given the nixpkgs package set, returns the path to a compressor tool, e.g. <literal>pkgs: "''${pkgs.pigz}/bin/pigz"</literal></para></listitem>
+         <listitem><para>(not recommended, because it does not work when cross-compiling) the full path to a compressor tool, e.g. <literal>"''${pkgs.pigz}/bin/pigz"</literal></para></listitem>
+        </itemizedlist>
+
+        The given program should read data from stdin and write it to stdout compressed.
+      '';
+      example = "xz";
+    };
+
+    boot.initrd.compressorArgs = mkOption {
+      default = null;
+      type = types.nullOr (types.listOf types.str);
+      description = "Arguments to pass to the compressor for the initrd image, or null to use the compressor's defaults.";
+    };
+
+    boot.initrd.secrets = mkOption
+      { default = {};
+        type = types.attrsOf (types.nullOr types.path);
+        description =
+          ''
+            Secrets to append to the initrd. The attribute name is the
+            path the secret should have inside the initrd, the value
+            is the path it should be copied from (or null for the same
+            path inside and out).
+          '';
+        example = literalExpression
+          ''
+            { "/etc/dropbear/dropbear_rsa_host_key" =
+                ./secret-dropbear-key;
+            }
+          '';
+      };
+
+    boot.initrd.supportedFilesystems = mkOption {
+      default = [ ];
+      example = [ "btrfs" ];
+      type = types.listOf types.str;
+      description = "Names of supported filesystem types in the initial ramdisk.";
+    };
+
+    boot.initrd.verbose = mkOption {
+      default = true;
+      type = types.bool;
+      description =
+        ''
+          Verbosity of the initrd. Please note that disabling verbosity removes
+          only the mandatory messages generated by the NixOS scripts. For a
+          completely silent boot, you might also want to set the two following
+          configuration options:
+
+          <itemizedlist>
+            <listitem><para><literal>boot.consoleLogLevel = 0;</literal></para></listitem>
+            <listitem><para><literal>boot.kernelParams = [ "quiet" "udev.log_level=3" ];</literal></para></listitem>
+          </itemizedlist>
+        '';
+    };
+
+    boot.loader.supportsInitrdSecrets = mkOption
+      { internal = true;
+        default = false;
+        type = types.bool;
+        description =
+          ''
+            Whether the bootloader setup runs append-initrd-secrets.
+            If not, any needed secrets must be copied into the initrd
+            and thus added to the store.
+          '';
+      };
+
+    fileSystems = mkOption {
+      type = with lib.types; attrsOf (submodule {
+        options.neededForBoot = mkOption {
+          default = false;
+          type = types.bool;
+          description = ''
+            If set, this file system will be mounted in the initial ramdisk.
+            Note that the file system will always be mounted in the initial
+            ramdisk if its mount point is one of the following:
+            ${concatStringsSep ", " (
+              forEach utils.pathsNeededForBoot (i: "<filename>${i}</filename>")
+            )}.
+          '';
+        };
+      });
+    };
+
+  };
+
+  config = mkIf config.boot.initrd.enable {
+    assertions = [
+      { assertion = any (fs: fs.mountPoint == "/") fileSystems;
+        message = "The ‘fileSystems’ option does not specify your root file system.";
+      }
+      { assertion = let inherit (config.boot) resumeDevice; in
+          resumeDevice == "" || builtins.substring 0 1 resumeDevice == "/";
+        message = "boot.resumeDevice has to be an absolute path."
+          + " Old \"x:y\" style is no longer supported.";
+      }
+      # TODO: remove when #85000 is fixed
+      { assertion = !config.boot.loader.supportsInitrdSecrets ->
+          all (source:
+            builtins.isPath source ||
+            (builtins.isString source && hasPrefix builtins.storeDir source))
+          (attrValues config.boot.initrd.secrets);
+        message = ''
+          boot.loader.initrd.secrets values must be unquoted paths when
+          using a bootloader that doesn't natively support initrd
+          secrets, e.g.:
+
+            boot.initrd.secrets = {
+              "/etc/secret" = /path/to/secret;
+            };
+
+          Note that this will result in all secrets being stored
+          world-readable in the Nix store!
+        '';
+      }
+    ];
+
+    system.build =
+      { inherit bootStage1 initialRamdisk initialRamdiskSecretAppender extraUtils; };
+
+    system.requiredKernelConfig = with config.lib.kernelConfig; [
+      (isYes "TMPFS")
+      (isYes "BLK_DEV_INITRD")
+    ];
+
+    boot.initrd.supportedFilesystems = map (fs: fs.fsType) fileSystems;
+
+  };
+}
diff --git a/nixos/modules/system/boot/stage-2-init.sh b/nixos/modules/system/boot/stage-2-init.sh
new file mode 100755
index 00000000000..a90f58042d2
--- /dev/null
+++ b/nixos/modules/system/boot/stage-2-init.sh
@@ -0,0 +1,176 @@
+#! @shell@
+
+systemConfig=@systemConfig@
+
+export HOME=/root PATH="@path@"
+
+
+# Process the kernel command line.
+for o in $(</proc/cmdline); do
+    case $o in
+        boot.debugtrace)
+            # Show each command.
+            set -x
+            ;;
+        resume=*)
+            set -- $(IFS==; echo $o)
+            resumeDevice=$2
+            ;;
+    esac
+done
+
+
+# Print a greeting.
+echo
+echo -e "\e[1;32m<<< NixOS Stage 2 >>>\e[0m"
+echo
+
+
+# Normally, stage 1 mounts the root filesystem read/writable.
+# However, in some environments, stage 2 is executed directly, and the
+# root is read-only.  So make it writable here.
+if [ -z "$container" ]; then
+    mount -n -o remount,rw none /
+fi
+
+
+# Likewise, stage 1 mounts /proc, /dev and /sys, so if we don't have a
+# stage 1, we need to do that here.
+if [ ! -e /proc/1 ]; then
+    specialMount() {
+        local device="$1"
+        local mountPoint="$2"
+        local options="$3"
+        local fsType="$4"
+
+        install -m 0755 -d "$mountPoint"
+        mount -n -t "$fsType" -o "$options" "$device" "$mountPoint"
+    }
+    source @earlyMountScript@
+fi
+
+
+echo "booting system configuration $systemConfig" > /dev/kmsg
+
+
+# Make /nix/store a read-only bind mount to enforce immutability of
+# the Nix store.  Note that we can't use "chown root:nixbld" here
+# because users/groups might not exist yet.
+# Silence chown/chmod to fail gracefully on a readonly filesystem
+# like squashfs.
+chown -f 0:30000 /nix/store
+chmod -f 1775 /nix/store
+if [ -n "@readOnlyStore@" ]; then
+    if ! [[ "$(findmnt --noheadings --output OPTIONS /nix/store)" =~ ro(,|$) ]]; then
+        if [ -z "$container" ]; then
+            mount --bind /nix/store /nix/store
+        else
+            mount --rbind /nix/store /nix/store
+        fi
+        mount -o remount,ro,bind /nix/store
+    fi
+fi
+
+
+# Provide a /etc/mtab.
+install -m 0755 -d /etc
+test -e /etc/fstab || touch /etc/fstab # to shut up mount
+rm -f /etc/mtab* # not that we care about stale locks
+ln -s /proc/mounts /etc/mtab
+
+
+# More special file systems, initialise required directories.
+[ -e /proc/bus/usb ] && mount -t usbfs usbfs /proc/bus/usb # UML doesn't have USB by default
+install -m 01777 -d /tmp
+install -m 0755 -d /var/{log,lib,db} /nix/var /etc/nixos/ \
+    /run/lock /home /bin # for the /bin/sh symlink
+
+
+# Miscellaneous boot time cleanup.
+rm -rf /var/run /var/lock
+rm -f /etc/{group,passwd,shadow}.lock
+
+
+# Also get rid of temporary GC roots.
+rm -rf /nix/var/nix/gcroots/tmp /nix/var/nix/temproots
+
+
+# For backwards compatibility, symlink /var/run to /run, and /var/lock
+# to /run/lock.
+ln -s /run /var/run
+ln -s /run/lock /var/lock
+
+
+# Clear the resume device.
+if test -n "$resumeDevice"; then
+    mkswap "$resumeDevice" || echo 'Failed to clear saved image.'
+fi
+
+
+# Use /etc/resolv.conf supplied by systemd-nspawn, if applicable.
+if [ -n "@useHostResolvConf@" ] && [ -e /etc/resolv.conf ]; then
+    resolvconf -m 1000 -a host </etc/resolv.conf
+fi
+
+# Log the script output to /dev/kmsg or /run/log/stage-2-init.log.
+# Only at this point are all the necessary prerequisites ready for these commands.
+exec {logOutFd}>&1 {logErrFd}>&2
+if test -w /dev/kmsg; then
+    exec > >(tee -i /proc/self/fd/"$logOutFd" | while read -r line; do
+        if test -n "$line"; then
+            echo "<7>stage-2-init: $line" > /dev/kmsg
+        fi
+    done) 2>&1
+else
+    mkdir -p /run/log
+    exec > >(tee -i /run/log/stage-2-init.log) 2>&1
+fi
+
+
+# Run the script that performs all configuration activation that does
+# not have to be done at boot time.
+echo "running activation script..."
+$systemConfig/activate
+
+
+# Restore the system time from the hardware clock.  We do this after
+# running the activation script to be sure that /etc/localtime points
+# at the current time zone.
+if [ -e /dev/rtc ]; then
+    hwclock --hctosys
+fi
+
+
+# Record the boot configuration.
+ln -sfn "$systemConfig" /run/booted-system
+
+# Prevent the booted system from being garbage-collected. If it weren't
+# a gcroot, if we were running a different kernel, switched system,
+# and garbage collected all, we could not load kernel modules anymore.
+ln -sfn /run/booted-system /nix/var/nix/gcroots/booted-system
+
+
+# Run any user-specified commands.
+@shell@ @postBootCommands@
+
+
+# Ensure systemd doesn't try to populate /etc, by forcing its first-boot
+# heuristic off. It doesn't matter what's in /etc/machine-id for this purpose,
+# and systemd will immediately fill in the file when it starts, so just
+# creating it is enough. This `: >>` pattern avoids forking and avoids changing
+# the mtime if the file already exists.
+: >> /etc/machine-id
+
+
+# Reset the logging file descriptors.
+exec 1>&$logOutFd 2>&$logErrFd
+exec {logOutFd}>&- {logErrFd}>&-
+
+
+# Start systemd.
+echo "starting systemd..."
+
+PATH=/run/current-system/systemd/lib/systemd:@fsPackagesPath@ \
+    LOCALE_ARCHIVE=/run/current-system/sw/lib/locale/locale-archive @systemdUnitPathEnvVar@ \
+    TZDIR=/etc/zoneinfo \
+    exec @systemdExecutable@
diff --git a/nixos/modules/system/boot/stage-2.nix b/nixos/modules/system/boot/stage-2.nix
new file mode 100644
index 00000000000..f6b6a8e4b0b
--- /dev/null
+++ b/nixos/modules/system/boot/stage-2.nix
@@ -0,0 +1,108 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  useHostResolvConf = config.networking.resolvconf.enable && config.networking.useHostResolvConf;
+
+  bootStage2 = pkgs.substituteAll {
+    src = ./stage-2-init.sh;
+    shellDebug = "${pkgs.bashInteractive}/bin/bash";
+    shell = "${pkgs.bash}/bin/bash";
+    inherit (config.boot) systemdExecutable extraSystemdUnitPaths;
+    isExecutable = true;
+    inherit (config.nix) readOnlyStore;
+    inherit useHostResolvConf;
+    inherit (config.system.build) earlyMountScript;
+    path = lib.makeBinPath ([
+      pkgs.coreutils
+      pkgs.util-linux
+    ] ++ lib.optional useHostResolvConf pkgs.openresolv);
+    fsPackagesPath = lib.makeBinPath config.system.fsPackages;
+    systemdUnitPathEnvVar = lib.optionalString (config.boot.extraSystemdUnitPaths != [])
+      ("SYSTEMD_UNIT_PATH="
+      + builtins.concatStringsSep ":" config.boot.extraSystemdUnitPaths
+      + ":"); # If SYSTEMD_UNIT_PATH ends with an empty component (":"), the usual unit load path will be appended to the contents of the variable
+    postBootCommands = pkgs.writeText "local-cmds"
+      ''
+        ${config.boot.postBootCommands}
+        ${config.powerManagement.powerUpCommands}
+      '';
+  };
+
+in
+
+{
+  options = {
+
+    boot = {
+
+      postBootCommands = mkOption {
+        default = "";
+        example = "rm -f /var/log/messages";
+        type = types.lines;
+        description = ''
+          Shell commands to be executed just before systemd is started.
+        '';
+      };
+
+      devSize = mkOption {
+        default = "5%";
+        example = "32m";
+        type = types.str;
+        description = ''
+          Size limit for the /dev tmpfs. Look at mount(8), tmpfs size option,
+          for the accepted syntax.
+        '';
+      };
+
+      devShmSize = mkOption {
+        default = "50%";
+        example = "256m";
+        type = types.str;
+        description = ''
+          Size limit for the /dev/shm tmpfs. Look at mount(8), tmpfs size option,
+          for the accepted syntax.
+        '';
+      };
+
+      runSize = mkOption {
+        default = "25%";
+        example = "256m";
+        type = types.str;
+        description = ''
+          Size limit for the /run tmpfs. Look at mount(8), tmpfs size option,
+          for the accepted syntax.
+        '';
+      };
+
+      systemdExecutable = mkOption {
+        default = "systemd";
+        type = types.str;
+        description = ''
+          The program to execute to start systemd. Typically
+          <literal>systemd</literal>, which will find systemd in the
+          PATH.
+        '';
+      };
+
+      extraSystemdUnitPaths = mkOption {
+        default = [];
+        type = types.listOf types.str;
+        description = ''
+          Additional paths that get appended to the SYSTEMD_UNIT_PATH environment variable
+          that can contain mutable unit files.
+        '';
+      };
+    };
+
+  };
+
+
+  config = {
+
+    system.build.bootStage2 = bootStage2;
+
+  };
+}
diff --git a/nixos/modules/system/boot/systemd-nspawn.nix b/nixos/modules/system/boot/systemd-nspawn.nix
new file mode 100644
index 00000000000..0c6822319a5
--- /dev/null
+++ b/nixos/modules/system/boot/systemd-nspawn.nix
@@ -0,0 +1,125 @@
+{ config, lib, pkgs, utils, ...}:
+
+with utils.systemdUtils.unitOptions;
+with utils.systemdUtils.lib;
+with lib;
+
+let
+  cfg = config.systemd.nspawn;
+
+  checkExec = checkUnitConfig "Exec" [
+    (assertOnlyFields [
+      "Boot" "ProcessTwo" "Parameters" "Environment" "User" "WorkingDirectory"
+      "PivotRoot" "Capability" "DropCapability" "NoNewPrivileges" "KillSignal"
+      "Personality" "MachineId" "PrivateUsers" "NotifyReady" "SystemCallFilter"
+      "LimitCPU" "LimitFSIZE" "LimitDATA" "LimitSTACK" "LimitCORE" "LimitRSS"
+      "LimitNOFILE" "LimitAS" "LimitNPROC" "LimitMEMLOCK" "LimitLOCKS"
+      "LimitSIGPENDING" "LimitMSGQUEUE" "LimitNICE" "LimitRTPRIO" "LimitRTTIME"
+      "OOMScoreAdjust" "CPUAffinity" "Hostname" "ResolvConf" "Timezone"
+      "LinkJournal"
+    ])
+    (assertValueOneOf "Boot" boolValues)
+    (assertValueOneOf "ProcessTwo" boolValues)
+    (assertValueOneOf "NotifyReady" boolValues)
+  ];
+
+  checkFiles = checkUnitConfig "Files" [
+    (assertOnlyFields [
+      "ReadOnly" "Volatile" "Bind" "BindReadOnly" "TemporaryFileSystem"
+      "Overlay" "OverlayReadOnly" "PrivateUsersChown"
+    ])
+    (assertValueOneOf "ReadOnly" boolValues)
+    (assertValueOneOf "Volatile" (boolValues ++ [ "state" ]))
+    (assertValueOneOf "PrivateUsersChown" boolValues)
+  ];
+
+  checkNetwork = checkUnitConfig "Network" [
+    (assertOnlyFields [
+      "Private" "VirtualEthernet" "VirtualEthernetExtra" "Interface" "MACVLAN"
+      "IPVLAN" "Bridge" "Zone" "Port"
+    ])
+    (assertValueOneOf "Private" boolValues)
+    (assertValueOneOf "VirtualEthernet" boolValues)
+  ];
+
+  instanceOptions = {
+    options = sharedOptions // {
+      execConfig = mkOption {
+        default = {};
+        example = { Parameters = "/bin/sh"; };
+        type = types.addCheck (types.attrsOf unitOption) checkExec;
+        description = ''
+          Each attribute in this set specifies an option in the
+          <literal>[Exec]</literal> section of this unit. See
+          <citerefentry><refentrytitle>systemd.nspawn</refentrytitle>
+          <manvolnum>5</manvolnum></citerefentry> for details.
+        '';
+      };
+
+      filesConfig = mkOption {
+        default = {};
+        example = { Bind = [ "/home/alice" ]; };
+        type = types.addCheck (types.attrsOf unitOption) checkFiles;
+        description = ''
+          Each attribute in this set specifies an option in the
+          <literal>[Files]</literal> section of this unit. See
+          <citerefentry><refentrytitle>systemd.nspawn</refentrytitle>
+          <manvolnum>5</manvolnum></citerefentry> for details.
+        '';
+      };
+
+      networkConfig = mkOption {
+        default = {};
+        example = { Private = false; };
+        type = types.addCheck (types.attrsOf unitOption) checkNetwork;
+        description = ''
+          Each attribute in this set specifies an option in the
+          <literal>[Network]</literal> section of this unit. See
+          <citerefentry><refentrytitle>systemd.nspawn</refentrytitle>
+          <manvolnum>5</manvolnum></citerefentry> for details.
+        '';
+      };
+    };
+
+  };
+
+  instanceToUnit = name: def:
+    let base = {
+      text = ''
+        [Exec]
+        ${attrsToSection def.execConfig}
+
+        [Files]
+        ${attrsToSection def.filesConfig}
+
+        [Network]
+        ${attrsToSection def.networkConfig}
+      '';
+    } // def;
+    in base // { unit = makeUnit name base; };
+
+in {
+
+  options = {
+
+    systemd.nspawn = mkOption {
+      default = {};
+      type = with types; attrsOf (submodule instanceOptions);
+      description = "Definition of systemd-nspawn configurations.";
+    };
+
+  };
+
+  config =
+    let
+      units = mapAttrs' (n: v: let nspawnFile = "${n}.nspawn"; in nameValuePair nspawnFile (instanceToUnit nspawnFile v)) cfg;
+    in
+      mkMerge [
+        (mkIf (cfg != {}) {
+          environment.etc."systemd/nspawn".source = mkIf (cfg != {}) (generateUnits' false "nspawn" units [] []);
+        })
+        {
+          systemd.targets.multi-user.wants = [ "machines.target" ];
+        }
+      ];
+}
diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix
new file mode 100644
index 00000000000..ff002d87a12
--- /dev/null
+++ b/nixos/modules/system/boot/systemd.nix
@@ -0,0 +1,1064 @@
+{ config, lib, pkgs, utils, ... }:
+
+with utils;
+with systemdUtils.unitOptions;
+with systemdUtils.lib;
+with lib;
+
+let
+
+  cfg = config.systemd;
+
+  systemd = cfg.package;
+
+  inherit (systemdUtils.lib)
+    makeJobScript
+    unitConfig
+    serviceConfig
+    mountConfig
+    automountConfig
+    commonUnitText
+    targetToUnit
+    serviceToUnit
+    socketToUnit
+    timerToUnit
+    pathToUnit
+    mountToUnit
+    automountToUnit
+    sliceToUnit;
+
+  upstreamSystemUnits =
+    [ # Targets.
+      "basic.target"
+      "sysinit.target"
+      "sockets.target"
+      "exit.target"
+      "graphical.target"
+      "multi-user.target"
+      "network.target"
+      "network-pre.target"
+      "network-online.target"
+      "nss-lookup.target"
+      "nss-user-lookup.target"
+      "time-sync.target"
+    ] ++ (optionals cfg.package.withCryptsetup [
+      "cryptsetup.target"
+      "cryptsetup-pre.target"
+      "remote-cryptsetup.target"
+    ]) ++ [
+      "sigpwr.target"
+      "timers.target"
+      "paths.target"
+      "rpcbind.target"
+
+      # Rescue mode.
+      "rescue.target"
+      "rescue.service"
+
+      # Udev.
+      "systemd-udevd-control.socket"
+      "systemd-udevd-kernel.socket"
+      "systemd-udevd.service"
+      "systemd-udev-settle.service"
+      ] ++ (optional (!config.boot.isContainer) "systemd-udev-trigger.service") ++ [
+      # hwdb.bin is managed by NixOS
+      # "systemd-hwdb-update.service"
+
+      # Consoles.
+      "getty.target"
+      "getty-pre.target"
+      "getty@.service"
+      "serial-getty@.service"
+      "console-getty.service"
+      "container-getty@.service"
+      "systemd-vconsole-setup.service"
+
+      # Hardware (started by udev when a relevant device is plugged in).
+      "sound.target"
+      "bluetooth.target"
+      "printer.target"
+      "smartcard.target"
+
+      # Login stuff.
+      "systemd-logind.service"
+      "autovt@.service"
+      "systemd-user-sessions.service"
+      "dbus-org.freedesktop.import1.service"
+      "dbus-org.freedesktop.machine1.service"
+      "dbus-org.freedesktop.login1.service"
+      "user@.service"
+      "user-runtime-dir@.service"
+
+      # Journal.
+      "systemd-journald.socket"
+      "systemd-journald@.socket"
+      "systemd-journald-varlink@.socket"
+      "systemd-journald.service"
+      "systemd-journald@.service"
+      "systemd-journal-flush.service"
+      "systemd-journal-catalog-update.service"
+      ] ++ (optional (!config.boot.isContainer) "systemd-journald-audit.socket") ++ [
+      "systemd-journald-dev-log.socket"
+      "syslog.socket"
+
+      # Coredumps.
+      "systemd-coredump.socket"
+      "systemd-coredump@.service"
+
+      # Kernel module loading.
+      "systemd-modules-load.service"
+      "kmod-static-nodes.service"
+      "modprobe@.service"
+
+      # Filesystems.
+      "systemd-fsck@.service"
+      "systemd-fsck-root.service"
+      "systemd-remount-fs.service"
+      "systemd-pstore.service"
+      "local-fs.target"
+      "local-fs-pre.target"
+      "remote-fs.target"
+      "remote-fs-pre.target"
+      "swap.target"
+      "dev-hugepages.mount"
+      "dev-mqueue.mount"
+      "sys-fs-fuse-connections.mount"
+      ] ++ (optional (!config.boot.isContainer) "sys-kernel-config.mount") ++ [
+      "sys-kernel-debug.mount"
+
+      # Maintaining state across reboots.
+      "systemd-random-seed.service"
+      "systemd-backlight@.service"
+      "systemd-rfkill.service"
+      "systemd-rfkill.socket"
+
+      # Hibernate / suspend.
+      "hibernate.target"
+      "suspend.target"
+      "suspend-then-hibernate.target"
+      "sleep.target"
+      "hybrid-sleep.target"
+      "systemd-hibernate.service"
+      "systemd-hybrid-sleep.service"
+      "systemd-suspend.service"
+      "systemd-suspend-then-hibernate.service"
+
+      # Reboot stuff.
+      "reboot.target"
+      "systemd-reboot.service"
+      "poweroff.target"
+      "systemd-poweroff.service"
+      "halt.target"
+      "systemd-halt.service"
+      "shutdown.target"
+      "umount.target"
+      "final.target"
+      "kexec.target"
+      "systemd-kexec.service"
+      "systemd-update-utmp.service"
+
+      # Password entry.
+      "systemd-ask-password-console.path"
+      "systemd-ask-password-console.service"
+      "systemd-ask-password-wall.path"
+      "systemd-ask-password-wall.service"
+
+      # Slices / containers.
+      "slices.target"
+      "user.slice"
+      "machine.slice"
+      "machines.target"
+      "systemd-importd.service"
+      "systemd-machined.service"
+      "systemd-nspawn@.service"
+
+      # Temporary file creation / cleanup.
+      "systemd-tmpfiles-clean.service"
+      "systemd-tmpfiles-clean.timer"
+      "systemd-tmpfiles-setup.service"
+      "systemd-tmpfiles-setup-dev.service"
+
+      # Misc.
+      "systemd-sysctl.service"
+      "dbus-org.freedesktop.timedate1.service"
+      "dbus-org.freedesktop.locale1.service"
+      "dbus-org.freedesktop.hostname1.service"
+      "systemd-timedated.service"
+      "systemd-localed.service"
+      "systemd-hostnamed.service"
+      "systemd-exit.service"
+      "systemd-update-done.service"
+    ] ++ optionals config.services.journald.enableHttpGateway [
+      "systemd-journal-gatewayd.socket"
+      "systemd-journal-gatewayd.service"
+    ] ++ cfg.additionalUpstreamSystemUnits;
+
+  upstreamSystemWants =
+    [ "sysinit.target.wants"
+      "sockets.target.wants"
+      "local-fs.target.wants"
+      "multi-user.target.wants"
+      "timers.target.wants"
+    ];
+
+    upstreamUserUnits = [
+      "app.slice"
+      "background.slice"
+      "basic.target"
+      "bluetooth.target"
+      "default.target"
+      "exit.target"
+      "graphical-session-pre.target"
+      "graphical-session.target"
+      "paths.target"
+      "printer.target"
+      "session.slice"
+      "shutdown.target"
+      "smartcard.target"
+      "sockets.target"
+      "sound.target"
+      "systemd-exit.service"
+      "systemd-tmpfiles-clean.service"
+      "systemd-tmpfiles-clean.timer"
+      "systemd-tmpfiles-setup.service"
+      "timers.target"
+      "xdg-desktop-autostart.target"
+    ];
+
+
+  logindHandlerType = types.enum [
+    "ignore" "poweroff" "reboot" "halt" "kexec" "suspend"
+    "hibernate" "hybrid-sleep" "suspend-then-hibernate" "lock"
+  ];
+
+  proxy_env = config.networking.proxy.envVars;
+
+in
+
+{
+  ###### interface
+
+  options = {
+
+    systemd.package = mkOption {
+      default = pkgs.systemd;
+      defaultText = literalExpression "pkgs.systemd";
+      type = types.package;
+      description = "The systemd package.";
+    };
+
+    systemd.units = mkOption {
+      description = "Definition of systemd units.";
+      default = {};
+      type = with types; attrsOf (submodule (
+        { name, config, ... }:
+        { options = concreteUnitOptions;
+          config = {
+            unit = mkDefault (makeUnit name config);
+          };
+        }));
+    };
+
+    systemd.packages = mkOption {
+      default = [];
+      type = types.listOf types.package;
+      example = literalExpression "[ pkgs.systemd-cryptsetup-generator ]";
+      description = "Packages providing systemd units and hooks.";
+    };
+
+    systemd.targets = mkOption {
+      default = {};
+      type = with types; attrsOf (submodule [ { options = targetOptions; } unitConfig] );
+      description = "Definition of systemd target units.";
+    };
+
+    systemd.services = mkOption {
+      default = {};
+      type = with types; attrsOf (submodule [ { options = serviceOptions; } unitConfig serviceConfig ]);
+      description = "Definition of systemd service units.";
+    };
+
+    systemd.sockets = mkOption {
+      default = {};
+      type = with types; attrsOf (submodule [ { options = socketOptions; } unitConfig ]);
+      description = "Definition of systemd socket units.";
+    };
+
+    systemd.timers = mkOption {
+      default = {};
+      type = with types; attrsOf (submodule [ { options = timerOptions; } unitConfig ]);
+      description = "Definition of systemd timer units.";
+    };
+
+    systemd.paths = mkOption {
+      default = {};
+      type = with types; attrsOf (submodule [ { options = pathOptions; } unitConfig ]);
+      description = "Definition of systemd path units.";
+    };
+
+    systemd.mounts = mkOption {
+      default = [];
+      type = with types; listOf (submodule [ { options = mountOptions; } unitConfig mountConfig ]);
+      description = ''
+        Definition of systemd mount units.
+        This is a list instead of an attrSet, because systemd mandates the names to be derived from
+        the 'where' attribute.
+      '';
+    };
+
+    systemd.automounts = mkOption {
+      default = [];
+      type = with types; listOf (submodule [ { options = automountOptions; } unitConfig automountConfig ]);
+      description = ''
+        Definition of systemd automount units.
+        This is a list instead of an attrSet, because systemd mandates the names to be derived from
+        the 'where' attribute.
+      '';
+    };
+
+    systemd.slices = mkOption {
+      default = {};
+      type = with types; attrsOf (submodule [ { options = sliceOptions; } unitConfig] );
+      description = "Definition of slice configurations.";
+    };
+
+    systemd.generators = mkOption {
+      type = types.attrsOf types.path;
+      default = {};
+      example = { systemd-gpt-auto-generator = "/dev/null"; };
+      description = ''
+        Definition of systemd generators.
+        For each <literal>NAME = VALUE</literal> pair of the attrSet, a link is generated from
+        <literal>/etc/systemd/system-generators/NAME</literal> to <literal>VALUE</literal>.
+      '';
+    };
+
+    systemd.shutdown = mkOption {
+      type = types.attrsOf types.path;
+      default = {};
+      description = ''
+        Definition of systemd shutdown executables.
+        For each <literal>NAME = VALUE</literal> pair of the attrSet, a link is generated from
+        <literal>/etc/systemd/system-shutdown/NAME</literal> to <literal>VALUE</literal>.
+      '';
+    };
+
+    systemd.defaultUnit = mkOption {
+      default = "multi-user.target";
+      type = types.str;
+      description = "Default unit started when the system boots.";
+    };
+
+    systemd.ctrlAltDelUnit = mkOption {
+      default = "reboot.target";
+      type = types.str;
+      example = "poweroff.target";
+      description = ''
+        Target that should be started when Ctrl-Alt-Delete is pressed.
+      '';
+    };
+
+    systemd.globalEnvironment = mkOption {
+      type = with types; attrsOf (nullOr (oneOf [ str path package ]));
+      default = {};
+      example = { TZ = "CET"; };
+      description = ''
+        Environment variables passed to <emphasis>all</emphasis> systemd units.
+      '';
+    };
+
+    systemd.enableCgroupAccounting = mkOption {
+      default = true;
+      type = types.bool;
+      description = ''
+        Whether to enable cgroup accounting.
+      '';
+    };
+
+    systemd.enableUnifiedCgroupHierarchy = mkOption {
+      default = true;
+      type = types.bool;
+      description = ''
+        Whether to enable the unified cgroup hierarchy (cgroupsv2).
+      '';
+    };
+
+    systemd.coredump.enable = mkOption {
+      default = true;
+      type = types.bool;
+      description = ''
+        Whether core dumps should be processed by
+        <command>systemd-coredump</command>. If disabled, core dumps
+        appear in the current directory of the crashing process.
+      '';
+    };
+
+    systemd.coredump.extraConfig = mkOption {
+      default = "";
+      type = types.lines;
+      example = "Storage=journal";
+      description = ''
+        Extra config options for systemd-coredump. See coredump.conf(5) man page
+        for available options.
+      '';
+    };
+
+    systemd.extraConfig = mkOption {
+      default = "";
+      type = types.lines;
+      example = "DefaultLimitCORE=infinity";
+      description = ''
+        Extra config options for systemd. See man systemd-system.conf for
+        available options.
+      '';
+    };
+
+    services.journald.console = mkOption {
+      default = "";
+      type = types.str;
+      description = "If non-empty, write log messages to the specified TTY device.";
+    };
+
+    services.journald.rateLimitInterval = mkOption {
+      default = "30s";
+      type = types.str;
+      description = ''
+        Configures the rate limiting interval that is applied to all
+        messages generated on the system. This rate limiting is applied
+        per-service, so that two services which log do not interfere with
+        each other's limit. The value may be specified in the following
+        units: s, min, h, ms, us. To turn off any kind of rate limiting,
+        set either value to 0.
+
+        See <option>services.journald.rateLimitBurst</option> for important
+        considerations when setting this value.
+      '';
+    };
+
+    services.journald.rateLimitBurst = mkOption {
+      default = 10000;
+      type = types.int;
+      description = ''
+        Configures the rate limiting burst limit (number of messages per
+        interval) that is applied to all messages generated on the system.
+        This rate limiting is applied per-service, so that two services
+        which log do not interfere with each other's limit.
+
+        Note that the effective rate limit is multiplied by a factor derived
+        from the available free disk space for the journal as described on
+        <link xlink:href="https://www.freedesktop.org/software/systemd/man/journald.conf.html">
+        journald.conf(5)</link>.
+
+        Note that the total amount of logs stored is limited by journald settings
+        such as <literal>SystemMaxUse</literal>, which defaults to a 4 GB cap.
+
+        It is thus recommended to compute what period of time that you will be
+        able to store logs for when an application logs at full burst rate.
+        With default settings for log lines that are 100 Bytes long, this can
+        amount to just a few hours.
+      '';
+    };
+
+    services.journald.extraConfig = mkOption {
+      default = "";
+      type = types.lines;
+      example = "Storage=volatile";
+      description = ''
+        Extra config options for systemd-journald. See man journald.conf
+        for available options.
+      '';
+    };
+
+    services.journald.enableHttpGateway = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Whether to enable the HTTP gateway to the journal.
+      '';
+    };
+
+    services.journald.forwardToSyslog = mkOption {
+      default = config.services.rsyslogd.enable || config.services.syslog-ng.enable;
+      defaultText = literalExpression "services.rsyslogd.enable || services.syslog-ng.enable";
+      type = types.bool;
+      description = ''
+        Whether to forward log messages to syslog.
+      '';
+    };
+
+    services.logind.extraConfig = mkOption {
+      default = "";
+      type = types.lines;
+      example = "IdleAction=lock";
+      description = ''
+        Extra config options for systemd-logind. See
+        <link xlink:href="https://www.freedesktop.org/software/systemd/man/logind.conf.html">
+        logind.conf(5)</link> for available options.
+      '';
+    };
+
+    services.logind.killUserProcesses = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Specifies whether the processes of a user should be killed
+        when the user logs out.  If true, the scope unit corresponding
+        to the session and all processes inside that scope will be
+        terminated.  If false, the scope is "abandoned" (see
+        <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemd.scope.html#">
+        systemd.scope(5)</link>), and processes are not killed.
+        </para>
+
+        <para>
+        See <link xlink:href="https://www.freedesktop.org/software/systemd/man/logind.conf.html#KillUserProcesses=">logind.conf(5)</link>
+        for more details.
+      '';
+    };
+
+    services.logind.lidSwitch = mkOption {
+      default = "suspend";
+      example = "ignore";
+      type = logindHandlerType;
+
+      description = ''
+        Specifies what to be done when the laptop lid is closed.
+      '';
+    };
+
+    services.logind.lidSwitchDocked = mkOption {
+      default = "ignore";
+      example = "suspend";
+      type = logindHandlerType;
+
+      description = ''
+        Specifies what to be done when the laptop lid is closed
+        and another screen is added.
+      '';
+    };
+
+    services.logind.lidSwitchExternalPower = mkOption {
+      default = config.services.logind.lidSwitch;
+      defaultText = literalExpression "services.logind.lidSwitch";
+      example = "ignore";
+      type = logindHandlerType;
+
+      description = ''
+        Specifies what to do when the laptop lid is closed and the system is
+        on external power. By default use the same action as specified in
+        services.logind.lidSwitch.
+      '';
+    };
+
+    systemd.sleep.extraConfig = mkOption {
+      default = "";
+      type = types.lines;
+      example = "HibernateDelaySec=1h";
+      description = ''
+        Extra config options for systemd sleep state logic.
+        See sleep.conf.d(5) man page for available options.
+      '';
+    };
+
+    systemd.user.extraConfig = mkOption {
+      default = "";
+      type = types.lines;
+      example = "DefaultCPUAccounting=yes";
+      description = ''
+        Extra config options for systemd user instances. See man systemd-user.conf for
+        available options.
+      '';
+    };
+
+    systemd.tmpfiles.rules = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = [ "d /tmp 1777 root root 10d" ];
+      description = ''
+        Rules for creation, deletion and cleaning of volatile and temporary files
+        automatically. See
+        <citerefentry><refentrytitle>tmpfiles.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        for the exact format.
+      '';
+    };
+
+    systemd.tmpfiles.packages = mkOption {
+      type = types.listOf types.package;
+      default = [];
+      example = literalExpression "[ pkgs.lvm2 ]";
+      apply = map getLib;
+      description = ''
+        List of packages containing <command>systemd-tmpfiles</command> rules.
+
+        All files ending in .conf found in
+        <filename><replaceable>pkg</replaceable>/lib/tmpfiles.d</filename>
+        will be included.
+        If this folder does not exist or does not contain any files an error will be returned instead.
+
+        If a <filename>lib</filename> output is available, rules are searched there and only there.
+        If there is no <filename>lib</filename> output it will fall back to <filename>out</filename>
+        and if that does not exist either, the default output will be used.
+      '';
+    };
+
+    systemd.user.units = mkOption {
+      description = "Definition of systemd per-user units.";
+      default = {};
+      type = with types; attrsOf (submodule (
+        { name, config, ... }:
+        { options = concreteUnitOptions;
+          config = {
+            unit = mkDefault (makeUnit name config);
+          };
+        }));
+    };
+
+    systemd.user.paths = mkOption {
+      default = {};
+      type = with types; attrsOf (submodule [ { options = pathOptions; } unitConfig ]);
+      description = "Definition of systemd per-user path units.";
+    };
+
+    systemd.user.services = mkOption {
+      default = {};
+      type = with types; attrsOf (submodule [ { options = serviceOptions; } unitConfig serviceConfig ] );
+      description = "Definition of systemd per-user service units.";
+    };
+
+    systemd.user.slices = mkOption {
+      default = {};
+      type = with types; attrsOf (submodule [ { options = sliceOptions; } unitConfig ] );
+      description = "Definition of systemd per-user slice units.";
+    };
+
+    systemd.user.sockets = mkOption {
+      default = {};
+      type = with types; attrsOf (submodule [ { options = socketOptions; } unitConfig ] );
+      description = "Definition of systemd per-user socket units.";
+    };
+
+    systemd.user.targets = mkOption {
+      default = {};
+      type = with types; attrsOf (submodule [ { options = targetOptions; } unitConfig] );
+      description = "Definition of systemd per-user target units.";
+    };
+
+    systemd.user.timers = mkOption {
+      default = {};
+      type = with types; attrsOf (submodule [ { options = timerOptions; } unitConfig ] );
+      description = "Definition of systemd per-user timer units.";
+    };
+
+    systemd.additionalUpstreamSystemUnits = mkOption {
+      default = [ ];
+      type = types.listOf types.str;
+      example = [ "debug-shell.service" "systemd-quotacheck.service" ];
+      description = ''
+        Additional units shipped with systemd that shall be enabled.
+      '';
+    };
+
+    systemd.suppressedSystemUnits = mkOption {
+      default = [ ];
+      type = types.listOf types.str;
+      example = [ "systemd-backlight@.service" ];
+      description = ''
+        A list of units to suppress when generating system systemd configuration directory. This has
+        priority over upstream units, <option>systemd.units</option>, and
+        <option>systemd.additionalUpstreamSystemUnits</option>. The main purpose of this is to
+        suppress a upstream systemd unit with any modifications made to it by other NixOS modules.
+      '';
+    };
+
+    systemd.watchdog.device = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      example = "/dev/watchdog";
+      description = ''
+        The path to a hardware watchdog device which will be managed by systemd.
+        If not specified, systemd will default to /dev/watchdog.
+      '';
+    };
+
+    systemd.watchdog.runtimeTime = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "30s";
+      description = ''
+        The amount of time which can elapse before a watchdog hardware device
+        will automatically reboot the system. Valid time units include "ms",
+        "s", "min", "h", "d", and "w".
+      '';
+    };
+
+    systemd.watchdog.rebootTime = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "10m";
+      description = ''
+        The amount of time which can elapse after a reboot has been triggered
+        before a watchdog hardware device will automatically reboot the system.
+        Valid time units include "ms", "s", "min", "h", "d", and "w".
+      '';
+    };
+
+    systemd.watchdog.kexecTime = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "10m";
+      description = ''
+        The amount of time which can elapse when kexec is being executed before
+        a watchdog hardware device will automatically reboot the system. This
+        option should only be enabled if reloadTime is also enabled. Valid
+        time units include "ms", "s", "min", "h", "d", and "w".
+      '';
+    };
+  };
+
+
+  ###### implementation
+
+  config = {
+
+    warnings = concatLists (
+      mapAttrsToList
+        (name: service:
+          let
+            type = service.serviceConfig.Type or "";
+            restart = service.serviceConfig.Restart or "no";
+            hasDeprecated = builtins.hasAttr "StartLimitInterval" service.serviceConfig;
+          in
+            concatLists [
+              (optional (type == "oneshot" && (restart == "always" || restart == "on-success"))
+                "Service '${name}.service' with 'Type=oneshot' cannot have 'Restart=always' or 'Restart=on-success'"
+              )
+              (optional hasDeprecated
+                "Service '${name}.service' uses the attribute 'StartLimitInterval' in the Service section, which is deprecated. See https://github.com/NixOS/nixpkgs/issues/45786."
+              )
+              (optional (service.reloadIfChanged && service.reloadTriggers != [])
+                "Service '${name}.service' has both 'reloadIfChanged' and 'reloadTriggers' set. This is probably not what you want, because 'reloadTriggers' behave the same whay as 'restartTriggers' if 'reloadIfChanged' is set."
+              )
+            ]
+        )
+        cfg.services
+    );
+
+    system.build.units = cfg.units;
+
+    system.nssModules = [ systemd.out ];
+    system.nssDatabases = {
+      hosts = (mkMerge [
+        (mkOrder 400 ["mymachines"]) # 400 to ensure it comes before resolve (which is mkBefore'd)
+        (mkOrder 999 ["myhostname"]) # after files (which is 998), but before regular nss modules
+      ]);
+      passwd = (mkMerge [
+        (mkAfter [ "systemd" ])
+      ]);
+      group = (mkMerge [
+        (mkAfter [ "systemd" ])
+      ]);
+    };
+
+    environment.systemPackages = [ systemd ];
+
+    environment.etc = let
+      # generate contents for /etc/systemd/system-${type} from attrset of links and packages
+      hooks = type: links: pkgs.runCommand "system-${type}" {
+          preferLocalBuild = true;
+          packages = cfg.packages;
+      } ''
+        set -e
+        mkdir -p $out
+        for package in $packages
+        do
+          for hook in $package/lib/systemd/system-${type}/*
+          do
+            ln -s $hook $out/
+          done
+        done
+        ${concatStrings (mapAttrsToList (exec: target: "ln -s ${target} $out/${exec};\n") links)}
+      '';
+
+      enabledUpstreamSystemUnits = filter (n: ! elem n cfg.suppressedSystemUnits) upstreamSystemUnits;
+      enabledUnits = filterAttrs (n: v: ! elem n cfg.suppressedSystemUnits) cfg.units;
+    in ({
+      "systemd/system".source = generateUnits "system" enabledUnits enabledUpstreamSystemUnits upstreamSystemWants;
+
+      "systemd/user".source = generateUnits "user" cfg.user.units upstreamUserUnits [];
+
+      "systemd/system.conf".text = ''
+        [Manager]
+        ${optionalString config.systemd.enableCgroupAccounting ''
+          DefaultCPUAccounting=yes
+          DefaultIOAccounting=yes
+          DefaultBlockIOAccounting=yes
+          DefaultIPAccounting=yes
+        ''}
+        DefaultLimitCORE=infinity
+        ${optionalString (config.systemd.watchdog.device != null) ''
+          WatchdogDevice=${config.systemd.watchdog.device}
+        ''}
+        ${optionalString (config.systemd.watchdog.runtimeTime != null) ''
+          RuntimeWatchdogSec=${config.systemd.watchdog.runtimeTime}
+        ''}
+        ${optionalString (config.systemd.watchdog.rebootTime != null) ''
+          RebootWatchdogSec=${config.systemd.watchdog.rebootTime}
+        ''}
+        ${optionalString (config.systemd.watchdog.kexecTime != null) ''
+          KExecWatchdogSec=${config.systemd.watchdog.kexecTime}
+        ''}
+
+        ${config.systemd.extraConfig}
+      '';
+
+      "systemd/user.conf".text = ''
+        [Manager]
+        ${config.systemd.user.extraConfig}
+      '';
+
+      "systemd/journald.conf".text = ''
+        [Journal]
+        Storage=persistent
+        RateLimitInterval=${config.services.journald.rateLimitInterval}
+        RateLimitBurst=${toString config.services.journald.rateLimitBurst}
+        ${optionalString (config.services.journald.console != "") ''
+          ForwardToConsole=yes
+          TTYPath=${config.services.journald.console}
+        ''}
+        ${optionalString (config.services.journald.forwardToSyslog) ''
+          ForwardToSyslog=yes
+        ''}
+        ${config.services.journald.extraConfig}
+      '';
+
+      "systemd/coredump.conf".text =
+        ''
+          [Coredump]
+          ${config.systemd.coredump.extraConfig}
+        '';
+
+      "systemd/logind.conf".text = ''
+        [Login]
+        KillUserProcesses=${if config.services.logind.killUserProcesses then "yes" else "no"}
+        HandleLidSwitch=${config.services.logind.lidSwitch}
+        HandleLidSwitchDocked=${config.services.logind.lidSwitchDocked}
+        HandleLidSwitchExternalPower=${config.services.logind.lidSwitchExternalPower}
+        ${config.services.logind.extraConfig}
+      '';
+
+      "systemd/sleep.conf".text = ''
+        [Sleep]
+        ${config.systemd.sleep.extraConfig}
+      '';
+
+      # install provided sysctl snippets
+      "sysctl.d/50-coredump.conf".source = "${systemd}/example/sysctl.d/50-coredump.conf";
+      "sysctl.d/50-default.conf".source = "${systemd}/example/sysctl.d/50-default.conf";
+
+      "tmpfiles.d".source = (pkgs.symlinkJoin {
+        name = "tmpfiles.d";
+        paths = map (p: p + "/lib/tmpfiles.d") cfg.tmpfiles.packages;
+        postBuild = ''
+          for i in $(cat $pathsPath); do
+            (test -d "$i" && test $(ls "$i"/*.conf | wc -l) -ge 1) || (
+              echo "ERROR: The path '$i' from systemd.tmpfiles.packages contains no *.conf files."
+              exit 1
+            )
+          done
+        '' + concatMapStrings (name: optionalString (hasPrefix "tmpfiles.d/" name) ''
+          rm -f $out/${removePrefix "tmpfiles.d/" name}
+        '') config.system.build.etc.passthru.targets;
+      }) + "/*";
+
+      "systemd/system-generators" = { source = hooks "generators" cfg.generators; };
+      "systemd/system-shutdown" = { source = hooks "shutdown" cfg.shutdown; };
+    });
+
+    services.dbus.enable = true;
+
+    users.users.systemd-coredump = {
+      uid = config.ids.uids.systemd-coredump;
+      group = "systemd-coredump";
+    };
+    users.groups.systemd-coredump = {};
+    users.users.systemd-network = {
+      uid = config.ids.uids.systemd-network;
+      group = "systemd-network";
+    };
+    users.groups.systemd-network.gid = config.ids.gids.systemd-network;
+    users.users.systemd-resolve = {
+      uid = config.ids.uids.systemd-resolve;
+      group = "systemd-resolve";
+    };
+    users.groups.systemd-resolve.gid = config.ids.gids.systemd-resolve;
+
+    # Target for ‘charon send-keys’ to hook into.
+    users.groups.keys.gid = config.ids.gids.keys;
+
+    systemd.targets.keys =
+      { description = "Security Keys";
+        unitConfig.X-StopOnReconfiguration = true;
+      };
+
+    systemd.tmpfiles.packages = [
+      # Default tmpfiles rules provided by systemd
+      (pkgs.runCommand "systemd-default-tmpfiles" {} ''
+        mkdir -p $out/lib/tmpfiles.d
+        cd $out/lib/tmpfiles.d
+
+        ln -s "${systemd}/example/tmpfiles.d/home.conf"
+        ln -s "${systemd}/example/tmpfiles.d/journal-nocow.conf"
+        ln -s "${systemd}/example/tmpfiles.d/static-nodes-permissions.conf"
+        ln -s "${systemd}/example/tmpfiles.d/systemd.conf"
+        ln -s "${systemd}/example/tmpfiles.d/systemd-nologin.conf"
+        ln -s "${systemd}/example/tmpfiles.d/systemd-nspawn.conf"
+        ln -s "${systemd}/example/tmpfiles.d/systemd-tmp.conf"
+        ln -s "${systemd}/example/tmpfiles.d/tmp.conf"
+        ln -s "${systemd}/example/tmpfiles.d/var.conf"
+        ln -s "${systemd}/example/tmpfiles.d/x11.conf"
+      '')
+      # User-specified tmpfiles rules
+      (pkgs.writeTextFile {
+        name = "nixos-tmpfiles.d";
+        destination = "/lib/tmpfiles.d/00-nixos.conf";
+        text = ''
+          # This file is created automatically and should not be modified.
+          # Please change the option ‘systemd.tmpfiles.rules’ instead.
+
+          ${concatStringsSep "\n" cfg.tmpfiles.rules}
+        '';
+      })
+    ];
+
+    systemd.units =
+         mapAttrs' (n: v: nameValuePair "${n}.path"    (pathToUnit    n v)) cfg.paths
+      // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.services
+      // mapAttrs' (n: v: nameValuePair "${n}.slice"   (sliceToUnit   n v)) cfg.slices
+      // mapAttrs' (n: v: nameValuePair "${n}.socket"  (socketToUnit  n v)) cfg.sockets
+      // mapAttrs' (n: v: nameValuePair "${n}.target"  (targetToUnit  n v)) cfg.targets
+      // mapAttrs' (n: v: nameValuePair "${n}.timer"   (timerToUnit   n v)) cfg.timers
+      // listToAttrs (map
+                   (v: let n = escapeSystemdPath v.where;
+                       in nameValuePair "${n}.mount" (mountToUnit n v)) cfg.mounts)
+      // listToAttrs (map
+                   (v: let n = escapeSystemdPath v.where;
+                       in nameValuePair "${n}.automount" (automountToUnit n v)) cfg.automounts);
+
+    systemd.user.units =
+         mapAttrs' (n: v: nameValuePair "${n}.path"    (pathToUnit    n v)) cfg.user.paths
+      // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.user.services
+      // mapAttrs' (n: v: nameValuePair "${n}.slice"   (sliceToUnit   n v)) cfg.user.slices
+      // mapAttrs' (n: v: nameValuePair "${n}.socket"  (socketToUnit  n v)) cfg.user.sockets
+      // mapAttrs' (n: v: nameValuePair "${n}.target"  (targetToUnit  n v)) cfg.user.targets
+      // mapAttrs' (n: v: nameValuePair "${n}.timer"   (timerToUnit   n v)) cfg.user.timers;
+
+    system.requiredKernelConfig = map config.lib.kernelConfig.isEnabled
+      [ "DEVTMPFS" "CGROUPS" "INOTIFY_USER" "SIGNALFD" "TIMERFD" "EPOLL" "NET"
+        "SYSFS" "PROC_FS" "FHANDLE" "CRYPTO_USER_API_HASH" "CRYPTO_HMAC"
+        "CRYPTO_SHA256" "DMIID" "AUTOFS4_FS" "TMPFS_POSIX_ACL"
+        "TMPFS_XATTR" "SECCOMP"
+      ];
+
+    users.groups.systemd-journal.gid = config.ids.gids.systemd-journal;
+    users.users.systemd-journal-gateway.uid = config.ids.uids.systemd-journal-gateway;
+    users.users.systemd-journal-gateway.group = "systemd-journal-gateway";
+    users.groups.systemd-journal-gateway.gid = config.ids.gids.systemd-journal-gateway;
+
+    # Generate timer units for all services that have a ‘startAt’ value.
+    systemd.timers =
+      mapAttrs (name: service:
+        { wantedBy = [ "timers.target" ];
+          timerConfig.OnCalendar = service.startAt;
+        })
+        (filterAttrs (name: service: service.enable && service.startAt != []) cfg.services);
+
+    # Generate timer units for all services that have a ‘startAt’ value.
+    systemd.user.timers =
+      mapAttrs (name: service:
+        { wantedBy = [ "timers.target" ];
+          timerConfig.OnCalendar = service.startAt;
+        })
+        (filterAttrs (name: service: service.startAt != []) cfg.user.services);
+
+    systemd.sockets.systemd-journal-gatewayd.wantedBy =
+      optional config.services.journald.enableHttpGateway "sockets.target";
+
+    # Provide the systemd-user PAM service, required to run systemd
+    # user instances.
+    security.pam.services.systemd-user =
+      { # Ensure that pam_systemd gets included. This is special-cased
+        # in systemd to provide XDG_RUNTIME_DIR.
+        startSession = true;
+      };
+
+    # Some overrides to upstream units.
+    systemd.services."systemd-backlight@".restartIfChanged = false;
+    systemd.services."systemd-fsck@".restartIfChanged = false;
+    systemd.services."systemd-fsck@".path = [ config.system.path ];
+    systemd.services."user@".restartIfChanged = false;
+    systemd.services.systemd-journal-flush.restartIfChanged = false;
+    systemd.services.systemd-random-seed.restartIfChanged = false;
+    systemd.services.systemd-remount-fs.restartIfChanged = false;
+    systemd.services.systemd-update-utmp.restartIfChanged = false;
+    systemd.services.systemd-user-sessions.restartIfChanged = false; # Restart kills all active sessions.
+    systemd.services.systemd-udev-settle.restartIfChanged = false; # Causes long delays in nixos-rebuild
+    # Restarting systemd-logind breaks X11
+    # - upstream commit: https://cgit.freedesktop.org/xorg/xserver/commit/?id=dc48bd653c7e101
+    # - systemd announcement: https://github.com/systemd/systemd/blob/22043e4317ecd2bc7834b48a6d364de76bb26d91/NEWS#L103-L112
+    # - this might be addressed in the future by xorg
+    #systemd.services.systemd-logind.restartTriggers = [ config.environment.etc."systemd/logind.conf".source ];
+    systemd.services.systemd-logind.restartIfChanged = false;
+    systemd.services.systemd-logind.stopIfChanged = false;
+    # The user-runtime-dir@ service is managed by systemd-logind we should not touch it or else we break the users' sessions.
+    systemd.services."user-runtime-dir@".stopIfChanged = false;
+    systemd.services."user-runtime-dir@".restartIfChanged = false;
+    systemd.services.systemd-journald.restartTriggers = [ config.environment.etc."systemd/journald.conf".source ];
+    systemd.services.systemd-journald.stopIfChanged = false;
+    systemd.services."systemd-journald@".restartTriggers = [ config.environment.etc."systemd/journald.conf".source ];
+    systemd.services."systemd-journald@".stopIfChanged = false;
+    systemd.targets.local-fs.unitConfig.X-StopOnReconfiguration = true;
+    systemd.targets.remote-fs.unitConfig.X-StopOnReconfiguration = true;
+    systemd.targets.network-online.wantedBy = [ "multi-user.target" ];
+    systemd.services.systemd-importd.environment = proxy_env;
+    systemd.services.systemd-pstore.wantedBy = [ "sysinit.target" ]; # see #81138
+
+    # Don't bother with certain units in containers.
+    systemd.services.systemd-remount-fs.unitConfig.ConditionVirtualization = "!container";
+    systemd.services.systemd-random-seed.unitConfig.ConditionVirtualization = "!container";
+
+    boot.kernel.sysctl."kernel.core_pattern" = mkIf (!cfg.coredump.enable) "core";
+
+    # Increase numeric PID range (set directly instead of copying a one-line file from systemd)
+    # https://github.com/systemd/systemd/pull/12226
+    boot.kernel.sysctl."kernel.pid_max" = mkIf pkgs.stdenv.is64bit (lib.mkDefault 4194304);
+
+    boot.kernelParams = optional (!cfg.enableUnifiedCgroupHierarchy) "systemd.unified_cgroup_hierarchy=0";
+
+    services.logrotate.paths = {
+      "/var/log/btmp" = mapAttrs (_: mkDefault) {
+        frequency = "monthly";
+        keep = 1;
+        extraConfig = ''
+          create 0660 root ${config.users.groups.utmp.name}
+          minsize 1M
+        '';
+      };
+      "/var/log/wtmp" = mapAttrs (_: mkDefault) {
+        frequency = "monthly";
+        keep = 1;
+        extraConfig = ''
+          create 0664 root ${config.users.groups.utmp.name}
+          minsize 1M
+        '';
+      };
+    };
+  };
+
+  # FIXME: Remove these eventually.
+  imports =
+    [ (mkRenamedOptionModule [ "boot" "systemd" "sockets" ] [ "systemd" "sockets" ])
+      (mkRenamedOptionModule [ "boot" "systemd" "targets" ] [ "systemd" "targets" ])
+      (mkRenamedOptionModule [ "boot" "systemd" "services" ] [ "systemd" "services" ])
+      (mkRenamedOptionModule [ "jobs" ] [ "systemd" "services" ])
+      (mkRemovedOptionModule [ "systemd" "generator-packages" ] "Use systemd.packages instead.")
+    ];
+}
diff --git a/nixos/modules/system/boot/timesyncd.nix b/nixos/modules/system/boot/timesyncd.nix
new file mode 100644
index 00000000000..5f35a154769
--- /dev/null
+++ b/nixos/modules/system/boot/timesyncd.nix
@@ -0,0 +1,74 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+
+  options = {
+
+    services.timesyncd = {
+      enable = mkOption {
+        default = !config.boot.isContainer;
+        defaultText = literalExpression "!config.boot.isContainer";
+        type = types.bool;
+        description = ''
+          Enables the systemd NTP client daemon.
+        '';
+      };
+      servers = mkOption {
+        default = config.networking.timeServers;
+        defaultText = literalExpression "config.networking.timeServers";
+        type = types.listOf types.str;
+        description = ''
+          The set of NTP servers from which to synchronise.
+        '';
+      };
+      extraConfig = mkOption {
+        default = "";
+        type = types.lines;
+        example = ''
+          PollIntervalMaxSec=180
+        '';
+        description = ''
+          Extra config options for systemd-timesyncd. See
+          <link xlink:href="https://www.freedesktop.org/software/systemd/man/timesyncd.conf.html">
+          timesyncd.conf(5)</link> for available options.
+        '';
+      };
+    };
+  };
+
+  config = mkIf config.services.timesyncd.enable {
+
+    systemd.additionalUpstreamSystemUnits = [ "systemd-timesyncd.service" ];
+
+    systemd.services.systemd-timesyncd = {
+      wantedBy = [ "sysinit.target" ];
+      aliases = [ "dbus-org.freedesktop.timesync1.service" ];
+      restartTriggers = [ config.environment.etc."systemd/timesyncd.conf".source ];
+    };
+
+    environment.etc."systemd/timesyncd.conf".text = ''
+      [Time]
+      NTP=${concatStringsSep " " config.services.timesyncd.servers}
+      ${config.services.timesyncd.extraConfig}
+    '';
+
+    users.users.systemd-timesync = {
+      uid = config.ids.uids.systemd-timesync;
+      group = "systemd-timesync";
+    };
+    users.groups.systemd-timesync.gid = config.ids.gids.systemd-timesync;
+
+    system.activationScripts.systemd-timesyncd-migration = mkIf (versionOlder config.system.stateVersion "19.09") ''
+      # workaround an issue of systemd-timesyncd not starting due to upstream systemd reverting their dynamic users changes
+      #  - https://github.com/NixOS/nixpkgs/pull/61321#issuecomment-492423742
+      #  - https://github.com/systemd/systemd/issues/12131
+      if [ -L /var/lib/systemd/timesync ]; then
+        rm /var/lib/systemd/timesync
+        mv /var/lib/private/systemd/timesync /var/lib/systemd/timesync
+      fi
+    '';
+  };
+
+}
diff --git a/nixos/modules/system/boot/tmp.nix b/nixos/modules/system/boot/tmp.nix
new file mode 100644
index 00000000000..cf6d19eb5f0
--- /dev/null
+++ b/nixos/modules/system/boot/tmp.nix
@@ -0,0 +1,64 @@
+{ config, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.boot;
+in
+{
+
+  ###### interface
+
+  options = {
+
+    boot.cleanTmpDir = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to delete all files in <filename>/tmp</filename> during boot.
+      '';
+    };
+
+    boot.tmpOnTmpfs = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+         Whether to mount a tmpfs on <filename>/tmp</filename> during boot.
+      '';
+    };
+
+    boot.tmpOnTmpfsSize = mkOption {
+      type = types.oneOf [ types.str types.types.ints.positive ];
+      default = "50%";
+      description = ''
+        Size of tmpfs in percentage.
+        Percentage is defined by systemd.
+      '';
+    };
+
+  };
+
+  ###### implementation
+
+  config = {
+
+    # When changing remember to update /tmp mount in virtualisation/qemu-vm.nix
+    systemd.mounts = mkIf cfg.tmpOnTmpfs [
+      {
+        what = "tmpfs";
+        where = "/tmp";
+        type = "tmpfs";
+        mountConfig.Options = concatStringsSep "," [ "mode=1777"
+                                                     "strictatime"
+                                                     "rw"
+                                                     "nosuid"
+                                                     "nodev"
+                                                     "size=${toString cfg.tmpOnTmpfsSize}" ];
+      }
+    ];
+
+    systemd.tmpfiles.rules = optional config.boot.cleanTmpDir "D! /tmp 1777 root root";
+
+  };
+
+}
diff --git a/nixos/modules/system/build.nix b/nixos/modules/system/build.nix
new file mode 100644
index 00000000000..58dc3f0d411
--- /dev/null
+++ b/nixos/modules/system/build.nix
@@ -0,0 +1,21 @@
+{ lib, ... }:
+let
+  inherit (lib) mkOption types;
+in
+{
+  options = {
+
+    system.build = mkOption {
+      default = {};
+      description = ''
+        Attribute set of derivations used to set up the system.
+      '';
+      type = types.submoduleWith {
+        modules = [{
+          freeformType = with types; lazyAttrsOf (uniq unspecified);
+        }];
+      };
+    };
+
+  };
+}
diff --git a/nixos/modules/system/etc/etc-activation.nix b/nixos/modules/system/etc/etc-activation.nix
new file mode 100644
index 00000000000..78010495018
--- /dev/null
+++ b/nixos/modules/system/etc/etc-activation.nix
@@ -0,0 +1,12 @@
+{ config, lib, ... }:
+let
+  inherit (lib) stringAfter;
+in {
+
+  imports = [ ./etc.nix ];
+
+  config = {
+    system.activationScripts.etc =
+      stringAfter [ "users" "groups" ] config.system.build.etcActivationCommands;
+  };
+}
diff --git a/nixos/modules/system/etc/etc.nix b/nixos/modules/system/etc/etc.nix
new file mode 100644
index 00000000000..ed552fecec5
--- /dev/null
+++ b/nixos/modules/system/etc/etc.nix
@@ -0,0 +1,201 @@
+# Management of static files in /etc.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  etc' = filter (f: f.enable) (attrValues config.environment.etc);
+
+  etc = pkgs.runCommandLocal "etc" {
+    # This is needed for the systemd module
+    passthru.targets = map (x: x.target) etc';
+  } /* sh */ ''
+    set -euo pipefail
+
+    makeEtcEntry() {
+      src="$1"
+      target="$2"
+      mode="$3"
+      user="$4"
+      group="$5"
+
+      if [[ "$src" = *'*'* ]]; then
+        # If the source name contains '*', perform globbing.
+        mkdir -p "$out/etc/$target"
+        for fn in $src; do
+            ln -s "$fn" "$out/etc/$target/"
+        done
+      else
+
+        mkdir -p "$out/etc/$(dirname "$target")"
+        if ! [ -e "$out/etc/$target" ]; then
+          ln -s "$src" "$out/etc/$target"
+        else
+          echo "duplicate entry $target -> $src"
+          if [ "$(readlink "$out/etc/$target")" != "$src" ]; then
+            echo "mismatched duplicate entry $(readlink "$out/etc/$target") <-> $src"
+            ret=1
+
+            continue
+          fi
+        fi
+
+        if [ "$mode" != symlink ]; then
+          echo "$mode" > "$out/etc/$target.mode"
+          echo "$user" > "$out/etc/$target.uid"
+          echo "$group" > "$out/etc/$target.gid"
+        fi
+      fi
+    }
+
+    mkdir -p "$out/etc"
+    ${concatMapStringsSep "\n" (etcEntry: escapeShellArgs [
+      "makeEtcEntry"
+      # Force local source paths to be added to the store
+      "${etcEntry.source}"
+      etcEntry.target
+      etcEntry.mode
+      etcEntry.user
+      etcEntry.group
+    ]) etc'}
+  '';
+
+in
+
+{
+
+  imports = [ ../build.nix ];
+
+  ###### interface
+
+  options = {
+
+    environment.etc = mkOption {
+      default = {};
+      example = literalExpression ''
+        { example-configuration-file =
+            { source = "/nix/store/.../etc/dir/file.conf.example";
+              mode = "0440";
+            };
+          "default/useradd".text = "GROUP=100 ...";
+        }
+      '';
+      description = ''
+        Set of files that have to be linked in <filename>/etc</filename>.
+      '';
+
+      type = with types; attrsOf (submodule (
+        { name, config, options, ... }:
+        { options = {
+
+            enable = mkOption {
+              type = types.bool;
+              default = true;
+              description = ''
+                Whether this /etc file should be generated.  This
+                option allows specific /etc files to be disabled.
+              '';
+            };
+
+            target = mkOption {
+              type = types.str;
+              description = ''
+                Name of symlink (relative to
+                <filename>/etc</filename>).  Defaults to the attribute
+                name.
+              '';
+            };
+
+            text = mkOption {
+              default = null;
+              type = types.nullOr types.lines;
+              description = "Text of the file.";
+            };
+
+            source = mkOption {
+              type = types.path;
+              description = "Path of the source file.";
+            };
+
+            mode = mkOption {
+              type = types.str;
+              default = "symlink";
+              example = "0600";
+              description = ''
+                If set to something else than <literal>symlink</literal>,
+                the file is copied instead of symlinked, with the given
+                file mode.
+              '';
+            };
+
+            uid = mkOption {
+              default = 0;
+              type = types.int;
+              description = ''
+                UID of created file. Only takes effect when the file is
+                copied (that is, the mode is not 'symlink').
+                '';
+            };
+
+            gid = mkOption {
+              default = 0;
+              type = types.int;
+              description = ''
+                GID of created file. Only takes effect when the file is
+                copied (that is, the mode is not 'symlink').
+              '';
+            };
+
+            user = mkOption {
+              default = "+${toString config.uid}";
+              type = types.str;
+              description = ''
+                User name of created file.
+                Only takes effect when the file is copied (that is, the mode is not 'symlink').
+                Changing this option takes precedence over <literal>uid</literal>.
+              '';
+            };
+
+            group = mkOption {
+              default = "+${toString config.gid}";
+              type = types.str;
+              description = ''
+                Group name of created file.
+                Only takes effect when the file is copied (that is, the mode is not 'symlink').
+                Changing this option takes precedence over <literal>gid</literal>.
+              '';
+            };
+
+          };
+
+          config = {
+            target = mkDefault name;
+            source = mkIf (config.text != null) (
+              let name' = "etc-" + baseNameOf name;
+              in mkDerivedConfig options.text (pkgs.writeText name')
+            );
+          };
+
+        }));
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = {
+
+    system.build.etc = etc;
+    system.build.etcActivationCommands =
+      ''
+        # Set up the statically computed bits of /etc.
+        echo "setting up /etc..."
+        ${pkgs.perl.withPackages (p: [ p.FileSlurp ])}/bin/perl ${./setup-etc.pl} ${etc}/etc
+      '';
+  };
+
+}
diff --git a/nixos/modules/system/etc/setup-etc.pl b/nixos/modules/system/etc/setup-etc.pl
new file mode 100644
index 00000000000..be6b2d9ae71
--- /dev/null
+++ b/nixos/modules/system/etc/setup-etc.pl
@@ -0,0 +1,146 @@
+use strict;
+use File::Find;
+use File::Copy;
+use File::Path;
+use File::Basename;
+use File::Slurp;
+
+my $etc = $ARGV[0] or die;
+my $static = "/etc/static";
+
+sub atomicSymlink {
+    my ($source, $target) = @_;
+    my $tmp = "$target.tmp";
+    unlink $tmp;
+    symlink $source, $tmp or return 0;
+    rename $tmp, $target or return 0;
+    return 1;
+}
+
+
+# Atomically update /etc/static to point at the etc files of the
+# current configuration.
+atomicSymlink $etc, $static or die;
+
+# Returns 1 if the argument points to the files in /etc/static.  That
+# means either argument is a symlink to a file in /etc/static or a
+# directory with all children being static.
+sub isStatic {
+    my $path = shift;
+
+    if (-l $path) {
+        my $target = readlink $path;
+        return substr($target, 0, length "/etc/static/") eq "/etc/static/";
+    }
+
+    if (-d $path) {
+        opendir DIR, "$path" or return 0;
+        my @names = readdir DIR or die;
+        closedir DIR;
+
+        foreach my $name (@names) {
+            next if $name eq "." || $name eq "..";
+            unless (isStatic("$path/$name")) {
+                return 0;
+            }
+        }
+        return 1;
+    }
+
+    return 0;
+}
+
+# Remove dangling symlinks that point to /etc/static.  These are
+# configuration files that existed in a previous configuration but not
+# in the current one.  For efficiency, don't look under /etc/nixos
+# (where all the NixOS sources live).
+sub cleanup {
+    if ($File::Find::name eq "/etc/nixos") {
+        $File::Find::prune = 1;
+        return;
+    }
+    if (-l $_) {
+        my $target = readlink $_;
+        if (substr($target, 0, length $static) eq $static) {
+            my $x = "/etc/static/" . substr($File::Find::name, length "/etc/");
+            unless (-l $x) {
+                print STDERR "removing obsolete symlink ‘$File::Find::name’...\n";
+                unlink "$_";
+            }
+        }
+    }
+}
+
+find(\&cleanup, "/etc");
+
+
+# Use /etc/.clean to keep track of copied files.
+my @oldCopied = read_file("/etc/.clean", chomp => 1, err_mode => 'quiet');
+open CLEAN, ">>/etc/.clean";
+
+
+# For every file in the etc tree, create a corresponding symlink in
+# /etc to /etc/static.  The indirection through /etc/static is to make
+# switching to a new configuration somewhat more atomic.
+my %created;
+my @copied;
+
+sub link {
+    my $fn = substr $File::Find::name, length($etc) + 1 or next;
+    my $target = "/etc/$fn";
+    File::Path::make_path(dirname $target);
+    $created{$fn} = 1;
+
+    # Rename doesn't work if target is directory.
+    if (-l $_ && -d $target) {
+        if (isStatic $target) {
+            rmtree $target or warn;
+        } else {
+            warn "$target directory contains user files. Symlinking may fail.";
+        }
+    }
+
+    if (-e "$_.mode") {
+        my $mode = read_file("$_.mode"); chomp $mode;
+        if ($mode eq "direct-symlink") {
+            atomicSymlink readlink("$static/$fn"), $target or warn;
+        } else {
+            my $uid = read_file("$_.uid"); chomp $uid;
+            my $gid = read_file("$_.gid"); chomp $gid;
+            copy "$static/$fn", "$target.tmp" or warn;
+            $uid = getpwnam $uid unless $uid =~ /^\+/;
+            $gid = getgrnam $gid unless $gid =~ /^\+/;
+            chown int($uid), int($gid), "$target.tmp" or warn;
+            chmod oct($mode), "$target.tmp" or warn;
+            rename "$target.tmp", $target or warn;
+        }
+        push @copied, $fn;
+        print CLEAN "$fn\n";
+    } elsif (-l "$_") {
+        atomicSymlink "$static/$fn", $target or warn;
+    }
+}
+
+find(\&link, $etc);
+
+
+# Delete files that were copied in a previous version but not in the
+# current.
+foreach my $fn (@oldCopied) {
+    if (!defined $created{$fn}) {
+        $fn = "/etc/$fn";
+        print STDERR "removing obsolete file ‘$fn’...\n";
+        unlink "$fn";
+    }
+}
+
+
+# Rewrite /etc/.clean.
+close CLEAN;
+write_file("/etc/.clean", map { "$_\n" } @copied);
+
+# Create /etc/NIXOS tag if not exists.
+# When /etc is not on a persistent filesystem, it will be wiped after reboot,
+# so we need to check and re-create it during activation.
+open TAG, ">>/etc/NIXOS";
+close TAG;
diff --git a/nixos/modules/system/etc/test.nix b/nixos/modules/system/etc/test.nix
new file mode 100644
index 00000000000..5e43b155038
--- /dev/null
+++ b/nixos/modules/system/etc/test.nix
@@ -0,0 +1,70 @@
+{ lib
+, coreutils
+, fakechroot
+, fakeroot
+, evalMinimalConfig
+, pkgsModule
+, runCommand
+, util-linux
+, vmTools
+, writeText
+}:
+let
+  node = evalMinimalConfig ({ config, ... }: {
+    imports = [ pkgsModule ../etc/etc.nix ];
+    environment.etc."passwd" = {
+      text = passwdText;
+    };
+    environment.etc."hosts" = {
+      text = hostsText;
+      mode = "0751";
+    };
+  });
+  passwdText = ''
+    root:x:0:0:System administrator:/root:/run/current-system/sw/bin/bash
+  '';
+  hostsText = ''
+    127.0.0.1 localhost
+    ::1 localhost
+    # testing...
+  '';
+in
+lib.recurseIntoAttrs {
+  test-etc-vm =
+    vmTools.runInLinuxVM (runCommand "test-etc-vm" { } ''
+      mkdir -p /etc
+      ${node.config.system.build.etcActivationCommands}
+      set -x
+      [[ -L /etc/passwd ]]
+      diff /etc/passwd ${writeText "expected-passwd" passwdText}
+      [[ 751 = $(stat --format %a /etc/hosts) ]]
+      diff /etc/hosts ${writeText "expected-hosts" hostsText}
+      set +x
+      touch $out
+    '');
+
+  # fakeroot is behaving weird
+  test-etc-fakeroot =
+    runCommand "test-etc"
+      {
+        nativeBuildInputs = [
+          fakeroot
+          fakechroot
+          # for chroot
+          coreutils
+          # fakechroot needs getopt, which is provided by util-linux
+          util-linux
+        ];
+        fakeRootCommands = ''
+          mkdir -p /etc
+          ${node.config.system.build.etcActivationCommands}
+          diff /etc/hosts ${writeText "expected-hosts" hostsText}
+          touch $out
+        '';
+      } ''
+      mkdir fake-root
+      export FAKECHROOT_EXCLUDE_PATH=/dev:/proc:/sys:${builtins.storeDir}:$out
+      fakechroot fakeroot chroot $PWD/fake-root bash -c 'source $stdenv/setup; eval "$fakeRootCommands"'
+    '';
+
+}