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.nix4
-rw-r--r--nixos/modules/system/activation/no-clone.nix4
-rw-r--r--nixos/modules/system/activation/switch-to-configuration.pl34
-rw-r--r--nixos/modules/system/activation/top-level.nix7
-rw-r--r--nixos/modules/system/boot/kernel.nix36
-rw-r--r--nixos/modules/system/boot/loader/efi.nix4
-rw-r--r--nixos/modules/system/boot/loader/generations-dir/generations-dir.nix4
-rw-r--r--nixos/modules/system/boot/loader/grub/grub.nix11
-rw-r--r--nixos/modules/system/boot/loader/grub/memtest.nix76
-rw-r--r--nixos/modules/system/boot/loader/gummiboot/gummiboot-builder.py14
-rw-r--r--nixos/modules/system/boot/loader/gummiboot/gummiboot.nix4
-rw-r--r--nixos/modules/system/boot/loader/init-script/init-script.nix4
-rw-r--r--nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix4
-rw-r--r--nixos/modules/system/boot/luksroot.nix305
-rw-r--r--nixos/modules/system/boot/modprobe.nix28
-rw-r--r--nixos/modules/system/boot/pbkdf2-sha512.c38
-rw-r--r--nixos/modules/system/boot/shutdown.nix4
-rw-r--r--nixos/modules/system/boot/stage-1-init.sh8
-rw-r--r--nixos/modules/system/boot/stage-1.nix6
-rw-r--r--nixos/modules/system/boot/stage-2-init.sh45
-rw-r--r--nixos/modules/system/boot/stage-2.nix7
-rw-r--r--nixos/modules/system/boot/systemd-unit-options.nix90
-rw-r--r--nixos/modules/system/boot/systemd.nix404
-rw-r--r--nixos/modules/system/etc/etc.nix24
-rw-r--r--nixos/modules/system/etc/make-etc.sh4
-rw-r--r--nixos/modules/system/etc/setup-etc.pl8
-rw-r--r--nixos/modules/system/upstart/upstart.nix8
27 files changed, 876 insertions, 309 deletions
diff --git a/nixos/modules/system/activation/activation-script.nix b/nixos/modules/system/activation/activation-script.nix
index 1545bcb8a1f9..41fe7d309a5a 100644
--- a/nixos/modules/system/activation/activation-script.nix
+++ b/nixos/modules/system/activation/activation-script.nix
@@ -1,7 +1,7 @@
 # generate the script used to activate the configuration.
-{ config, pkgs, ... }:
+{ config, lib, pkgs, ... }:
 
-with pkgs.lib;
+with lib;
 
 let
 
diff --git a/nixos/modules/system/activation/no-clone.nix b/nixos/modules/system/activation/no-clone.nix
index c9ab691ce477..7f4584435266 100644
--- a/nixos/modules/system/activation/no-clone.nix
+++ b/nixos/modules/system/activation/no-clone.nix
@@ -1,6 +1,6 @@
-{pkgs, ...}:
+{ lib, ... }:
 
-with pkgs.lib;
+with lib;
 
 {
   boot.loader.grub.device = mkOverride 0 "nodev";
diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl
index 91beed1130eb..25b5afe99da4 100644
--- a/nixos/modules/system/activation/switch-to-configuration.pl
+++ b/nixos/modules/system/activation/switch-to-configuration.pl
@@ -27,7 +27,10 @@ EOF
     exit 1;
 }
 
-die "This is not a NixOS installation (/etc/NIXOS is missing)!\n" unless -f "/etc/NIXOS";
+# 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);
 
@@ -96,12 +99,18 @@ sub parseFstab {
 sub parseUnit {
     my ($filename) = @_;
     my $info = {};
-    foreach my $line (read_file($filename)) {
+    parseKeyValues($info, read_file($filename));
+    parseKeyValues($info, read_file("${filename}.d/overrides.conf")) if -f "${filename}.d/overrides.conf";
+    return $info;
+}
+
+sub parseKeyValues {
+    my $info = shift;
+    foreach my $line (@_) {
         # FIXME: not quite correct.
         $line =~ /^([^=]+)=(.*)$/ or next;
         $info->{$1} = $2;
     }
-    return $info;
 }
 
 sub boolIsTrue {
@@ -109,6 +118,14 @@ sub boolIsTrue {
     return $s eq "yes" || $s eq "true";
 }
 
+# As a fingerprint for determining whether a unit has changed, we use
+# its absolute path. If it has an override file, we append *its*
+# absolute path as well.
+sub fingerprintUnit {
+    my ($s) = @_;
+    return abs_path($s) . (-f "${s}.d/overrides.conf" ? " " . abs_path "${s}.d/overrides.conf" : "");
+}
+
 # Stop all services that no longer exist or have changed in the new
 # configuration.
 my (@unitsToStop, @unitsToSkip);
@@ -125,7 +142,7 @@ while (my ($unit, $state) = each %{$activePrev}) {
     $baseName =~ s/\.[a-z]*$//;
 
     if (-e $prevUnitFile && ($state->{state} eq "active" || $state->{state} eq "activating")) {
-        if (! -e $newUnitFile) {
+        if (! -e $newUnitFile || abs_path($newUnitFile) eq "/dev/null") {
             push @unitsToStop, $unit;
         }
 
@@ -160,7 +177,7 @@ while (my ($unit, $state) = each %{$activePrev}) {
             }
         }
 
-        elsif (abs_path($prevUnitFile) ne abs_path($newUnitFile)) {
+        elsif (fingerprintUnit($prevUnitFile) ne fingerprintUnit($newUnitFile)) {
             if ($unit eq "sysinit.target" || $unit eq "basic.target" || $unit eq "multi-user.target" || $unit eq "graphical.target") {
                 # Do nothing.  These cannot be restarted directly.
             } elsif ($unit =~ /\.mount$/) {
@@ -170,7 +187,10 @@ while (my ($unit, $state) = each %{$activePrev}) {
                 # FIXME: do something?
             } else {
                 my $unitInfo = parseUnit($newUnitFile);
-                if (!boolIsTrue($unitInfo->{'X-RestartIfChanged'} // "yes")) {
+                if (boolIsTrue($unitInfo->{'X-ReloadIfChanged'} // "no")) {
+                    write_file($reloadListFile, { append => 1 }, "$unit\n");
+                }
+                elsif (!boolIsTrue($unitInfo->{'X-RestartIfChanged'} // "yes")) {
                     push @unitsToSkip, $unit;
                 } else {
                     # If this unit is socket-activated, then stop the
@@ -319,7 +339,7 @@ if (scalar @restart > 0) {
 # 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 @start = unique("default.target", "timers.target", split('\n', read_file($startListFile, err_mode => 'quiet') // ""));
+my @start = unique("default.target", "timers.target", "sockets.target", split('\n', read_file($startListFile, err_mode => 'quiet') // ""));
 print STDERR "starting the following units: ", join(", ", sort(@start)), "\n";
 $systemdManager->StartUnit($_, "replace") for @start;
 unlink($startListFile);
diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix
index 30a529988a96..1600a1fb0104 100644
--- a/nixos/modules/system/activation/top-level.nix
+++ b/nixos/modules/system/activation/top-level.nix
@@ -1,6 +1,6 @@
-{ config, pkgs, modules, baseModules, ... }:
+{ config, lib, pkgs, modules, baseModules, ... }:
 
-with pkgs.lib;
+with lib;
 
 let
 
@@ -11,7 +11,7 @@ let
   # you can provide an easy way to boot the same configuration
   # as you use, but with another kernel
   # !!! fix this
-  cloner = inheritParent: list: with pkgs.lib;
+  cloner = inheritParent: list:
     map (childConfig:
       (import ../../../lib/eval-config.nix {
         inherit baseModules;
@@ -68,6 +68,7 @@ let
       echo -n "$configurationName" > $out/configuration-name
       echo -n "systemd ${toString config.systemd.package.interfaceVersion}" > $out/init-interface-version
       echo -n "$nixosVersion" > $out/nixos-version
+      echo -n "$system" > $out/system
 
       mkdir $out/fine-tune
       childCount=0
diff --git a/nixos/modules/system/boot/kernel.nix b/nixos/modules/system/boot/kernel.nix
index 2b075bf6a6d2..b81bcf20f439 100644
--- a/nixos/modules/system/boot/kernel.nix
+++ b/nixos/modules/system/boot/kernel.nix
@@ -1,6 +1,6 @@
-{ config, pkgs, ... }:
+{ config, lib, pkgs, ... }:
 
-with pkgs.lib;
+with lib;
 
 let
 
@@ -159,7 +159,7 @@ in
 
     boot.kernel.sysctl."kernel.printk" = config.boot.consoleLogLevel;
 
-    boot.kernelModules = [ "loop" ];
+    boot.kernelModules = [ "loop" "configs" ];
 
     boot.initrd.availableKernelModules =
       [ # Note: most of these (especially the SATA/PATA modules)
@@ -203,6 +203,9 @@ in
 
         # To wait for SCSI devices to appear.
         "scsi_wait_scan"
+
+        # Needed by the stage 2 init script.
+        "rtc_cmos"
       ];
 
     boot.initrd.kernelModules =
@@ -215,37 +218,26 @@ in
 
     # Create /etc/modules-load.d/nixos.conf, which is read by
     # systemd-modules-load.service to load required kernel modules.
-    # FIXME: ensure that systemd-modules-load.service is restarted if
-    # this file changes.
     environment.etc = singleton
       { target = "modules-load.d/nixos.conf";
         source = kernelModulesConf;
       };
 
-    # Sigh.  This overrides systemd's systemd-modules-load.service
-    # just so we can set a restart trigger.  Also make
-    # multi-user.target pull it in so that it gets started if it
-    # failed earlier.
     systemd.services."systemd-modules-load" =
-      { description = "Load Kernel Modules";
-        wantedBy = [ "sysinit.target" "multi-user.target" ];
-        before = [ "sysinit.target" "shutdown.target" ];
-        conflicts = [ "shutdown.target" ];
-        unitConfig =
-          { DefaultDependencies = false;
-            ConditionCapability = "CAP_SYS_MODULE";
-          };
+      { wantedBy = [ "multi-user.target" ];
+        restartTriggers = [ kernelModulesConf ];
+        environment.MODULE_DIR = "/run/booted-system/kernel-modules/lib/modules";
         serviceConfig =
-          { Type = "oneshot";
-            RemainAfterExit = true;
-            ExecStart = "${config.systemd.package}/lib/systemd/systemd-modules-load";
-            # Ignore failed module loads.  Typically some of the
+          { # 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";
           };
-        restartTriggers = [ kernelModulesConf ];
+      };
+
+    systemd.services.kmod-static-nodes =
+      { environment.MODULE_DIR = "/run/booted-system/kernel-modules/lib/modules";
       };
 
     lib.kernelConfig = {
diff --git a/nixos/modules/system/boot/loader/efi.nix b/nixos/modules/system/boot/loader/efi.nix
index 7e739173f9a3..241cfc7e836d 100644
--- a/nixos/modules/system/boot/loader/efi.nix
+++ b/nixos/modules/system/boot/loader/efi.nix
@@ -1,6 +1,6 @@
-{ pkgs, ... }:
+{ lib, ... }:
 
-with pkgs.lib;
+with lib;
 
 {
   options.boot.loader.efi = {
diff --git a/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix b/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix
index 9855c8c19dd0..4b5e84f53c1a 100644
--- a/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix
+++ b/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix
@@ -1,6 +1,6 @@
-{ config, pkgs, ... }:
+{ config, lib, pkgs, ... }:
 
-with pkgs.lib;
+with lib;
 
 let
 
diff --git a/nixos/modules/system/boot/loader/grub/grub.nix b/nixos/modules/system/boot/loader/grub/grub.nix
index ef6ff71ed778..a3b09223cbb8 100644
--- a/nixos/modules/system/boot/loader/grub/grub.nix
+++ b/nixos/modules/system/boot/loader/grub/grub.nix
@@ -1,6 +1,6 @@
-{ config, pkgs, ... }:
+{ config, lib, pkgs, ... }:
 
-with pkgs.lib;
+with lib;
 
 let
 
@@ -133,11 +133,8 @@ in
             chainloader (hd0,1)+1
 
           # GRUB 2 example
-          menuentry "Windows7" {
-            title Windows7
-            insmod ntfs
-            set root='(hd1,1)'
-            chainloader +1
+          menuentry "Windows 7" {
+            chainloader (hd0,4)+1
           }
         '';
         description = ''
diff --git a/nixos/modules/system/boot/loader/grub/memtest.nix b/nixos/modules/system/boot/loader/grub/memtest.nix
index 80c1a160cfde..94e5a14174b0 100644
--- a/nixos/modules/system/boot/loader/grub/memtest.nix
+++ b/nixos/modules/system/boot/loader/grub/memtest.nix
@@ -1,33 +1,87 @@
 # This module adds Memtest86+ to the GRUB boot menu.
 
-{ config, pkgs, ... }:
+{ config, lib, pkgs, ... }:
 
-with pkgs.lib;
+with lib;
 
 let
   memtest86 = pkgs.memtest86plus;
+  cfg = config.boot.loader.grub.memtest86;
 in
 
 {
   options = {
 
-    boot.loader.grub.memtest86 = mkOption {
-      default = false;
-      type = types.bool;
-      description = ''
-        Make Memtest86+, a memory testing program, available from the
-        GRUB boot menu.
-      '';
+    boot.loader.grub.memtest86 = {
+
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Make Memtest86+, a memory testing program, available from the
+          GRUB boot menu.
+        '';
+      };
+
+      params = mkOption {
+        default = [];
+        example = [ "console=ttyS0,115200" ];
+        type = types.listOf types.str;
+        description = ''
+          Parameters added to the Memtest86+ command line. As of memtest86+ 5.01
+          the following list of (apparently undocumented) parameters are
+          accepted:
+
+          <itemizedlist>
+
+          <listitem>
+            <para><literal>console=...</literal>, set up a serial console.
+            Examples:
+            <literal>console=ttyS0</literal>,
+            <literal>console=ttyS0,9600</literal> or
+            <literal>console=ttyS0,115200n8</literal>.</para>
+          </listitem>
+
+          <listitem>
+            <para><literal>btrace</literal>, enable boot trace.</para>
+          </listitem>
+
+          <listitem>
+            <para><literal>maxcpus=N</literal>, limit number of CPUs.</para>
+          </listitem>
+
+          <listitem>
+            <para><literal>onepass</literal>, run one pass and exit if there
+            are no errors.</para>
+          </listitem>
+
+          <listitem>
+            <para><literal>tstlist=...</literal>, list of tests to run.
+            Example: <literal>0,1,2</literal>.</para>
+          </listitem>
+
+          <listitem>
+            <para><literal>cpumask=...</literal>, set a CPU mask, to select CPUs
+            to use for testing.</para>
+          </listitem>
+
+          </itemizedlist>
+
+          This list of command line options was obtained by reading the
+          Memtest86+ source code.
+        '';
+      };
+
     };
   };
 
-  config = mkIf config.boot.loader.grub.memtest86 {
+  config = mkIf cfg.enable {
 
     boot.loader.grub.extraEntries =
       if config.boot.loader.grub.version == 2 then
         ''
           menuentry "Memtest86+" {
-            linux16 @bootRoot@/memtest.bin
+            linux16 @bootRoot@/memtest.bin ${toString cfg.params}
           }
         ''
       else
diff --git a/nixos/modules/system/boot/loader/gummiboot/gummiboot-builder.py b/nixos/modules/system/boot/loader/gummiboot/gummiboot-builder.py
index 9ea224b51f63..db73544181b6 100644
--- a/nixos/modules/system/boot/loader/gummiboot/gummiboot-builder.py
+++ b/nixos/modules/system/boot/loader/gummiboot/gummiboot-builder.py
@@ -9,7 +9,6 @@ import tempfile
 import errno
 
 def copy_if_not_exists(source, dest):
-    known_paths.append(dest)
     if not os.path.exists(dest):
         shutil.copyfile(source, dest)
 
@@ -38,12 +37,13 @@ def write_loader_conf(generation):
         print >> f, "default nixos-generation-%d" % (generation)
     os.rename("@efiSysMountPoint@/loader/loader.conf.tmp", "@efiSysMountPoint@/loader/loader.conf")
 
-def copy_from_profile(generation, name):
+def copy_from_profile(generation, name, dry_run=False):
     store_file_path = os.readlink("%s/%s" % (system_dir(generation), name))
     suffix = os.path.basename(store_file_path)
     store_dir = os.path.basename(os.path.dirname(store_file_path))
     efi_file_path = "/efi/nixos/%s-%s.efi" % (store_dir, suffix)
-    copy_if_not_exists(store_file_path, "@efiSysMountPoint@%s" % (efi_file_path))
+    if not dry_run:
+        copy_if_not_exists(store_file_path, "@efiSysMountPoint@%s" % (efi_file_path))
     return efi_file_path
 
 def add_entry(generation):
@@ -72,6 +72,10 @@ def get_generations(profile):
 def remove_old_entries(gens):
     slice_start = len("@efiSysMountPoint@/loader/entries/nixos-generation-")
     slice_end = -1 * len(".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:
             gen = int(path[slice_start:slice_end])
@@ -94,7 +98,6 @@ if os.getenv("NIXOS_INSTALL_GRUB") == "1":
     else:
         subprocess.check_call(["@gummiboot@/bin/gummiboot", "--path=@efiSysMountPoint@", "--no-variables", "install"])
 
-known_paths = []
 mkdir_p("@efiSysMountPoint@/efi/nixos")
 mkdir_p("@efiSysMountPoint@/loader/entries")
 try:
@@ -106,9 +109,8 @@ except IOError as e:
     machine_id = None
 
 gens = get_generations("system")
+remove_old_entries(gens)
 for gen in gens:
     add_entry(gen)
     if os.readlink(system_dir(gen)) == args.default_config:
         write_loader_conf(gen)
-
-remove_old_entries(gens)
diff --git a/nixos/modules/system/boot/loader/gummiboot/gummiboot.nix b/nixos/modules/system/boot/loader/gummiboot/gummiboot.nix
index 7edc30776379..19c613a7c94c 100644
--- a/nixos/modules/system/boot/loader/gummiboot/gummiboot.nix
+++ b/nixos/modules/system/boot/loader/gummiboot/gummiboot.nix
@@ -1,6 +1,6 @@
-{ config, pkgs, ... }:
+{ config, lib, pkgs, ... }:
 
-with pkgs.lib;
+with lib;
 
 let
   cfg = config.boot.loader.gummiboot;
diff --git a/nixos/modules/system/boot/loader/init-script/init-script.nix b/nixos/modules/system/boot/loader/init-script/init-script.nix
index 4b0fcd85b4b5..3b33d42b4ae4 100644
--- a/nixos/modules/system/boot/loader/init-script/init-script.nix
+++ b/nixos/modules/system/boot/loader/init-script/init-script.nix
@@ -1,6 +1,6 @@
-{ config, pkgs, ... }:
+{ config, lib, pkgs, ... }:
 
-with pkgs.lib;
+with lib;
 
 let
 
diff --git a/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix b/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix
index 5bc856c3df0b..d3f32418a64c 100644
--- a/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix
+++ b/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix
@@ -1,6 +1,6 @@
-{ config, pkgs, ... }:
+{ config, lib, pkgs, ... }:
 
-with pkgs.lib;
+with lib;
 
 let
 
diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix
index ba357f5d2de3..c923cc49c449 100644
--- a/nixos/modules/system/boot/luksroot.nix
+++ b/nixos/modules/system/boot/luksroot.nix
@@ -1,11 +1,11 @@
-{ config, pkgs, ... }:
+{ config, lib, pkgs, ... }:
 
-with pkgs.lib;
+with lib;
 
 let
   luks = config.boot.initrd.luks;
 
-  openCommand = { name, device, keyFile, keyFileSize, allowDiscards, ... }: ''
+  openCommand = { name, device, keyFile, keyFileSize, allowDiscards, yubikey, ... }: ''
     # Wait for luksRoot to appear, e.g. if on a usb drive.
     # XXX: copied and adapted from stage-1-init.sh - should be
     # available as a function.
@@ -31,9 +31,161 @@ let
     fi
     ''}
 
+    open_normally() {
+        cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} \
+          ${optionalString (keyFile != null) "--key-file=${keyFile} ${optionalString (keyFileSize != null) "--keyfile-size=${toString keyFileSize}"}"}
+    }
+
+    ${optionalString (luks.yubikeySupport && (yubikey != null)) ''
+
+    rbtohex() {
+        ( od -An -vtx1 | tr -d ' \n' )
+    }
+
+    hextorb() {
+        ( tr '[:lower:]' '[:upper:]' | sed -e 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/gI' | xargs printf )
+    }
+
+    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
+
+        mkdir -p ${yubikey.storage.mountPoint}
+        mount -t ${yubikey.storage.fsType} ${toString yubikey.storage.device} ${yubikey.storage.mountPoint}
+
+        salt="$(cat ${yubikey.storage.mountPoint}${yubikey.storage.path} | sed -n 1p | tr -d '\n')"
+        iterations="$(cat ${yubikey.storage.mountPoint}${yubikey.storage.path} | sed -n 2p | tr -d '\n')"
+        challenge="$(echo -n $salt | openssl-wrap dgst -binary -sha512 | rbtohex)"
+        response="$(ykchalresp -${toString yubikey.slot} -x $challenge 2>/dev/null)"
+
+        for try in $(seq 3); do
+
+            ${optionalString yubikey.twoFactor ''
+            echo -n "Enter two-factor passphrase: "
+            read -s k_user
+            echo
+            ''}
+
+            if [ ! -z "$k_user" ]; then
+                k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString yubikey.keyLength} $iterations $response | rbtohex)"
+            else
+                k_luks="$(echo | pbkdf2-sha512 ${toString yubikey.keyLength} $iterations $response | rbtohex)"
+            fi
+
+            echo -n "$k_luks" | hextorb | cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} --key-file=-
+
+            if [ $? == "0" ]; then
+                opened=true
+                break
+            else
+                opened=false
+                echo "Authentication failed!"
+            fi
+        done
+
+        if [ "$opened" == false ]; then
+            umount ${yubikey.storage.mountPoint}
+            echo "Maximum authentication errors reached"
+            exit 1
+        fi
+
+        echo -n "Gathering entropy for new salt (please enter random keys to generate entropy if this blocks for long)..."
+        for i in $(seq ${toString 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 (yubikey.iterationStep > 0) ''
+        new_iterations="$(($new_iterations + ${toString yubikey.iterationStep}))"
+        ''}
+
+        new_challenge="$(echo -n $new_salt | openssl-wrap dgst -binary -sha512 | rbtohex)"
+
+        new_response="$(ykchalresp -${toString yubikey.slot} -x $new_challenge 2>/dev/null)"
+
+        if [ ! -z "$k_user" ]; then
+            new_k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString yubikey.keyLength} $new_iterations $new_response | rbtohex)"
+        else
+            new_k_luks="$(echo | pbkdf2-sha512 ${toString yubikey.keyLength} $new_iterations $new_response | rbtohex)"
+        fi
+
+        mkdir -p ${yubikey.ramfsMountPoint}
+        # 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!
+        mount -t ramfs none ${yubikey.ramfsMountPoint}
+
+        echo -n "$new_k_luks" | hextorb > ${yubikey.ramfsMountPoint}/new_key
+        echo -n "$k_luks" | hextorb | cryptsetup luksChangeKey ${device} --key-file=- ${yubikey.ramfsMountPoint}/new_key
+
+        if [ $? == "0" ]; then
+            echo -ne "$new_salt\n$new_iterations" > ${yubikey.storage.mountPoint}${yubikey.storage.path}
+        else
+            echo "Warning: Could not update LUKS key, current challenge persists!"
+        fi
+
+        rm -f ${yubikey.ramfsMountPoint}/new_key
+        umount ${yubikey.ramfsMountPoint}
+        rm -rf ${yubikey.ramfsMountPoint}
+
+        umount ${yubikey.storage.mountPoint}
+    }
+
+    ${optionalString (yubikey.gracePeriod > 0) ''
+    echo -n "Waiting ${toString yubikey.gracePeriod} seconds as grace..."
+    for i in $(seq ${toString yubikey.gracePeriod}); do
+        sleep 1
+        echo -n .
+    done
+    echo "ok"
+    ''}
+
+    yubikey_missing=true
+    ykinfo -v 1>/dev/null 2>&1
+    if [ $? != "0" ]; then
+        echo -n "waiting 10 seconds for yubikey to appear..."
+        for try in $(seq 10); do
+            sleep 1
+            ykinfo -v 1>/dev/null 2>&1
+            if [ $? == "0" ]; then
+                yubikey_missing=false
+                break
+            fi
+            echo -n .
+        done
+        echo "ok"
+    else
+        yubikey_missing=false
+    fi
+
+    if [ "$yubikey_missing" == true ]; then
+        echo "no yubikey found, falling back to non-yubikey open procedure"
+        open_normally
+    else
+        open_yubikey
+    fi
+    ''}
+
     # open luksRoot and scan for logical volumes
-    cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} \
-      ${optionalString (keyFile != null) "--key-file=${keyFile} ${optionalString (keyFileSize != null) "--keyfile-size=${toString keyFileSize}"}"}
+    ${optionalString ((!luks.yubikeySupport) || (yubikey == null)) ''
+    open_normally
+    ''}
   '';
 
   isPreLVM = f: f.preLVM;
@@ -139,10 +291,108 @@ in
           '';
         };
 
-      };
+        yubikey = mkOption {
+          default = null;
+          type = types.nullOr types.optionSet;
+          description = ''
+            The options to use for this LUKS device in Yubikey-PBA.
+            If null (the default), Yubikey-PBA will be disabled for this device.
+          '';
 
+          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 = 2;
+              type = types.int;
+              description = "Time in seconds to wait before attempting to find the Yubikey";
+            };
+
+            ramfsMountPoint = mkOption {
+              default = "/crypt-ramfs";
+              type = types.string;
+              description = "Path where the ramfs used to update the LUKS key will be mounted in stage-1";
+            };
+
+            storage = mkOption {
+              type = types.optionSet;
+              description = "Options related to the storing the salt";
+
+              options = {
+                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.string;
+                  description = "The filesystem of the unencrypted device";
+                };
+
+                mountPoint = mkOption {
+                  default = "/crypt-storage";
+                  type = types.string;
+                  description = "Path where the unencrypted device will be mounted in stage-1";
+                };
+
+                path = mkOption {
+                  default = "/crypt-storage/default";
+                  type = types.string;
+                  description = ''
+                    Absolute path of the salt on the unencrypted device with
+                    that device's root directory as "/".
+                  '';
+                };
+              };
+            };
+          };
+        };
+
+      };
     };
 
+    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.
+          '';
+    };
   };
 
   config = mkIf (luks.devices != []) {
@@ -157,15 +407,48 @@ in
     # copy the cryptsetup binary and it's dependencies
     boot.initrd.extraUtilsCommands = ''
       cp -pdv ${pkgs.cryptsetup}/sbin/cryptsetup $out/bin
-      # XXX: do we have a function that does this?
-      for lib in $(ldd $out/bin/cryptsetup |grep '=>' |grep /nix/store/ |cut -d' ' -f3); do
-        cp -pdvn $lib $out/lib
-        cp -pvn $(readlink -f $lib) $out/lib
-      done
+
+      cp -pdv ${pkgs.libgcrypt}/lib/libgcrypt*.so.* $out/lib
+      cp -pdv ${pkgs.libgpgerror}/lib/libgpg-error*.so.* $out/lib
+      cp -pdv ${pkgs.cryptsetup}/lib/libcryptsetup*.so.* $out/lib
+      cp -pdv ${pkgs.popt}/lib/libpopt*.so.* $out/lib
+
+      ${optionalString luks.yubikeySupport ''
+      cp -pdv ${pkgs.ykpers}/bin/ykchalresp $out/bin
+      cp -pdv ${pkgs.ykpers}/bin/ykinfo $out/bin
+      cp -pdv ${pkgs.openssl}/bin/openssl $out/bin
+
+      cc -O3 -I${pkgs.openssl}/include -L${pkgs.openssl}/lib ${./pbkdf2-sha512.c} -o $out/bin/pbkdf2-sha512 -lcrypto
+      strip -s $out/bin/pbkdf2-sha512
+
+      cp -pdv ${pkgs.libusb1}/lib/libusb*.so.* $out/lib
+      cp -pdv ${pkgs.ykpers}/lib/libykpers*.so.* $out/lib
+      cp -pdv ${pkgs.libyubikey}/lib/libyubikey*.so.* $out/lib
+      cp -pdv ${pkgs.openssl}/lib/libssl*.so.* $out/lib
+      cp -pdv ${pkgs.openssl}/lib/libcrypto*.so.* $out/lib
+
+      mkdir -p $out/etc/ssl
+      cp -pdv ${pkgs.openssl}/etc/ssl/openssl.cnf $out/etc/ssl
+
+      cat > $out/bin/openssl-wrap <<EOF
+#!$out/bin/sh
+EOF
+      chmod +x $out/bin/openssl-wrap
+      ''}
     '';
 
     boot.initrd.extraUtilsCommandsTest = ''
       $out/bin/cryptsetup --version
+      ${optionalString luks.yubikeySupport ''
+        $out/bin/ykchalresp -V
+        $out/bin/ykinfo -V
+        cat > $out/bin/openssl-wrap <<EOF
+#!$out/bin/sh
+export OPENSSL_CONF=$out/etc/ssl/openssl.cnf
+$out/bin/openssl "\$@"
+EOF
+        $out/bin/openssl-wrap version
+      ''}
     '';
 
     boot.initrd.preLVMCommands = concatMapStrings openCommand preLVM;
diff --git a/nixos/modules/system/boot/modprobe.nix b/nixos/modules/system/boot/modprobe.nix
index 027a7ac99d51..7b214cd1e1f5 100644
--- a/nixos/modules/system/boot/modprobe.nix
+++ b/nixos/modules/system/boot/modprobe.nix
@@ -1,6 +1,6 @@
-{ config, pkgs, ... }:
+{ config, lib, pkgs, ... }:
 
-with pkgs.lib;
+with lib;
 
 {
 
@@ -68,7 +68,10 @@ with pkgs.lib;
 
   config = mkIf (!config.boot.isContainer) {
 
-    environment.etc = singleton
+    environment.etc = [
+      { source = "${pkgs.kmod-blacklist-ubuntu}/modprobe.conf";
+        target = "modprobe.d/ubuntu.conf";
+      }
       { source = pkgs.writeText "modprobe.conf"
           ''
             ${flip concatMapStrings config.boot.blacklistedKernelModules (name: ''
@@ -77,26 +80,11 @@ with pkgs.lib;
             ${config.boot.extraModprobeConfig}
           '';
         target = "modprobe.d/nixos.conf";
-      };
+      }
+    ];
 
     environment.systemPackages = [ config.system.sbin.modprobe pkgs.kmod ];
 
-    boot.blacklistedKernelModules =
-      [ # This module is for debugging and generates gigantic amounts
-        # of log output, so it should never be loaded automatically.
-        "evbug"
-
-        # This module causes ALSA to occassionally select the wrong
-        # default sound device, and is little more than an annoyance
-        # on modern machines.
-        "snd_pcsp"
-
-        # The cirrusfb module prevents X11 from starting.  FIXME:
-        # Ubuntu blacklists all framebuffer devices because they're
-        # "buggy" and cause suspend problems.  Maybe we should too?
-        "cirrusfb"
-      ];
-
     system.activationScripts.modprobe =
       ''
         # Allow the kernel to find our wrapped modprobe (which searches
diff --git a/nixos/modules/system/boot/pbkdf2-sha512.c b/nixos/modules/system/boot/pbkdf2-sha512.c
new file mode 100644
index 000000000000..b40c383ac023
--- /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;
+}
\ No newline at end of file
diff --git a/nixos/modules/system/boot/shutdown.nix b/nixos/modules/system/boot/shutdown.nix
index 44cadcd64a76..68bc936c5b0b 100644
--- a/nixos/modules/system/boot/shutdown.nix
+++ b/nixos/modules/system/boot/shutdown.nix
@@ -1,6 +1,6 @@
-{ config, pkgs, ... }:
+{ config, lib, pkgs, ... }:
 
-with pkgs.lib;
+with lib;
 
 {
 
diff --git a/nixos/modules/system/boot/stage-1-init.sh b/nixos/modules/system/boot/stage-1-init.sh
index 1f65026b5def..216937a619b1 100644
--- a/nixos/modules/system/boot/stage-1-init.sh
+++ b/nixos/modules/system/boot/stage-1-init.sh
@@ -14,7 +14,7 @@ fail() {
     # in an interactive shell.
     cat <<EOF
 
-An error occured in stage 1 of the boot process, which must mount the
+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:
 
@@ -139,8 +139,6 @@ mkdir -p /dev/.mdadm
 systemd-udevd --daemon
 udevadm trigger --action=add
 udevadm settle || true
-modprobe scsi_wait_scan || true
-udevadm settle || true
 
 
 # Load boot-time keymap before any LVM/LUKS initialization
@@ -320,6 +318,10 @@ while read -u 3 mountPoint; do
         echo -n "waiting for device $device to appear..."
         for try in $(seq 1 20); 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 $device; then break; fi
             echo -n "."
         done
diff --git a/nixos/modules/system/boot/stage-1.nix b/nixos/modules/system/boot/stage-1.nix
index 8ed3aecb6911..c38d33c45d6e 100644
--- a/nixos/modules/system/boot/stage-1.nix
+++ b/nixos/modules/system/boot/stage-1.nix
@@ -3,9 +3,9 @@
 # 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, pkgs, ... }:
+{ config, lib, pkgs, ... }:
 
-with pkgs.lib;
+with lib;
 
 let
 
@@ -74,7 +74,7 @@ let
       cp -v ${pkgs.lvm2}/sbin/dmsetup $out/bin/dmsetup
       cp -v ${pkgs.lvm2}/sbin/lvm $out/bin/lvm
       cp -v ${pkgs.lvm2}/lib/libdevmapper.so.*.* $out/lib
-      cp -v ${pkgs.systemd}/lib/libsystemd-daemon.so.* $out/lib
+      cp -v ${pkgs.systemd}/lib/libsystemd.so.* $out/lib
 
       # Add RAID mdadm tool.
       cp -v ${pkgs.mdadm}/sbin/mdadm $out/bin/mdadm
diff --git a/nixos/modules/system/boot/stage-2-init.sh b/nixos/modules/system/boot/stage-2-init.sh
index 2fadd3de1f0f..a64c6cdfa191 100644
--- a/nixos/modules/system/boot/stage-2-init.sh
+++ b/nixos/modules/system/boot/stage-2-init.sh
@@ -82,7 +82,7 @@ done
 
 # More special file systems, initialise required directories.
 mkdir -m 0755 /dev/shm
-mount -t tmpfs -o "rw,nosuid,nodev,size=@devShmSize@" tmpfs /dev/shm
+mount -t tmpfs -o "rw,nosuid,nodev,size=@devShmSize@" none /dev/shm
 mkdir -m 0755 -p /dev/pts
 [ -e /proc/bus/usb ] && mount -t usbfs none /proc/bus/usb # UML doesn't have USB by default
 mkdir -m 01777 -p /tmp
@@ -96,28 +96,14 @@ mkdir -m 0755 -p /etc/nixos
 
 # Miscellaneous boot time cleanup.
 rm -rf /var/run /var/lock
-rm -f /etc/resolv.conf
-touch /etc/resolv.conf
 rm -f /etc/{group,passwd,shadow}.lock
 
 if test -n "@cleanTmpDir@"; then
     echo -n "cleaning \`/tmp'..."
     find /tmp -maxdepth 1 -mindepth 1 -print0 | xargs -0r rm -rf --one-file-system
     echo " done"
-else
-    # Get rid of ICE locks...
-    rm -rf /tmp/.ICE-unix
 fi
 
-# ... and ensure that it's owned by root.
-mkdir -m 1777 /tmp/.ICE-unix
-
-# This is a good time to clean up /nix/var/nix/chroots.  Doing an `rm
-# -rf' on it isn't safe in general because it can contain bind mounts
-# to /nix/store and other places.  But after rebooting these are all
-# gone, of course.
-rm -rf /nix/var/nix/chroots # recreated in activate-configuration.sh
-
 
 # Also get rid of temporary GC roots.
 rm -rf /nix/var/nix/gcroots/tmp /nix/var/nix/temproots
@@ -131,6 +117,15 @@ if ! mountpoint -q /run; then
     mount -t tmpfs -o "mode=0755,size=@runSize@" none /run
 fi
 
+# Create a ramfs on /run/keys to hold secrets that shouldn't be
+# written to disk (generally used for NixOps, harmless elsewhere).
+if ! mountpoint -q /run/keys; then
+    rm -rf /run/keys
+    mkdir -m 0750 /run/keys
+    chown 0:96 /run/keys
+    mount -t ramfs none /run/keys
+fi
+
 mkdir -m 0755 -p /run/lock
 
 
@@ -146,12 +141,32 @@ if test -n "$resumeDevice"; then
 fi
 
 
+# Use /etc/resolv.conf supplied by systemd-nspawn, if applicable.
+if [ -n "@useHostResolvConf@" -a -e /etc/resolv.conf ]; then
+    cat /etc/resolv.conf | resolvconf -m 1000 -a host
+else
+    touch /etc/resolv.conf
+fi
+
+
+# Create /var/setuid-wrappers as a tmpfs.
+rm -rf /var/setuid-wrappers
+mkdir -m 0755 -p /var/setuid-wrappers
+mount -t tmpfs -o "mode=0755" none /var/setuid-wrappers
+
+
 # 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.
+hwclock --hctosys
+
+
 # Record the boot configuration.
 ln -sfn "$systemConfig" /run/booted-system
 
diff --git a/nixos/modules/system/boot/stage-2.nix b/nixos/modules/system/boot/stage-2.nix
index aa0d7e0c138b..f53c3b8b8e70 100644
--- a/nixos/modules/system/boot/stage-2.nix
+++ b/nixos/modules/system/boot/stage-2.nix
@@ -1,6 +1,6 @@
-{ config, pkgs, ... }:
+{ config, lib, pkgs, ... }:
 
-with pkgs.lib;
+with lib;
 
 let
 
@@ -19,11 +19,13 @@ let
     isExecutable = true;
     inherit (config.boot) devShmSize runSize cleanTmpDir;
     inherit (config.nix) readOnlyStore;
+    inherit (config.networking) useHostResolvConf;
     ttyGid = config.ids.gids.tty;
     path =
       [ pkgs.coreutils
         pkgs.utillinux
         pkgs.sysvtools
+        pkgs.openresolv
       ] ++ (optional config.boot.cleanTmpDir pkgs.findutils)
       ++ optional config.nix.readOnlyStore readonlyMountpoint;
     postBootCommands = pkgs.writeText "local-cmds"
@@ -79,6 +81,7 @@ in
         '';
       };
 
+      # FIXME: should replace this with something that uses systemd-tmpfiles.
       cleanTmpDir = mkOption {
         type = types.bool;
         default = false;
diff --git a/nixos/modules/system/boot/systemd-unit-options.nix b/nixos/modules/system/boot/systemd-unit-options.nix
index c0518599f17a..a6183c47eb1b 100644
--- a/nixos/modules/system/boot/systemd-unit-options.nix
+++ b/nixos/modules/system/boot/systemd-unit-options.nix
@@ -1,6 +1,6 @@
-{ config, pkgs }:
+{ config, lib }:
 
-with pkgs.lib;
+with lib;
 
 let
 
@@ -28,7 +28,7 @@ let
 
 in rec {
 
-  unitOptions = {
+  sharedOptions = {
 
     enable = mkOption {
       default = true;
@@ -41,6 +41,37 @@ in rec {
       '';
     };
 
+    requiredBy = mkOption {
+      default = [];
+      type = types.listOf types.string;
+      description = "Units that require (i.e. depend on and need to go down with) this unit.";
+    };
+
+    wantedBy = mkOption {
+      default = [];
+      type = types.listOf types.string;
+      description = "Units that want (i.e. depend on) this unit.";
+    };
+
+  };
+
+  concreteUnitOptions = sharedOptions // {
+
+    text = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = "Text of this systemd unit.";
+    };
+
+    unit = mkOption {
+      internal = true;
+      description = "The generated unit.";
+    };
+
+  };
+
+  commonUnitOptions = sharedOptions // {
+
     description = mkOption {
       default = "";
       type = types.str;
@@ -109,18 +140,6 @@ in rec {
       '';
     };
 
-    requiredBy = mkOption {
-      default = [];
-      type = types.listOf types.str;
-      description = "Units that require (i.e. depend on and need to go down with) this unit.";
-    };
-
-    wantedBy = mkOption {
-      default = [];
-      type = types.listOf types.str;
-      description = "Units that want (i.e. depend on) this unit.";
-    };
-
     unitConfig = mkOption {
       default = {};
       example = { RequiresMountsFor = "/data"; };
@@ -135,6 +154,7 @@ in rec {
 
     restartTriggers = mkOption {
       default = [];
+      type = types.listOf types.unspecified;
       description = ''
         An arbitrary list of items such as derivations.  If any item
         in the list changes between reconfigurations, the service will
@@ -145,7 +165,7 @@ in rec {
   };
 
 
-  serviceOptions = unitOptions // {
+  serviceOptions = commonUnitOptions // {
 
     environment = mkOption {
       default = {};
@@ -236,6 +256,17 @@ in rec {
       '';
     };
 
+    reloadIfChanged = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether the service should be reloaded during a NixOS
+        configuration switch if its definition has changed.  If
+        enabled, the value of <option>restartIfChanged</option> is
+        ignored.
+      '';
+    };
+
     stopIfChanged = mkOption {
       type = types.bool;
       default = true;
@@ -268,7 +299,7 @@ in rec {
   };
 
 
-  socketOptions = unitOptions // {
+  socketOptions = commonUnitOptions // {
 
     listenStreams = mkOption {
       default = [];
@@ -295,7 +326,7 @@ in rec {
   };
 
 
-  timerOptions = unitOptions // {
+  timerOptions = commonUnitOptions // {
 
     timerConfig = mkOption {
       default = {};
@@ -314,7 +345,24 @@ in rec {
   };
 
 
-  mountOptions = unitOptions // {
+  pathOptions = commonUnitOptions // {
+
+    pathConfig = mkOption {
+      default = {};
+      example = { PathChanged = "/some/path"; Unit = "changedpath.service"; };
+      type = types.attrsOf unitOption;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[Path]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.path</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+  };
+
+
+  mountOptions = commonUnitOptions // {
 
     what = mkOption {
       example = "/dev/sda1";
@@ -358,7 +406,7 @@ in rec {
     };
   };
 
-  automountOptions = unitOptions // {
+  automountOptions = commonUnitOptions // {
 
     where = mkOption {
       example = "/mnt";
@@ -382,4 +430,6 @@ in rec {
     };
   };
 
+  targetOptions = commonUnitOptions;
+
 }
diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix
index b575deb24b7b..6c6adab66e7c 100644
--- a/nixos/modules/system/boot/systemd.nix
+++ b/nixos/modules/system/boot/systemd.nix
@@ -1,8 +1,8 @@
-{ config, pkgs, utils, ... }:
+{ config, lib, pkgs, utils, ... }:
 
-with pkgs.lib;
+with lib;
 with utils;
-with import ./systemd-unit-options.nix { inherit config pkgs; };
+with import ./systemd-unit-options.nix { inherit config lib; };
 
 let
 
@@ -11,29 +11,26 @@ let
   systemd = cfg.package;
 
   makeUnit = name: unit:
-    pkgs.runCommand "unit" { preferLocalBuild = true; inherit (unit) text; }
-      ((if !unit.enable then  ''
-        mkdir -p $out
-        ln -s /dev/null $out/${name}
-      '' else if unit.linkTarget != null then ''
-        mkdir -p $out
-        ln -s ${unit.linkTarget} $out/${name}
-      '' else if unit.text != null then ''
-        mkdir -p $out
-        echo -n "$text" > $out/${name}
-      '' else "") + optionalString (unit.extraConfig != {}) ''
-        mkdir -p $out/${name}.d
-        ${concatStringsSep "\n" (mapAttrsToList (n: v: "echo -n \"${v}\" > $out/${name}.d/${n}") unit.extraConfig)}
-      '');
-
-  upstreamUnits =
+    if unit.enable then
+      pkgs.runCommand "unit" { preferLocalBuild = true; inherit (unit) text; }
+        ''
+          mkdir -p $out
+          echo -n "$text" > $out/${name}
+        ''
+    else
+      pkgs.runCommand "unit" { preferLocalBuild = true; }
+        ''
+          mkdir -p $out
+          ln -s /dev/null $out/${name}
+        '';
+
+  upstreamSystemUnits =
     [ # Targets.
       "basic.target"
       "sysinit.target"
       "sockets.target"
       "graphical.target"
       "multi-user.target"
-      "getty.target"
       "network.target"
       "network-online.target"
       "nss-lookup.target"
@@ -43,6 +40,7 @@ let
       "sigpwr.target"
       "timers.target"
       "paths.target"
+      "rpcbind.target"
 
       # Rescue mode.
       "rescue.target"
@@ -55,6 +53,13 @@ let
       "systemd-udev-settle.service"
       "systemd-udev-trigger.service"
 
+      # Consoles.
+      "getty.target"
+      "getty@.service"
+      "serial-getty@.service"
+      "container-getty@.service"
+      "systemd-vconsole-setup.service"
+
       # Hardware (started by udev when a relevant device is plugged in).
       "sound.target"
       "bluetooth.target"
@@ -67,12 +72,15 @@ let
       #"systemd-vconsole-setup.service"
       "systemd-user-sessions.service"
       "dbus-org.freedesktop.login1.service"
+      "dbus-org.freedesktop.machine1.service"
       "user@.service"
 
       # Journal.
       "systemd-journald.socket"
       "systemd-journald.service"
       "systemd-journal-flush.service"
+      "systemd-journal-gatewayd.socket"
+      "systemd-journal-gatewayd.service"
       "syslog.socket"
 
       # SysV init compatibility.
@@ -80,7 +88,8 @@ let
       "systemd-initctl.service"
 
       # Kernel module loading.
-      #"systemd-modules-load.service"
+      "systemd-modules-load.service"
+      "kmod-static-nodes.service"
 
       # Filesystems.
       "systemd-fsck@.service"
@@ -93,10 +102,16 @@ let
       "swap.target"
       "dev-hugepages.mount"
       "dev-mqueue.mount"
+      "proc-sys-fs-binfmt_misc.mount"
       "sys-fs-fuse-connections.mount"
       "sys-kernel-config.mount"
       "sys-kernel-debug.mount"
 
+      # Maintaining state across reboots.
+      "systemd-random-seed.service"
+      "systemd-backlight@.service"
+      "systemd-rfkill@.service"
+
       # Hibernate / suspend.
       "hibernate.target"
       "suspend.target"
@@ -121,12 +136,30 @@ let
       "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"
+      "-.slice"
+      "system.slice"
+      "user.slice"
+      "machine.slice"
+      "systemd-machined.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"
     ]
 
     ++ optionals cfg.enableEmergencyMode [
@@ -134,16 +167,26 @@ let
       "emergency.service"
     ];
 
-  upstreamWants =
+  upstreamSystemWants =
     [ #"basic.target.wants"
       "sysinit.target.wants"
       "sockets.target.wants"
       "local-fs.target.wants"
       "multi-user.target.wants"
-      "shutdown.target.wants"
       "timers.target.wants"
     ];
 
+  upstreamUserUnits =
+    [ "basic.target"
+      "default.target"
+      "exit.target"
+      "paths.target"
+      "shutdown.target"
+      "sockets.target"
+      "systemd-exit.service"
+      "timers.target"
+    ];
+
   makeJobScript = name: text:
     let x = pkgs.writeTextFile { name = "unit-script"; executable = true; destination = "/bin/${name}"; inherit text; };
     in "${x}/bin/${name}";
@@ -151,15 +194,23 @@ let
   unitConfig = { name, config, ... }: {
     config = {
       unitConfig =
-        { Requires = concatStringsSep " " config.requires;
-          Wants = concatStringsSep " " config.wants;
-          After = concatStringsSep " " config.after;
-          Before = concatStringsSep " " config.before;
-          BindsTo = concatStringsSep " " config.bindsTo;
-          PartOf = concatStringsSep " " config.partOf;
-          Conflicts = concatStringsSep " " config.conflicts;
-          "X-Restart-Triggers" = toString config.restartTriggers;
-        } // optionalAttrs (config.description != "") {
+        optionalAttrs (config.requires != [])
+          { Requires = toString config.requires; }
+        // optionalAttrs (config.wants != [])
+          { Wants = toString config.wants; }
+        // optionalAttrs (config.after != [])
+          { After = toString config.after; }
+        // optionalAttrs (config.before != [])
+          { Before = toString config.before; }
+        // optionalAttrs (config.bindsTo != [])
+          { BindsTo = toString config.bindsTo; }
+        // optionalAttrs (config.partOf != [])
+          { PartOf = toString config.partOf; }
+        // optionalAttrs (config.conflicts != [])
+          { Conflicts = toString config.conflicts; }
+        // optionalAttrs (config.restartTriggers != [])
+          { X-Restart-Triggers = toString config.restartTriggers; }
+        // optionalAttrs (config.description != "") {
           Description = config.description;
         };
     };
@@ -244,6 +295,11 @@ let
         (if isList value then value else [value]))
         as));
 
+  commonUnitText = def: ''
+      [Unit]
+      ${attrsToSection def.unitConfig}
+    '';
+
   targetToUnit = name: def:
     { inherit (def) wantedBy requiredBy enable;
       text =
@@ -255,15 +311,16 @@ let
 
   serviceToUnit = name: def:
     { inherit (def) wantedBy requiredBy enable;
-      text =
+      text = commonUnitText def +
         ''
-          [Unit]
-          ${attrsToSection def.unitConfig}
-
           [Service]
           ${let env = cfg.globalEnvironment // def.environment;
             in concatMapStrings (n: "Environment=\"${n}=${getAttr n env}\"\n") (attrNames env)}
-          ${optionalString (!def.restartIfChanged) "X-RestartIfChanged=false"}
+          ${if def.reloadIfChanged then ''
+            X-ReloadIfChanged=true
+          '' else if !def.restartIfChanged then ''
+            X-RestartIfChanged=false
+          '' else ""}
           ${optionalString (!def.stopIfChanged) "X-StopIfChanged=false"}
           ${attrsToSection def.serviceConfig}
         '';
@@ -271,11 +328,8 @@ let
 
   socketToUnit = name: def:
     { inherit (def) wantedBy requiredBy enable;
-      text =
+      text = commonUnitText def +
         ''
-          [Unit]
-          ${attrsToSection def.unitConfig}
-
           [Socket]
           ${attrsToSection def.socketConfig}
           ${concatStringsSep "\n" (map (s: "ListenStream=${s}") def.listenStreams)}
@@ -284,23 +338,26 @@ let
 
   timerToUnit = name: def:
     { inherit (def) wantedBy requiredBy enable;
-      text =
+      text = commonUnitText def +
         ''
-          [Unit]
-          ${attrsToSection def.unitConfig}
-
           [Timer]
           ${attrsToSection def.timerConfig}
         '';
     };
 
-  mountToUnit = name: def:
+  pathToUnit = name: def:
     { inherit (def) wantedBy requiredBy enable;
-      text =
+      text = commonUnitText def +
         ''
-          [Unit]
-          ${attrsToSection def.unitConfig}
+          [Path]
+          ${attrsToSection def.pathConfig}
+        '';
+    };
 
+  mountToUnit = name: def:
+    { inherit (def) wantedBy requiredBy enable;
+      text = commonUnitText def +
+        ''
           [Mount]
           ${attrsToSection def.mountConfig}
         '';
@@ -308,70 +365,99 @@ let
 
   automountToUnit = name: def:
     { inherit (def) wantedBy requiredBy enable;
-      text =
+      text = commonUnitText def +
         ''
-          [Unit]
-          ${attrsToSection def.unitConfig}
-
           [Automount]
           ${attrsToSection def.automountConfig}
         '';
     };
 
-  units = pkgs.runCommand "units" { preferLocalBuild = true; }
-    ''
+  generateUnits = type: units: upstreamUnits: upstreamWants:
+    pkgs.runCommand "${type}-units" { preferLocalBuild = true; } ''
       mkdir -p $out
+
+      # Copy the upstream systemd units we're interested in.
       for i in ${toString upstreamUnits}; do
-        fn=${systemd}/example/systemd/system/$i
+        fn=${systemd}/example/systemd/${type}/$i
         if ! [ -e $fn ]; then echo "missing $fn"; false; fi
         if [ -L $fn ]; then
-          cp -pd $fn $out/
+          target="$(readlink "$fn")"
+          if [ ''${target:0:3} = ../ ]; then
+            ln -s "$(readlink -f "$fn")" $out/
+          else
+            cp -pd $fn $out/
+          fi
         else
           ln -s $fn $out/
         fi
       done
 
+      # Copy .wants links, but only those that point to units that
+      # we're interested in.
       for i in ${toString upstreamWants}; do
-        fn=${systemd}/example/systemd/system/$i
+        fn=${systemd}/example/systemd/${type}/$i
         if ! [ -e $fn ]; then echo "missing $fn"; false; fi
         x=$out/$(basename $fn)
         mkdir $x
         for i in $fn/*; do
           y=$x/$(basename $i)
           cp -pd $i $y
-          if ! [ -e $y ]; then rm -v $y; fi
+          if ! [ -e $y ]; then rm $y; fi
         done
       done
 
-      for i in ${toString (mapAttrsToList (n: v: v.unit) cfg.units)}; do
-        ln -fs $i/* $out/
+      # Symlink all units provided listed in systemd.packages.
+      for i in ${toString cfg.packages}; do
+        files=$(echo $i/etc/systemd/${type}/* $i/lib/systemd/${type}/*)
+        if [ -n "$files" ]; then
+          ln -s $files $out/
+        fi
       done
 
-      for i in ${toString cfg.packages}; do
-        ln -s $i/etc/systemd/system/* $out/
+      # Symlink all units defined by systemd.units. If these are also
+      # provided by systemd or systemd.packages, then add them as
+      # <unit-name>.d/overrides.conf, which makes them extend the
+      # upstream unit.
+      for i in ${toString (mapAttrsToList (n: v: v.unit) units)}; do
+        fn=$(basename $i/*)
+        if [ -e $out/$fn ]; then
+          if [ "$(readlink -f $i/$fn)" = /dev/null ]; then
+            ln -sfn /dev/null $out/$fn
+          else
+            mkdir $out/$fn.d
+            ln -s $i/$fn $out/$fn.d/overrides.conf
+          fi
+       else
+          ln -fs $i/$fn $out/
+        fi
       done
 
+      # Created .wants and .requires symlinks from the wantedBy and
+      # requiredBy options.
       ${concatStrings (mapAttrsToList (name: unit:
           concatMapStrings (name2: ''
             mkdir -p $out/'${name2}.wants'
             ln -sfn '../${name}' $out/'${name2}.wants'/
-          '') unit.wantedBy) cfg.units)}
+          '') unit.wantedBy) units)}
 
       ${concatStrings (mapAttrsToList (name: unit:
           concatMapStrings (name2: ''
             mkdir -p $out/'${name2}.requires'
             ln -sfn '../${name}' $out/'${name2}.requires'/
-          '') unit.requiredBy) cfg.units)}
+          '') unit.requiredBy) units)}
 
-      ln -s ${cfg.defaultUnit} $out/default.target
+      ${optionalString (type == "system") ''
+        # Stupid misc. symlinks.
+        ln -s ${cfg.defaultUnit} $out/default.target
 
-      ln -s rescue.target $out/kbrequest.target
+        ln -s rescue.target $out/kbrequest.target
 
-      mkdir -p $out/getty.target.wants/
-      ln -s ../autovt@tty1.service $out/getty.target.wants/
+        mkdir -p $out/getty.target.wants/
+        ln -s ../autovt@tty1.service $out/getty.target.wants/
 
-      ln -s ../local-fs.target ../remote-fs.target ../network.target ../nss-lookup.target \
-            ../nss-user-lookup.target ../swap.target $out/multi-user.target.wants/
+        ln -s ../local-fs.target ../remote-fs.target ../network.target ../nss-lookup.target \
+              ../nss-user-lookup.target ../swap.target $out/multi-user.target.wants/
+      ''}
     ''; # */
 
 in
@@ -393,55 +479,9 @@ in
       default = {};
       type = types.attrsOf types.optionSet;
       options = { name, config, ... }:
-        { options = {
-            text = mkOption {
-              type = types.nullOr types.str;
-              default = null;
-              description = "Text of this systemd unit.";
-            };
-            enable = mkOption {
-              default = true;
-              type = types.bool;
-              description = ''
-                If set to false, this unit will be a symlink to
-                /dev/null. This is primarily useful to prevent specific
-                template instances (e.g. <literal>serial-getty@ttyS0</literal>)
-                from being started.
-              '';
-            };
-            requiredBy = mkOption {
-              default = [];
-              type = types.listOf types.string;
-              description = "Units that require (i.e. depend on and need to go down with) this unit.";
-            };
-            wantedBy = mkOption {
-              default = [];
-              type = types.listOf types.string;
-              description = "Units that want (i.e. depend on) this unit.";
-            };
-            unit = mkOption {
-              internal = true;
-              description = "The generated unit.";
-            };
-            linkTarget = mkOption {
-              default = null;
-              description = "The file to symlink this target to.";
-              type = types.nullOr types.path;
-            };
-            extraConfig = mkOption {
-              default = {};
-              example = { "foo@1.conf" = "X-RestartIfChanged=false"; };
-              type = types.attrsOf types.lines;
-              description = ''
-                Extra files to be appended to the configuration for the unit.
-                This can be used to override configuration for a unit provided
-                by systemd or another package, or to override only a single instance
-                of a template unit.
-              '';
-            };
-          };
+        { options = concreteUnitOptions;
           config = {
-            unit = makeUnit name config;
+            unit = mkDefault (makeUnit name config);
           };
         };
     };
@@ -455,7 +495,7 @@ in
     systemd.targets = mkOption {
       default = {};
       type = types.attrsOf types.optionSet;
-      options = [ unitOptions unitConfig ];
+      options = [ targetOptions unitConfig ];
       description = "Definition of systemd target units.";
     };
 
@@ -480,6 +520,13 @@ in
       description = "Definition of systemd timer units.";
     };
 
+    systemd.paths = mkOption {
+      default = {};
+      type = types.attrsOf types.optionSet;
+      options = [ pathOptions unitConfig ];
+      description = "Definition of systemd path units.";
+    };
+
     systemd.mounts = mkOption {
       default = [];
       type = types.listOf types.optionSet;
@@ -567,6 +614,14 @@ in
       '';
     };
 
+    services.journald.enableHttpGateway = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Whether to enable the HTTP gateway to the journal.
+      '';
+    };
+
     services.logind.extraConfig = mkOption {
       default = "";
       type = types.lines;
@@ -590,6 +645,41 @@ in
       '';
     };
 
+    systemd.tmpfiles.rules = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = [ "d /tmp 1777 root root 10d" ];
+      description = ''
+        Rules for creating and cleaning up temporary files
+        automatically. See
+        <citerefentry><refentrytitle>tmpfiles.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        for the exact format. You should not use this option to create
+        files required by systemd services, since there is no
+        guarantee that <command>systemd-tmpfiles</command> runs when
+        the system is reconfigured using
+        <command>nixos-rebuild</command>.
+      '';
+    };
+
+    systemd.user.units = mkOption {
+      description = "Definition of systemd per-user units.";
+      default = {};
+      type = types.attrsOf types.optionSet;
+      options = { name, config, ... }:
+        { options = concreteUnitOptions;
+          config = {
+            unit = mkDefault (makeUnit name config);
+          };
+        };
+    };
+
+    systemd.user.services = mkOption {
+      default = {};
+      type = types.attrsOf types.optionSet;
+      options = [ serviceOptions unitConfig serviceConfig ];
+      description = "Definition of systemd per-user service units.";
+    };
+
   };
 
 
@@ -597,11 +687,20 @@ in
 
   config = {
 
-    system.build.units = units;
+    assertions = mapAttrsToList (name: service: {
+      assertion = service.serviceConfig.Type or "" == "oneshot" -> service.serviceConfig.Restart or "no" == "no";
+      message = "${name}: Type=oneshot services must have Restart=no";
+    }) cfg.services;
+
+    system.build.units = cfg.units;
 
     environment.systemPackages = [ systemd ];
 
-    environment.etc."systemd/system".source = units;
+    environment.etc."systemd/system".source =
+      generateUnits "system" cfg.units upstreamSystemUnits upstreamSystemWants;
+
+    environment.etc."systemd/user".source =
+      generateUnits "user" cfg.user.units upstreamUserUnits [];
 
     environment.etc."systemd/system.conf".text =
       ''
@@ -645,8 +744,11 @@ in
       '';
 
     # Target for ‘charon send-keys’ to hook into.
+    users.extraGroups.keys.gid = config.ids.gids.keys;
+
     systemd.targets.keys =
       { description = "Security Keys";
+        unitConfig.X-StopOnReconfiguration = true;
       };
 
     systemd.units =
@@ -654,6 +756,7 @@ in
       // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.services
       // mapAttrs' (n: v: nameValuePair "${n}.socket" (socketToUnit n v)) cfg.sockets
       // mapAttrs' (n: v: nameValuePair "${n}.timer" (timerToUnit n v)) cfg.timers
+      // mapAttrs' (n: v: nameValuePair "${n}.path" (pathToUnit n v)) cfg.paths
       // listToAttrs (map
                    (v: let n = escapeSystemdPath v.where;
                        in nameValuePair "${n}.mount" (mountToUnit n v)) cfg.mounts)
@@ -661,6 +764,9 @@ in
                    (v: let n = escapeSystemdPath v.where;
                        in nameValuePair "${n}.automount" (automountToUnit n v)) cfg.automounts);
 
+    systemd.user.units =
+      mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.user.services;
+
     system.requiredKernelConfig = map config.lib.kernelConfig.isEnabled [
       "CGROUPS" "AUTOFS4_FS" "DEVTMPFS"
     ];
@@ -673,6 +779,8 @@ in
       };
 
     users.extraGroups.systemd-journal.gid = config.ids.gids.systemd-journal;
+    users.extraUsers.systemd-journal-gateway.uid = config.ids.uids.systemd-journal-gateway;
+    users.extraGroups.systemd-journal-gateway.gid = config.ids.gids.systemd-journal-gateway;
 
     # Generate timer units for all services that have a ‘startAt’ value.
     systemd.timers =
@@ -682,43 +790,25 @@ in
         })
         (filterAttrs (name: service: service.startAt != "") cfg.services);
 
-    # FIXME: These are borrowed from upstream systemd.
-    systemd.services."systemd-update-utmp" =
-      { description = "Update UTMP about System Reboot/Shutdown";
-        wantedBy = [ "sysinit.target" ];
-        after = [ "systemd-remount-fs.service" ];
-        before = [ "sysinit.target" "shutdown.target" ];
-        conflicts = [ "shutdown.target" ];
-        unitConfig = {
-          DefaultDependencies = false;
-          RequiresMountsFor = "/var/log";
-        };
-        serviceConfig = {
-          Type = "oneshot";
-          RemainAfterExit = true;
-          ExecStart = "${systemd}/lib/systemd/systemd-update-utmp reboot";
-          ExecStop = "${systemd}/lib/systemd/systemd-update-utmp shutdown";
-        };
-        restartIfChanged = false;
-      };
+    systemd.sockets.systemd-journal-gatewayd.wantedBy =
+      optional config.services.journald.enableHttpGateway "sockets.target";
 
-    systemd.services."systemd-random-seed" =
-      { description = "Load/Save Random Seed";
-        wantedBy = [ "sysinit.target" "multi-user.target" ];
-        after = [ "systemd-remount-fs.service" ];
-        before = [ "sysinit.target" "shutdown.target" ];
-        conflicts = [ "shutdown.target" ];
-        unitConfig = {
-          DefaultDependencies = false;
-          RequiresMountsFor = "/var/lib";
-        };
-        serviceConfig = {
-          Type = "oneshot";
-          RemainAfterExit = true;
-          ExecStart = "${systemd}/lib/systemd/systemd-random-seed load";
-          ExecStop = "${systemd}/lib/systemd/systemd-random-seed save";
-        };
+    # 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;
       };
 
+    environment.etc."tmpfiles.d/x11.conf".source = "${systemd}/example/tmpfiles.d/x11.conf";
+
+    environment.etc."tmpfiles.d/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}
+      '';
+
   };
 }
diff --git a/nixos/modules/system/etc/etc.nix b/nixos/modules/system/etc/etc.nix
index a8f0a59b6fa9..22d55a9e246c 100644
--- a/nixos/modules/system/etc/etc.nix
+++ b/nixos/modules/system/etc/etc.nix
@@ -1,8 +1,8 @@
 # Management of static files in /etc.
 
-{ config, pkgs, ... }:
+{ config, lib, pkgs, ... }:
 
-with pkgs.lib;
+with lib;
 
 let
 
@@ -19,6 +19,8 @@ let
     sources = map (x: x.source) etc';
     targets = map (x: x.target) etc';
     modes = map (x: x.mode) etc';
+    uids  = map (x: x.uid) etc';
+    gids  = map (x: x.gid) etc';
   };
 
 in
@@ -87,6 +89,24 @@ in
               '';
             };
 
+            uid = mkOption {
+              default = 0;
+              type = types.int;
+              description = ''
+                UID of created file. Only takes affect 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 affect when the file is
+                copied (that is, the mode is not 'symlink').
+              '';
+            };
+
           };
 
           config = {
diff --git a/nixos/modules/system/etc/make-etc.sh b/nixos/modules/system/etc/make-etc.sh
index 7cf68db9ddce..60d4ba1301a3 100644
--- a/nixos/modules/system/etc/make-etc.sh
+++ b/nixos/modules/system/etc/make-etc.sh
@@ -6,6 +6,8 @@ set -f
 sources_=($sources)
 targets_=($targets)
 modes_=($modes)
+uids_=($uids)
+gids_=($gids)
 set +f
 
 for ((i = 0; i < ${#targets_[@]}; i++)); do
@@ -35,6 +37,8 @@ for ((i = 0; i < ${#targets_[@]}; i++)); do
         
         if test "${modes_[$i]}" != symlink; then
             echo "${modes_[$i]}" > $out/etc/$target.mode
+            echo "${uids_[$i]}" > $out/etc/$target.uid
+            echo "${gids_[$i]}" > $out/etc/$target.gid
         fi
         
     fi
diff --git a/nixos/modules/system/etc/setup-etc.pl b/nixos/modules/system/etc/setup-etc.pl
index 4b79dbaab89e..8ba9a370b27a 100644
--- a/nixos/modules/system/etc/setup-etc.pl
+++ b/nixos/modules/system/etc/setup-etc.pl
@@ -60,7 +60,15 @@ sub link {
         if ($mode eq "direct-symlink") {
             atomicSymlink readlink("$static/$fn"), $target or warn;
         } else {
+            open UID, "<$_.uid";
+            my $uid = <UID>; chomp $uid;
+            close UID;
+            open GID, "<$_.gid";
+            my $gid = <GID>; chomp $gid;
+            close GID;
+
             copy "$static/$fn", "$target.tmp" or warn;
+            chown int($uid), int($gid), "$target.tmp" or warn;
             chmod oct($mode), "$target.tmp" or warn;
             rename "$target.tmp", $target or warn;
         }
diff --git a/nixos/modules/system/upstart/upstart.nix b/nixos/modules/system/upstart/upstart.nix
index aa5c8dfe64b2..5c0461304072 100644
--- a/nixos/modules/system/upstart/upstart.nix
+++ b/nixos/modules/system/upstart/upstart.nix
@@ -1,7 +1,7 @@
-{ config, pkgs, ... }:
+{ config, lib, pkgs, ... }:
 
-with pkgs.lib;
-with import ../boot/systemd-unit-options.nix { inherit config pkgs; };
+with lib;
+with import ../boot/systemd-unit-options.nix { inherit config lib; };
 
 let
 
@@ -93,7 +93,7 @@ let
             if job.daemonType == "fork" || job.daemonType == "daemon" then { Type = "forking"; GuessMainPID = true; } else
             if job.daemonType == "none" then { } else
             throw "invalid daemon type `${job.daemonType}'")
-        // optionalAttrs (!job.task && job.respawn)
+        // optionalAttrs (!job.task && !(job.script == "" && job.exec == "") && job.respawn)
           { Restart = "always"; }
         // optionalAttrs job.task
           { Type = "oneshot"; RemainAfterExit = false; };