about summary refs log tree commit diff
path: root/nixos/modules
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules')
-rw-r--r--nixos/modules/config/fonts/corefonts.nix36
-rw-r--r--nixos/modules/config/fonts/fontconfig-ultimate.nix86
-rw-r--r--nixos/modules/config/pulseaudio.nix5
-rw-r--r--nixos/modules/config/shells-environment.nix14
-rw-r--r--nixos/modules/config/update-users-groups.pl4
-rw-r--r--nixos/modules/hardware/brillo.nix22
-rw-r--r--nixos/modules/hardware/video/displaylink.nix22
-rw-r--r--nixos/modules/installer/tools/nixos-option.sh327
-rw-r--r--nixos/modules/installer/tools/nixos-option/CMakeLists.txt8
-rw-r--r--nixos/modules/installer/tools/nixos-option/default.nix11
-rw-r--r--nixos/modules/installer/tools/nixos-option/libnix-copy-paste.cc83
-rw-r--r--nixos/modules/installer/tools/nixos-option/libnix-copy-paste.hh9
-rw-r--r--nixos/modules/installer/tools/nixos-option/nixos-option.cc618
-rw-r--r--nixos/modules/installer/tools/nixos-rebuild.sh9
-rw-r--r--nixos/modules/installer/tools/tools.nix7
-rw-r--r--nixos/modules/misc/ids.nix4
-rw-r--r--nixos/modules/module-list.nix12
-rw-r--r--nixos/modules/programs/gnupg.nix4
-rw-r--r--nixos/modules/programs/shadow.nix14
-rw-r--r--nixos/modules/programs/ssh.nix11
-rw-r--r--nixos/modules/programs/x2goserver.nix1
-rw-r--r--nixos/modules/programs/zsh/zsh-syntax-highlighting.nix4
-rw-r--r--nixos/modules/rename.nix13
-rw-r--r--nixos/modules/security/acme.nix46
-rw-r--r--nixos/modules/security/pam_mount.nix6
-rw-r--r--nixos/modules/services/admin/oxidized.nix1
-rw-r--r--nixos/modules/services/audio/jack.nix1
-rw-r--r--nixos/modules/services/audio/mpd.nix1
-rw-r--r--nixos/modules/services/backup/automysqlbackup.nix5
-rw-r--r--nixos/modules/services/backup/znapzend.nix28
-rw-r--r--nixos/modules/services/continuous-integration/buildkite-agent.nix1
-rw-r--r--nixos/modules/services/databases/redis.nix13
-rw-r--r--nixos/modules/services/databases/rethinkdb.nix1
-rw-r--r--nixos/modules/services/desktops/geoclue2.nix41
-rw-r--r--nixos/modules/services/editors/infinoted.nix1
-rw-r--r--nixos/modules/services/hardware/fancontrol.nix39
-rw-r--r--nixos/modules/services/hardware/trezord.nix16
-rw-r--r--nixos/modules/services/hardware/usbmuxd.nix1
-rw-r--r--nixos/modules/services/hardware/vdr.nix1
-rw-r--r--nixos/modules/services/mail/mailhog.nix1
-rw-r--r--nixos/modules/services/misc/airsonic.nix1
-rw-r--r--nixos/modules/services/misc/docker-registry.nix6
-rw-r--r--nixos/modules/services/misc/errbot.nix5
-rw-r--r--nixos/modules/services/misc/gitea.nix1
-rw-r--r--nixos/modules/services/misc/gitlab.nix8
-rw-r--r--nixos/modules/services/misc/gollum.nix1
-rw-r--r--nixos/modules/services/misc/jellyfin.nix5
-rw-r--r--nixos/modules/services/misc/matrix-synapse.nix3
-rw-r--r--nixos/modules/services/misc/osrm.nix1
-rw-r--r--nixos/modules/services/misc/redmine.nix15
-rw-r--r--nixos/modules/services/misc/zoneminder.nix2
-rw-r--r--nixos/modules/services/monitoring/collectd.nix1
-rw-r--r--nixos/modules/services/monitoring/fusion-inventory.nix1
-rw-r--r--nixos/modules/services/monitoring/netdata.nix3
-rw-r--r--nixos/modules/services/monitoring/zabbix-agent.nix1
-rw-r--r--nixos/modules/services/network-filesystems/orangefs/client.nix97
-rw-r--r--nixos/modules/services/network-filesystems/orangefs/server.nix225
-rw-r--r--nixos/modules/services/networking/bitcoind.nix1
-rw-r--r--nixos/modules/services/networking/dnscache.nix2
-rw-r--r--nixos/modules/services/networking/dnscrypt-wrapper.nix1
-rw-r--r--nixos/modules/services/networking/dnsdist.nix5
-rw-r--r--nixos/modules/services/networking/go-shadowsocks2.nix30
-rw-r--r--nixos/modules/services/networking/hans.nix1
-rw-r--r--nixos/modules/services/networking/haproxy.nix36
-rw-r--r--nixos/modules/services/networking/jormungandr.nix102
-rw-r--r--nixos/modules/services/networking/matterbridge.nix1
-rw-r--r--nixos/modules/services/networking/morty.nix1
-rw-r--r--nixos/modules/services/networking/nat.nix2
-rw-r--r--nixos/modules/services/networking/networkmanager.nix22
-rw-r--r--nixos/modules/services/networking/nghttpx/default.nix1
-rw-r--r--nixos/modules/services/networking/owamp.nix1
-rw-r--r--nixos/modules/services/networking/thelounge.nix1
-rw-r--r--nixos/modules/services/networking/tinydns.nix2
-rw-r--r--nixos/modules/services/networking/trickster.nix112
-rw-r--r--nixos/modules/services/printing/cupsd.nix2
-rw-r--r--nixos/modules/services/scheduling/marathon.nix2
-rw-r--r--nixos/modules/services/security/bitwarden_rs/default.nix5
-rw-r--r--nixos/modules/services/security/oauth2_proxy.nix1
-rw-r--r--nixos/modules/services/security/vault.nix5
-rw-r--r--nixos/modules/services/torrent/magnetico.nix1
-rw-r--r--nixos/modules/services/web-apps/codimd.nix1
-rw-r--r--nixos/modules/services/web-apps/frab.nix1
-rw-r--r--nixos/modules/services/web-apps/limesurvey.nix5
-rw-r--r--nixos/modules/services/web-apps/matomo-doc.xml2
-rw-r--r--nixos/modules/services/web-apps/matomo.nix56
-rw-r--r--nixos/modules/services/web-apps/mediawiki.nix5
-rw-r--r--nixos/modules/services/web-apps/moinmoin.nix303
-rw-r--r--nixos/modules/services/web-apps/moodle.nix6
-rw-r--r--nixos/modules/services/web-apps/nexus.nix1
-rw-r--r--nixos/modules/services/web-apps/trac.nix79
-rw-r--r--nixos/modules/services/web-apps/virtlyst.nix1
-rw-r--r--nixos/modules/services/web-apps/wordpress.nix5
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/default.nix134
-rw-r--r--nixos/modules/services/web-servers/hitch/default.nix5
-rw-r--r--nixos/modules/services/web-servers/traefik.nix1
-rw-r--r--nixos/modules/services/web-servers/unit/default.nix1
-rw-r--r--nixos/modules/services/x11/display-managers/sddm.nix2
-rw-r--r--nixos/modules/services/x11/hardware/digimend.nix43
-rw-r--r--nixos/modules/services/x11/hardware/libinput.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/xmonad.nix2
-rw-r--r--nixos/modules/system/boot/networkd.nix2
-rw-r--r--nixos/modules/system/boot/plymouth.nix10
-rw-r--r--nixos/modules/system/boot/systemd-unit-options.nix2
-rw-r--r--nixos/modules/virtualisation/amazon-image.nix3
-rw-r--r--nixos/modules/virtualisation/ec2-amis.nix18
-rw-r--r--nixos/modules/virtualisation/qemu-vm.nix135
106 files changed, 2203 insertions, 862 deletions
diff --git a/nixos/modules/config/fonts/corefonts.nix b/nixos/modules/config/fonts/corefonts.nix
deleted file mode 100644
index b9f69879a103..000000000000
--- a/nixos/modules/config/fonts/corefonts.nix
+++ /dev/null
@@ -1,36 +0,0 @@
-# This module is deprecated, since you can just say ‘fonts.fonts = [
-# pkgs.corefonts ];’ instead.
-
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-{
-
-  options = {
-
-    fonts = {
-
-      enableCoreFonts = mkOption {
-        visible = false;
-        default = false;
-        description = ''
-          Whether to include Microsoft's proprietary Core Fonts.  These fonts
-          are redistributable, but only verbatim, among other restrictions.
-          See <link xlink:href="http://corefonts.sourceforge.net/eula.htm"/>
-          for details.
-       '';
-      };
-
-    };
-
-  };
-
-
-  config = mkIf config.fonts.enableCoreFonts {
-
-    fonts.fonts = [ pkgs.corefonts ];
-
-  };
-
-}
diff --git a/nixos/modules/config/fonts/fontconfig-ultimate.nix b/nixos/modules/config/fonts/fontconfig-ultimate.nix
deleted file mode 100644
index 84d90899dfff..000000000000
--- a/nixos/modules/config/fonts/fontconfig-ultimate.nix
+++ /dev/null
@@ -1,86 +0,0 @@
-{ config, pkgs, lib, ... }:
-
-with lib;
-
-let cfg = config.fonts.fontconfig.ultimate;
-
-    latestVersion  = pkgs.fontconfig.configVersion;
-
-    # The configuration to be included in /etc/font/
-    confPkg = pkgs.runCommand "font-ultimate-conf" { preferLocalBuild = true; } ''
-      support_folder=$out/etc/fonts/conf.d
-      latest_folder=$out/etc/fonts/${latestVersion}/conf.d
-
-      mkdir -p $support_folder
-      mkdir -p $latest_folder
-
-      # fontconfig ultimate substitutions
-      ${optionalString (cfg.substitutions != "none") ''
-      ln -s ${pkgs.fontconfig-ultimate}/etc/fonts/presets/${cfg.substitutions}/*.conf \
-            $support_folder
-      ln -s ${pkgs.fontconfig-ultimate}/etc/fonts/presets/${cfg.substitutions}/*.conf \
-            $latest_folder
-      ''}
-
-      # fontconfig ultimate various configuration files
-      ln -s ${pkgs.fontconfig-ultimate}/etc/fonts/conf.d/*.conf \
-            $support_folder
-      ln -s ${pkgs.fontconfig-ultimate}/etc/fonts/conf.d/*.conf \
-            $latest_folder
-    '';
-
-in
-{
-
-  options = {
-
-    fonts = {
-
-      fontconfig = {
-
-        ultimate = {
-          enable = mkOption {
-            type = types.bool;
-            default = false;
-            description = ''
-              Enable fontconfig-ultimate settings (formerly known as
-              Infinality). Besides the customizable settings in this NixOS
-              module, fontconfig-ultimate also provides many font-specific
-              rendering tweaks.
-            '';
-          };
-
-          substitutions = mkOption {
-            type = types.enum ["free" "combi" "ms" "none"];
-            default = "free";
-            description = ''
-              Font substitutions to replace common Type 1 fonts with nicer
-              TrueType fonts. <literal>free</literal> uses free fonts,
-              <literal>ms</literal> uses Microsoft fonts,
-              <literal>combi</literal> uses a combination, and
-              <literal>none</literal> disables the substitutions.
-            '';
-          };
-
-          preset = mkOption {
-            type = types.enum ["ultimate1" "ultimate2" "ultimate3" "ultimate4" "ultimate5" "osx" "windowsxp"];
-            default = "ultimate3";
-            description = ''
-              FreeType rendering settings preset. Any of the presets may be
-              customized by setting environment variables.
-            '';
-          };
-        };
-      };
-    };
-
-  };
-
-  config = mkIf (config.fonts.fontconfig.enable && cfg.enable) {
-
-    fonts.fontconfig.confPackages = [ confPkg ];
-    environment.variables.INFINALITY_FT = cfg.preset;
-
-  };
-
-}
diff --git a/nixos/modules/config/pulseaudio.nix b/nixos/modules/config/pulseaudio.nix
index b3bc4a451aa0..9baad9b58545 100644
--- a/nixos/modules/config/pulseaudio.nix
+++ b/nixos/modules/config/pulseaudio.nix
@@ -98,11 +98,12 @@ in {
         description = ''
           If false, a PulseAudio server is launched automatically for
           each user that tries to use the sound system. The server runs
-          with user privileges. This is the recommended and most secure
-          way to use PulseAudio. If true, one system-wide PulseAudio
+          with user privileges. If true, one system-wide PulseAudio
           server is launched on boot, running as the user "pulse", and
           only users in the "audio" group will have access to the server.
           Please read the PulseAudio documentation for more details.
+
+          Don't enable this option unless you know what you are doing.
         '';
       };
 
diff --git a/nixos/modules/config/shells-environment.nix b/nixos/modules/config/shells-environment.nix
index d939cbb393ee..b79e16cd7979 100644
--- a/nixos/modules/config/shells-environment.nix
+++ b/nixos/modules/config/shells-environment.nix
@@ -118,6 +118,14 @@ in
       type = with types; attrsOf (nullOr (either str path));
     };
 
+    environment.homeBinInPath = mkOption {
+      description = ''
+        Include ~/bin/ in $PATH.
+      '';
+      default = true;
+      type = types.bool;
+    };
+
     environment.binsh = mkOption {
       default = "${config.system.build.binsh}/bin/sh";
       defaultText = "\${config.system.build.binsh}/bin/sh";
@@ -186,8 +194,10 @@ in
 
         ${cfg.extraInit}
 
-        # ~/bin if it exists overrides other bin directories.
-        export PATH="$HOME/bin:$PATH"
+        ${optionalString cfg.homeBinInPath ''
+          # ~/bin if it exists overrides other bin directories.
+          export PATH="$HOME/bin:$PATH"
+        ''}
       '';
 
     system.activationScripts.binsh = stringAfter [ "stdio" ]
diff --git a/nixos/modules/config/update-users-groups.pl b/nixos/modules/config/update-users-groups.pl
index 59cea51c611b..15e448b787aa 100644
--- a/nixos/modules/config/update-users-groups.pl
+++ b/nixos/modules/config/update-users-groups.pl
@@ -56,12 +56,12 @@ sub allocGid {
         $gidsUsed{$prevGid} = 1;
         return $prevGid;
     }
-    return allocId(\%gidsUsed, \%gidsPrevUsed, 400, 499, 0, sub { my ($gid) = @_; getgrgid($gid) });
+    return allocId(\%gidsUsed, \%gidsPrevUsed, 400, 999, 0, sub { my ($gid) = @_; getgrgid($gid) });
 }
 
 sub allocUid {
     my ($name, $isSystemUser) = @_;
-    my ($min, $max, $up) = $isSystemUser ? (400, 499, 0) : (1000, 29999, 1);
+    my ($min, $max, $up) = $isSystemUser ? (400, 999, 0) : (1000, 29999, 1);
     my $prevUid = $uidMap->{$name};
     if (defined $prevUid && $prevUid >= $min && $prevUid <= $max && !defined $uidsUsed{$prevUid}) {
         print STDERR "reviving user '$name' with UID $prevUid\n";
diff --git a/nixos/modules/hardware/brillo.nix b/nixos/modules/hardware/brillo.nix
new file mode 100644
index 000000000000..e970c9480998
--- /dev/null
+++ b/nixos/modules/hardware/brillo.nix
@@ -0,0 +1,22 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.hardware.brillo;
+in
+{
+  options = {
+    hardware.brillo = {
+      enable = mkEnableOption ''
+        Enable brillo in userspace.
+        This will allow brightness control from users in the video group.
+      '';
+    };
+  };
+
+
+  config = mkIf cfg.enable {
+    services.udev.packages = [ pkgs.brillo ];
+    environment.systemPackages = [ pkgs.brillo ];
+  };
+}
diff --git a/nixos/modules/hardware/video/displaylink.nix b/nixos/modules/hardware/video/displaylink.nix
index 669ac849cbad..912f53da836a 100644
--- a/nixos/modules/hardware/video/displaylink.nix
+++ b/nixos/modules/hardware/video/displaylink.nix
@@ -19,6 +19,21 @@ in
   config = mkIf enabled {
 
     boot.extraModulePackages = [ evdi ];
+    boot.kernelModules = [ "evdi" ];
+
+    environment.etc."X11/xorg.conf.d/40-displaylink.conf".text = ''
+      Section "OutputClass"
+        Identifier  "DisplayLink"
+        MatchDriver "evdi"
+        Driver      "modesetting"
+        Option      "AccelMethod" "none"
+      EndSection
+    '';
+
+    # make the device available
+    services.xserver.displayManager.sessionCommands = ''
+      ${lib.getBin pkgs.xorg.xrandr}/bin/xrandr --setprovideroutputsource 1 0
+    '';
 
     # Those are taken from displaylink-installer.sh and from Arch Linux AUR package.
 
@@ -47,18 +62,13 @@ in
       description = "DisplayLink Manager Service";
       after = [ "display-manager.service" ];
       conflicts = [ "getty@tty7.service" ];
-      path = [ pkgs.kmod ];
 
       serviceConfig = {
         ExecStart = "${displaylink}/bin/DisplayLinkManager";
         Restart = "always";
         RestartSec = 5;
+        LogsDirectory = "displaylink";
       };
-
-      preStart = ''
-        mkdir -p /var/log/displaylink
-        modprobe evdi
-      '';
     };
 
   };
diff --git a/nixos/modules/installer/tools/nixos-option.sh b/nixos/modules/installer/tools/nixos-option.sh
deleted file mode 100644
index 4560e9c7403a..000000000000
--- a/nixos/modules/installer/tools/nixos-option.sh
+++ /dev/null
@@ -1,327 +0,0 @@
-#! @shell@ -e
-
-# FIXME: rewrite this in a more suitable language.
-
-usage () {
-    exec man nixos-option
-    exit 1
-}
-
-#####################
-# Process Arguments #
-#####################
-
-xml=false
-verbose=false
-nixPath=""
-
-option=""
-exit_code=0
-
-argfun=""
-for arg; do
-  if test -z "$argfun"; then
-    case $arg in
-      -*)
-        sarg="$arg"
-        longarg=""
-        while test "$sarg" != "-"; do
-          case $sarg in
-            --*) longarg=$arg; sarg="--";;
-            -I) argfun="include_nixpath";;
-            -*) usage;;
-          esac
-          # remove the first letter option
-          sarg="-${sarg#??}"
-        done
-        ;;
-      *) longarg=$arg;;
-    esac
-    for larg in $longarg; do
-      case $larg in
-        --xml) xml=true;;
-        --verbose) verbose=true;;
-        --help) usage;;
-        -*) usage;;
-        *) if test -z "$option"; then
-             option="$larg"
-           else
-             usage
-           fi;;
-      esac
-    done
-  else
-    case $argfun in
-      set_*)
-        var=$(echo $argfun | sed 's,^set_,,')
-        eval $var=$arg
-        ;;
-      include_nixpath)
-        nixPath="-I $arg $nixPath"
-        ;;
-    esac
-    argfun=""
-  fi
-done
-
-if $verbose; then
-  set -x
-else
-  set +x
-fi
-
-#############################
-# Process the configuration #
-#############################
-
-evalNix(){
-  # disable `-e` flag, it's possible that the evaluation of `nix-instantiate` fails (e.g. due to broken pkgs)
-  set +e
-  result=$(nix-instantiate ${nixPath:+$nixPath} - --eval-only "$@" 2>&1)
-  exit_code=$?
-  set -e
-
-  if test $exit_code -eq 0; then
-      sed '/^warning: Nix search path/d' <<EOF
-$result
-EOF
-      return 0;
-  else
-      sed -n '
-  /^error/ { s/, at (string):[0-9]*:[0-9]*//; p; };
-  /^warning: Nix search path/ { p; };
-' >&2 <<EOF
-$result
-EOF
-    exit_code=1
-  fi
-}
-
-header="let
-  nixos = import <nixpkgs/nixos> {};
-  nixpkgs = import <nixpkgs> {};
-in with nixpkgs.lib;
-"
-
-# This function is used for converting the option definition path given by
-# the user into accessors for reaching the definition and the declaration
-# corresponding to this option.
-generateAccessors(){
-  if result=$(evalNix --strict --show-trace <<EOF
-$header
-
-let
-  path = "${option:+$option}";
-  pathList = splitString "." path;
-
-  walkOptions = attrsNames: result:
-    if attrsNames == [] then
-      result
-    else
-      let name = head attrsNames; rest = tail attrsNames; in
-      if isOption result.options then
-        walkOptions rest {
-          options = result.options.type.getSubOptions "";
-          opt = ''(\${result.opt}.type.getSubOptions "")'';
-          cfg = ''\${result.cfg}."\${name}"'';
-        }
-      else
-        walkOptions rest {
-          options = result.options.\${name};
-          opt = ''\${result.opt}."\${name}"'';
-          cfg = ''\${result.cfg}."\${name}"'';
-        }
-    ;
-
-  walkResult = (if path == "" then x: x else walkOptions pathList) {
-    options = nixos.options;
-    opt = ''nixos.options'';
-    cfg = ''nixos.config'';
-  };
-
-in
-  ''let option = \${walkResult.opt}; config = \${walkResult.cfg}; in''
-EOF
-)
-  then
-      echo $result
-  else
-      # In case of error we want to ignore the error message roduced by the
-      # script above, as it is iterating over each attribute, which does not
-      # produce a nice error message.  The following code is a fallback
-      # solution which is cause a nicer error message in the next
-      # evaluation.
-      echo "\"let option = nixos.options${option:+.$option}; config = nixos.config${option:+.$option}; in\""
-  fi
-}
-
-header="$header
-$(eval echo $(generateAccessors))
-"
-
-evalAttr(){
-  local prefix="$1"
-  local strict="$2"
-  local suffix="$3"
-
-  # If strict is set, then set it to "true".
-  test -n "$strict" && strict=true
-
-  evalNix ${strict:+--strict} <<EOF
-$header
-
-let
-  value = $prefix${suffix:+.$suffix};
-  strict = ${strict:-false};
-  cleanOutput = x: with nixpkgs.lib;
-    if isDerivation x then x.outPath
-    else if isFunction x then "<CODE>"
-    else if strict then
-      if isAttrs x then mapAttrs (n: cleanOutput) x
-      else if isList x then map cleanOutput x
-      else x
-    else x;
-in
-  cleanOutput value
-EOF
-}
-
-evalOpt(){
-  evalAttr "option" "" "$@"
-}
-
-evalCfg(){
-  local strict="$1"
-  evalAttr "config" "$strict"
-}
-
-findSources(){
-  local suffix=$1
-  evalNix --strict <<EOF
-$header
-
-option.$suffix
-EOF
-}
-
-# Given a result from nix-instantiate, recover the list of attributes it
-# contains.
-attrNames() {
-  local attributeset=$1
-  # sed is used to replace un-printable subset by 0s, and to remove most of
-  # the inner-attribute set, which reduce the likelyhood to encounter badly
-  # pre-processed input.
-  echo "builtins.attrNames $attributeset" | \
-    sed 's,<[A-Z]*>,0,g; :inner; s/{[^\{\}]*};/0;/g; t inner;' | \
-    evalNix --strict
-}
-
-# map a simple list which contains strings or paths.
-nixMap() {
-  local fun="$1"
-  local list="$2"
-  local elem
-  for elem in $list; do
-    test $elem = '[' -o $elem = ']' && continue;
-    $fun $elem
-  done
-}
-
-# This duplicates the work made below, but it is useful for processing
-# the output of nixos-option with other tools such as nixos-gui.
-if $xml; then
-  evalNix --xml --no-location <<EOF
-$header
-
-let
-  sources = builtins.map (f: f.source);
-  opt = option;
-  cfg = config;
-in
-
-with nixpkgs.lib;
-
-let
-  optStrict = v:
-    let
-      traverse = x :
-        if isAttrs x then
-          if x ? outPath then true
-          else all id (mapAttrsFlatten (n: traverseNoAttrs) x)
-        else traverseNoAttrs x;
-      traverseNoAttrs = x:
-        # do not continue in attribute sets
-        if isAttrs x then true
-        else if isList x then all id (map traverse x)
-        else true;
-    in assert traverse v; v;
-in
-
-if isOption opt then
-  optStrict ({}
-  // optionalAttrs (opt ? default) { inherit (opt) default; }
-  // optionalAttrs (opt ? example) { inherit (opt) example; }
-  // optionalAttrs (opt ? description) { inherit (opt) description; }
-  // optionalAttrs (opt ? type) { typename = opt.type.description; }
-  // optionalAttrs (opt ? options) { inherit (opt) options; }
-  // {
-    # to disambiguate the xml output.
-    _isOption = true;
-    declarations = sources opt.declarations;
-    definitions = sources opt.definitions;
-    value = cfg;
-  })
-else
-  opt
-EOF
-  exit $?
-fi
-
-if test "$(evalOpt "_type" 2> /dev/null)" = '"option"'; then
-  echo "Value:"
-  evalCfg 1
-
-  echo
-
-  echo "Default:"
-  if default=$(evalOpt "default" - 2> /dev/null); then
-    echo "$default"
-  else
-    echo "<None>"
-  fi
-  echo
-  if example=$(evalOpt "example" - 2> /dev/null); then
-    echo "Example:"
-    echo "$example"
-    echo
-  fi
-  echo "Description:"
-  echo
-  echo $(evalOpt "description")
-
-  echo $desc;
-
-  printPath () { echo "  $1"; }
-
-  echo "Declared by:"
-  nixMap printPath "$(findSources "declarations")"
-  echo
-  echo "Defined by:"
-  nixMap printPath "$(findSources "files")"
-  echo
-
-else
-  # echo 1>&2 "Warning: This value is not an option."
-
-  result=$(evalCfg "")
-  if [ ! -z "$result" ]; then
-    names=$(attrNames "$result" 2> /dev/null)
-    echo 1>&2 "This attribute set contains:"
-    escapeQuotes () { eval echo "$1"; }
-    nixMap escapeQuotes "$names"
-  else
-    echo 1>&2 "An error occurred while looking for attribute names. Are you sure that '$option' exists?"
-  fi
-fi
-
-exit $exit_code
diff --git a/nixos/modules/installer/tools/nixos-option/CMakeLists.txt b/nixos/modules/installer/tools/nixos-option/CMakeLists.txt
new file mode 100644
index 000000000000..e5834598c4fd
--- /dev/null
+++ b/nixos/modules/installer/tools/nixos-option/CMakeLists.txt
@@ -0,0 +1,8 @@
+cmake_minimum_required (VERSION 2.6)
+project (nixos-option)
+
+add_executable(nixos-option nixos-option.cc libnix-copy-paste.cc)
+target_link_libraries(nixos-option PRIVATE -lnixmain -lnixexpr -lnixstore -lnixutil)
+target_compile_features(nixos-option PRIVATE cxx_std_17)
+
+install (TARGETS nixos-option DESTINATION bin)
diff --git a/nixos/modules/installer/tools/nixos-option/default.nix b/nixos/modules/installer/tools/nixos-option/default.nix
new file mode 100644
index 000000000000..753fd92c7bbf
--- /dev/null
+++ b/nixos/modules/installer/tools/nixos-option/default.nix
@@ -0,0 +1,11 @@
+{lib, stdenv, boost, cmake, pkgconfig, nix, ... }:
+stdenv.mkDerivation rec {
+  name = "nixos-option";
+  src = ./.;
+  nativeBuildInputs = [ cmake pkgconfig ];
+  buildInputs = [ boost nix ];
+  meta = {
+    license = stdenv.lib.licenses.lgpl2Plus;
+    maintainers = with lib.maintainers; [ chkno ];
+  };
+}
diff --git a/nixos/modules/installer/tools/nixos-option/libnix-copy-paste.cc b/nixos/modules/installer/tools/nixos-option/libnix-copy-paste.cc
new file mode 100644
index 000000000000..875c07da6399
--- /dev/null
+++ b/nixos/modules/installer/tools/nixos-option/libnix-copy-paste.cc
@@ -0,0 +1,83 @@
+// These are useful methods inside the nix library that ought to be exported.
+// Since they are not, copy/paste them here.
+// TODO: Delete these and use the ones in the library as they become available.
+
+#include <nix/config.h> // for nix/globals.hh's reference to SYSTEM
+
+#include "libnix-copy-paste.hh"
+#include <boost/format/alt_sstream.hpp>           // for basic_altstringbuf...
+#include <boost/format/alt_sstream_impl.hpp>      // for basic_altstringbuf...
+#include <boost/format/format_class.hpp>          // for basic_format
+#include <boost/format/format_fwd.hpp>            // for format
+#include <boost/format/format_implementation.hpp> // for basic_format::basi...
+#include <boost/optional/optional.hpp>            // for get_pointer
+#include <iostream>                               // for operator<<, basic_...
+#include <nix/types.hh>                           // for Strings, Error
+#include <string>                                 // for string, basic_string
+
+using boost::format;
+using nix::Error;
+using nix::Strings;
+using std::string;
+
+// From nix/src/libexpr/attr-path.cc
+Strings parseAttrPath(const string & s)
+{
+    Strings res;
+    string cur;
+    string::const_iterator i = s.begin();
+    while (i != s.end()) {
+        if (*i == '.') {
+            res.push_back(cur);
+            cur.clear();
+        } else if (*i == '"') {
+            ++i;
+            while (1) {
+                if (i == s.end())
+                    throw Error(format("missing closing quote in selection path '%1%'") % s);
+                if (*i == '"')
+                    break;
+                cur.push_back(*i++);
+            }
+        } else
+            cur.push_back(*i);
+        ++i;
+    }
+    if (!cur.empty())
+        res.push_back(cur);
+    return res;
+}
+
+// From nix/src/nix/repl.cc
+bool isVarName(const string & s)
+{
+    if (s.size() == 0)
+        return false;
+    char c = s[0];
+    if ((c >= '0' && c <= '9') || c == '-' || c == '\'')
+        return false;
+    for (auto & i : s)
+        if (!((i >= 'a' && i <= 'z') || (i >= 'A' && i <= 'Z') || (i >= '0' && i <= '9') || i == '_' || i == '-' ||
+              i == '\''))
+            return false;
+    return true;
+}
+
+// From nix/src/nix/repl.cc
+std::ostream & printStringValue(std::ostream & str, const char * string)
+{
+    str << "\"";
+    for (const char * i = string; *i; i++)
+        if (*i == '\"' || *i == '\\')
+            str << "\\" << *i;
+        else if (*i == '\n')
+            str << "\\n";
+        else if (*i == '\r')
+            str << "\\r";
+        else if (*i == '\t')
+            str << "\\t";
+        else
+            str << *i;
+    str << "\"";
+    return str;
+}
diff --git a/nixos/modules/installer/tools/nixos-option/libnix-copy-paste.hh b/nixos/modules/installer/tools/nixos-option/libnix-copy-paste.hh
new file mode 100644
index 000000000000..2274e9a0f853
--- /dev/null
+++ b/nixos/modules/installer/tools/nixos-option/libnix-copy-paste.hh
@@ -0,0 +1,9 @@
+#pragma once
+
+#include <iostream>
+#include <nix/types.hh>
+#include <string>
+
+nix::Strings parseAttrPath(const std::string & s);
+bool isVarName(const std::string & s);
+std::ostream & printStringValue(std::ostream & str, const char * string);
diff --git a/nixos/modules/installer/tools/nixos-option/nixos-option.cc b/nixos/modules/installer/tools/nixos-option/nixos-option.cc
new file mode 100644
index 000000000000..9b92dc829cd1
--- /dev/null
+++ b/nixos/modules/installer/tools/nixos-option/nixos-option.cc
@@ -0,0 +1,618 @@
+#include <nix/config.h> // for nix/globals.hh's reference to SYSTEM
+
+#include <exception>               // for exception_ptr, current_exception
+#include <functional>              // for function
+#include <iostream>                // for operator<<, basic_ostream, ostrin...
+#include <iterator>                // for next
+#include <list>                    // for _List_iterator
+#include <memory>                  // for allocator, unique_ptr, make_unique
+#include <new>                     // for operator new
+#include <nix/args.hh>             // for argvToStrings, UsageError
+#include <nix/attr-path.hh>        // for findAlongAttrPath
+#include <nix/attr-set.hh>         // for Attr, Bindings, Bindings::iterator
+#include <nix/common-eval-args.hh> // for MixEvalArgs
+#include <nix/eval-inline.hh>      // for EvalState::forceValue
+#include <nix/eval.hh>             // for EvalState, initGC, operator<<
+#include <nix/globals.hh>          // for initPlugins, Settings, settings
+#include <nix/nixexpr.hh>          // for Pos
+#include <nix/shared.hh>           // for getArg, LegacyArgs, printVersion
+#include <nix/store-api.hh>        // for openStore
+#include <nix/symbol-table.hh>     // for Symbol, SymbolTable
+#include <nix/types.hh>            // for Error, Path, Strings, PathSet
+#include <nix/util.hh>             // for absPath, baseNameOf
+#include <nix/value.hh>            // for Value, Value::(anonymous), Value:...
+#include <string>                  // for string, operator+, operator==
+#include <utility>                 // for move
+#include <variant>                 // for get, holds_alternative, variant
+#include <vector>                  // for vector<>::iterator, vector
+
+#include "libnix-copy-paste.hh"
+
+using nix::absPath;
+using nix::Bindings;
+using nix::Error;
+using nix::EvalError;
+using nix::EvalState;
+using nix::Path;
+using nix::PathSet;
+using nix::Strings;
+using nix::Symbol;
+using nix::tAttrs;
+using nix::ThrownError;
+using nix::tLambda;
+using nix::tString;
+using nix::UsageError;
+using nix::Value;
+
+// An ostream wrapper to handle nested indentation
+class Out
+{
+  public:
+    class Separator
+    {};
+    const static Separator sep;
+    enum LinePolicy
+    {
+        ONE_LINE,
+        MULTI_LINE
+    };
+    explicit Out(std::ostream & ostream) : ostream(ostream), policy(ONE_LINE), writeSinceSep(true) {}
+    Out(Out & o, const std::string & start, const std::string & end, LinePolicy policy);
+    Out(Out & o, const std::string & start, const std::string & end, int count)
+        : Out(o, start, end, count < 2 ? ONE_LINE : MULTI_LINE)
+    {}
+    Out(const Out &) = delete;
+    Out(Out &&) = default;
+    Out & operator=(const Out &) = delete;
+    Out & operator=(Out &&) = delete;
+    ~Out() { ostream << end; }
+
+  private:
+    std::ostream & ostream;
+    std::string indentation;
+    std::string end;
+    LinePolicy policy;
+    bool writeSinceSep;
+    template <typename T> friend Out & operator<<(Out & o, T thing);
+};
+
+template <typename T> Out & operator<<(Out & o, T thing)
+{
+    if (!o.writeSinceSep && o.policy == Out::MULTI_LINE) {
+        o.ostream << o.indentation;
+    }
+    o.writeSinceSep = true;
+    o.ostream << thing;
+    return o;
+}
+
+template <> Out & operator<<<Out::Separator>(Out & o, Out::Separator /* thing */)
+{
+    o.ostream << (o.policy == Out::ONE_LINE ? " " : "\n");
+    o.writeSinceSep = false;
+    return o;
+}
+
+Out::Out(Out & o, const std::string & start, const std::string & end, LinePolicy policy)
+    : ostream(o.ostream), indentation(policy == ONE_LINE ? o.indentation : o.indentation + "  "),
+      end(policy == ONE_LINE ? end : o.indentation + end), policy(policy), writeSinceSep(true)
+{
+    o << start;
+    *this << Out::sep;
+}
+
+// Stuff needed for evaluation
+struct Context
+{
+    Context(EvalState & state, Bindings & autoArgs, Value optionsRoot, Value configRoot)
+        : state(state), autoArgs(autoArgs), optionsRoot(optionsRoot), configRoot(configRoot),
+          underscoreType(state.symbols.create("_type"))
+    {}
+    EvalState & state;
+    Bindings & autoArgs;
+    Value optionsRoot;
+    Value configRoot;
+    Symbol underscoreType;
+};
+
+Value evaluateValue(Context & ctx, Value & v)
+{
+    ctx.state.forceValue(v);
+    if (ctx.autoArgs.empty()) {
+        return v;
+    }
+    Value called{};
+    ctx.state.autoCallFunction(ctx.autoArgs, v, called);
+    return called;
+}
+
+bool isOption(Context & ctx, const Value & v)
+{
+    if (v.type != tAttrs) {
+        return false;
+    }
+    const auto & atualType = v.attrs->find(ctx.underscoreType);
+    if (atualType == v.attrs->end()) {
+        return false;
+    }
+    try {
+        Value evaluatedType = evaluateValue(ctx, *atualType->value);
+        if (evaluatedType.type != tString) {
+            return false;
+        }
+        return static_cast<std::string>(evaluatedType.string.s) == "option";
+    } catch (Error &) {
+        return false;
+    }
+}
+
+// Add quotes to a component of a path.
+// These are needed for paths like:
+//    fileSystems."/".fsType
+//    systemd.units."dbus.service".text
+std::string quoteAttribute(const std::string & attribute)
+{
+    if (isVarName(attribute)) {
+        return attribute;
+    }
+    std::ostringstream buf;
+    printStringValue(buf, attribute.c_str());
+    return buf.str();
+}
+
+const std::string appendPath(const std::string & prefix, const std::string & suffix)
+{
+    if (prefix.empty()) {
+        return quoteAttribute(suffix);
+    }
+    return prefix + "." + quoteAttribute(suffix);
+}
+
+bool forbiddenRecursionName(std::string name) { return (!name.empty() && name[0] == '_') || name == "haskellPackages"; }
+
+void recurse(const std::function<bool(const std::string & path, std::variant<Value, std::exception_ptr>)> & f,
+             Context & ctx, Value v, const std::string & path)
+{
+    std::variant<Value, std::exception_ptr> evaluated;
+    try {
+        evaluated = evaluateValue(ctx, v);
+    } catch (Error &) {
+        evaluated = std::current_exception();
+    }
+    if (!f(path, evaluated)) {
+        return;
+    }
+    if (std::holds_alternative<std::exception_ptr>(evaluated)) {
+        return;
+    }
+    const Value & evaluated_value = std::get<Value>(evaluated);
+    if (evaluated_value.type != tAttrs) {
+        return;
+    }
+    for (const auto & child : evaluated_value.attrs->lexicographicOrder()) {
+        if (forbiddenRecursionName(child->name)) {
+            continue;
+        }
+        recurse(f, ctx, *child->value, appendPath(path, child->name));
+    }
+}
+
+// Calls f on all the option names
+void mapOptions(const std::function<void(const std::string & path)> & f, Context & ctx, Value root)
+{
+    recurse(
+        [f, &ctx](const std::string & path, std::variant<Value, std::exception_ptr> v) {
+            bool isOpt = std::holds_alternative<std::exception_ptr>(v) || isOption(ctx, std::get<Value>(v));
+            if (isOpt) {
+                f(path);
+            }
+            return !isOpt;
+        },
+        ctx, root, "");
+}
+
+// Calls f on all the config values inside one option.
+// Simple options have one config value inside, like sound.enable = true.
+// Compound options have multiple config values.  For example, the option
+// "users.users" has about 1000 config values inside it:
+//   users.users.avahi.createHome = false;
+//   users.users.avahi.cryptHomeLuks = null;
+//   users.users.avahi.description = "`avahi-daemon' privilege separation user";
+//   ...
+//   users.users.avahi.openssh.authorizedKeys.keyFiles = [ ];
+//   users.users.avahi.openssh.authorizedKeys.keys = [ ];
+//   ...
+//   users.users.avahi.uid = 10;
+//   users.users.avahi.useDefaultShell = false;
+//   users.users.cups.createHome = false;
+//   ...
+//   users.users.cups.useDefaultShell = false;
+//   users.users.gdm = ... ... ...
+//   users.users.messagebus = ... .. ...
+//   users.users.nixbld1 = ... .. ...
+//   ...
+//   users.users.systemd-timesync = ... .. ...
+void mapConfigValuesInOption(
+    const std::function<void(const std::string & path, std::variant<Value, std::exception_ptr> v)> & f,
+    const std::string & path, Context & ctx)
+{
+    Value * option;
+    try {
+        option = findAlongAttrPath(ctx.state, path, ctx.autoArgs, ctx.configRoot);
+    } catch (Error &) {
+        f(path, std::current_exception());
+        return;
+    }
+    recurse(
+        [f, ctx](const std::string & path, std::variant<Value, std::exception_ptr> v) {
+            bool leaf = std::holds_alternative<std::exception_ptr>(v) || std::get<Value>(v).type != tAttrs ||
+                        ctx.state.isDerivation(std::get<Value>(v));
+            if (!leaf) {
+                return true; // Keep digging
+            }
+            f(path, v);
+            return false;
+        },
+        ctx, *option, path);
+}
+
+std::string describeError(const Error & e) { return "«error: " + e.msg() + "»"; }
+
+void describeDerivation(Context & ctx, Out & out, Value v)
+{
+    // Copy-pasted from nix/src/nix/repl.cc  :(
+    Bindings::iterator i = v.attrs->find(ctx.state.sDrvPath);
+    PathSet pathset;
+    try {
+        Path drvPath = i != v.attrs->end() ? ctx.state.coerceToPath(*i->pos, *i->value, pathset) : "???";
+        out << "«derivation " << drvPath << "»";
+    } catch (Error & e) {
+        out << describeError(e);
+    }
+}
+
+Value parseAndEval(EvalState & state, const std::string & expression, const std::string & path)
+{
+    Value v{};
+    state.eval(state.parseExprFromString(expression, absPath(path)), v);
+    return v;
+}
+
+void printValue(Context & ctx, Out & out, std::variant<Value, std::exception_ptr> maybeValue, const std::string & path);
+
+void printList(Context & ctx, Out & out, Value & v)
+{
+    Out listOut(out, "[", "]", v.listSize());
+    for (unsigned int n = 0; n < v.listSize(); ++n) {
+        printValue(ctx, listOut, *v.listElems()[n], "");
+        listOut << Out::sep;
+    }
+}
+
+void printAttrs(Context & ctx, Out & out, Value & v, const std::string & path)
+{
+    Out attrsOut(out, "{", "}", v.attrs->size());
+    for (const auto & a : v.attrs->lexicographicOrder()) {
+        std::string name = a->name;
+        attrsOut << name << " = ";
+        printValue(ctx, attrsOut, *a->value, appendPath(path, name));
+        attrsOut << ";" << Out::sep;
+    }
+}
+
+void multiLineStringEscape(Out & out, const std::string & s)
+{
+    int i;
+    for (i = 1; i < s.size(); i++) {
+        if (s[i - 1] == '$' && s[i] == '{') {
+            out << "''${";
+            i++;
+        } else if (s[i - 1] == '\'' && s[i] == '\'') {
+            out << "'''";
+            i++;
+        } else {
+            out << s[i - 1];
+        }
+    }
+    if (i == s.size()) {
+        out << s[i - 1];
+    }
+}
+
+void printMultiLineString(Out & out, const Value & v)
+{
+    std::string s = v.string.s;
+    Out strOut(out, "''", "''", Out::MULTI_LINE);
+    std::string::size_type begin = 0;
+    while (begin < s.size()) {
+        std::string::size_type end = s.find('\n', begin);
+        if (end == std::string::npos) {
+            multiLineStringEscape(strOut, s.substr(begin, s.size() - begin));
+            break;
+        }
+        multiLineStringEscape(strOut, s.substr(begin, end - begin));
+        strOut << Out::sep;
+        begin = end + 1;
+    }
+}
+
+void printValue(Context & ctx, Out & out, std::variant<Value, std::exception_ptr> maybeValue, const std::string & path)
+{
+    try {
+        if (auto ex = std::get_if<std::exception_ptr>(&maybeValue)) {
+            std::rethrow_exception(*ex);
+        }
+        Value v = evaluateValue(ctx, std::get<Value>(maybeValue));
+        if (ctx.state.isDerivation(v)) {
+            describeDerivation(ctx, out, v);
+        } else if (v.isList()) {
+            printList(ctx, out, v);
+        } else if (v.type == tAttrs) {
+            printAttrs(ctx, out, v, path);
+        } else if (v.type == tString && std::string(v.string.s).find('\n') != std::string::npos) {
+            printMultiLineString(out, v);
+        } else {
+            ctx.state.forceValueDeep(v);
+            out << v;
+        }
+    } catch (ThrownError & e) {
+        if (e.msg() == "The option `" + path + "' is used but not defined.") {
+            // 93% of errors are this, and just letting this message through would be
+            // misleading.  These values may or may not actually be "used" in the
+            // config.  The thing throwing the error message assumes that if anything
+            // ever looks at this value, it is a "use" of this value.  But here in
+            // nixos-option, we are looking at this value only to print it.
+            // In order to avoid implying that this undefined value is actually
+            // referenced, eat the underlying error message and emit "«not defined»".
+            out << "«not defined»";
+        } else {
+            out << describeError(e);
+        }
+    } catch (Error & e) {
+        out << describeError(e);
+    }
+}
+
+void printConfigValue(Context & ctx, Out & out, const std::string & path, std::variant<Value, std::exception_ptr> v)
+{
+    out << path << " = ";
+    printValue(ctx, out, std::move(v), path);
+    out << ";\n";
+}
+
+void printAll(Context & ctx, Out & out)
+{
+    mapOptions(
+        [&ctx, &out](const std::string & optionPath) {
+            mapConfigValuesInOption(
+                [&ctx, &out](const std::string & configPath, std::variant<Value, std::exception_ptr> v) {
+                    printConfigValue(ctx, out, configPath, v);
+                },
+                optionPath, ctx);
+        },
+        ctx, ctx.optionsRoot);
+}
+
+void printAttr(Context & ctx, Out & out, const std::string & path, Value & root)
+{
+    try {
+        printValue(ctx, out, *findAlongAttrPath(ctx.state, path, ctx.autoArgs, root), path);
+    } catch (Error & e) {
+        out << describeError(e);
+    }
+}
+
+bool hasExample(Context & ctx, Value & option)
+{
+    try {
+        findAlongAttrPath(ctx.state, "example", ctx.autoArgs, option);
+        return true;
+    } catch (Error &) {
+        return false;
+    }
+}
+
+void printOption(Context & ctx, Out & out, const std::string & path, Value & option)
+{
+    out << "Value:\n";
+    printAttr(ctx, out, path, ctx.configRoot);
+
+    out << "\n\nDefault:\n";
+    printAttr(ctx, out, "default", option);
+
+    out << "\n\nType:\n";
+    printAttr(ctx, out, "type.description", option);
+
+    if (hasExample(ctx, option)) {
+        out << "\n\nExample:\n";
+        printAttr(ctx, out, "example", option);
+    }
+
+    out << "\n\nDescription:\n";
+    printAttr(ctx, out, "description", option);
+
+    out << "\n\nDeclared by:\n";
+    printAttr(ctx, out, "declarations", option);
+
+    out << "\n\nDefined by:\n";
+    printAttr(ctx, out, "files", option);
+    out << "\n";
+}
+
+void printListing(Out & out, Value & v)
+{
+    out << "This attribute set contains:\n";
+    for (const auto & a : v.attrs->lexicographicOrder()) {
+        std::string name = a->name;
+        if (!name.empty() && name[0] != '_') {
+            out << name << "\n";
+        }
+    }
+}
+
+bool optionTypeIs(Context & ctx, Value & v, const std::string & soughtType)
+{
+    try {
+        const auto & typeLookup = v.attrs->find(ctx.state.sType);
+        if (typeLookup == v.attrs->end()) {
+            return false;
+        }
+        Value type = evaluateValue(ctx, *typeLookup->value);
+        if (type.type != tAttrs) {
+            return false;
+        }
+        const auto & nameLookup = type.attrs->find(ctx.state.sName);
+        if (nameLookup == type.attrs->end()) {
+            return false;
+        }
+        Value name = evaluateValue(ctx, *nameLookup->value);
+        if (name.type != tString) {
+            return false;
+        }
+        return name.string.s == soughtType;
+    } catch (Error &) {
+        return false;
+    }
+}
+
+bool isAggregateOptionType(Context & ctx, Value & v)
+{
+    return optionTypeIs(ctx, v, "attrsOf") || optionTypeIs(ctx, v, "listOf") || optionTypeIs(ctx, v, "loaOf");
+}
+
+MakeError(OptionPathError, EvalError);
+
+Value getSubOptions(Context & ctx, Value & option)
+{
+    Value getSubOptions = evaluateValue(ctx, *findAlongAttrPath(ctx.state, "type.getSubOptions", ctx.autoArgs, option));
+    if (getSubOptions.type != tLambda) {
+        throw OptionPathError("Option's type.getSubOptions isn't a function");
+    }
+    Value emptyString{};
+    nix::mkString(emptyString, "");
+    Value v;
+    ctx.state.callFunction(getSubOptions, emptyString, v, nix::Pos{});
+    return v;
+}
+
+// Carefully walk an option path, looking for sub-options when a path walks past
+// an option value.
+Value findAlongOptionPath(Context & ctx, const std::string & path)
+{
+    Strings tokens = parseAttrPath(path);
+    Value v = ctx.optionsRoot;
+    for (auto i = tokens.begin(); i != tokens.end(); i++) {
+        const auto & attr = *i;
+        try {
+            bool lastAttribute = std::next(i) == tokens.end();
+            v = evaluateValue(ctx, v);
+            if (attr.empty()) {
+                throw OptionPathError("empty attribute name");
+            }
+            if (isOption(ctx, v) && optionTypeIs(ctx, v, "submodule")) {
+                v = getSubOptions(ctx, v);
+            }
+            if (isOption(ctx, v) && isAggregateOptionType(ctx, v) && !lastAttribute) {
+                v = getSubOptions(ctx, v);
+                // Note that we've consumed attr, but didn't actually use it.  This is the path component that's looked
+                // up in the list or attribute set that doesn't name an option -- the "root" in "users.users.root.name".
+            } else if (v.type != tAttrs) {
+                throw OptionPathError("Value is %s while a set was expected", showType(v));
+            } else {
+                const auto & next = v.attrs->find(ctx.state.symbols.create(attr));
+                if (next == v.attrs->end()) {
+                    throw OptionPathError("Attribute not found", attr, path);
+                }
+                v = *next->value;
+            }
+        } catch (OptionPathError & e) {
+            throw OptionPathError("At '%s' in path '%s': %s", attr, path, e.msg());
+        }
+    }
+    return v;
+}
+
+void printOne(Context & ctx, Out & out, const std::string & path)
+{
+    try {
+        Value option = findAlongOptionPath(ctx, path);
+        option = evaluateValue(ctx, option);
+        if (isOption(ctx, option)) {
+            printOption(ctx, out, path, option);
+        } else {
+            printListing(out, option);
+        }
+    } catch (Error & e) {
+        std::cerr << "error: " << e.msg()
+                  << "\nAn error occurred while looking for attribute names. Are "
+                     "you sure that '"
+                  << path << "' exists?\n";
+    }
+}
+
+int main(int argc, char ** argv)
+{
+    bool all = false;
+    std::string path = ".";
+    std::string optionsExpr = "(import <nixpkgs/nixos> {}).options";
+    std::string configExpr = "(import <nixpkgs/nixos> {}).config";
+    std::vector<std::string> args;
+
+    struct MyArgs : nix::LegacyArgs, nix::MixEvalArgs
+    {
+        using nix::LegacyArgs::LegacyArgs;
+    };
+
+    MyArgs myArgs(nix::baseNameOf(argv[0]), [&](Strings::iterator & arg, const Strings::iterator & end) {
+        if (*arg == "--help") {
+            nix::showManPage("nixos-option");
+        } else if (*arg == "--version") {
+            nix::printVersion("nixos-option");
+        } else if (*arg == "--all") {
+            all = true;
+        } else if (*arg == "--path") {
+            path = nix::getArg(*arg, arg, end);
+        } else if (*arg == "--options_expr") {
+            optionsExpr = nix::getArg(*arg, arg, end);
+        } else if (*arg == "--config_expr") {
+            configExpr = nix::getArg(*arg, arg, end);
+        } else if (!arg->empty() && arg->at(0) == '-') {
+            return false;
+        } else {
+            args.push_back(*arg);
+        }
+        return true;
+    });
+
+    myArgs.parseCmdline(nix::argvToStrings(argc, argv));
+
+    nix::initPlugins();
+    nix::initGC();
+    nix::settings.readOnlyMode = true;
+    auto store = nix::openStore();
+    auto state = std::make_unique<EvalState>(myArgs.searchPath, store);
+
+    Value optionsRoot = parseAndEval(*state, optionsExpr, path);
+    Value configRoot = parseAndEval(*state, configExpr, path);
+
+    Context ctx{*state, *myArgs.getAutoArgs(*state), optionsRoot, configRoot};
+    Out out(std::cout);
+
+    if (all) {
+        if (!args.empty()) {
+            throw UsageError("--all cannot be used with arguments");
+        }
+        printAll(ctx, out);
+    } else {
+        if (args.empty()) {
+            printOne(ctx, out, "");
+        }
+        for (const auto & arg : args) {
+            printOne(ctx, out, arg);
+        }
+    }
+
+    ctx.state.printStats();
+
+    return 0;
+}
diff --git a/nixos/modules/installer/tools/nixos-rebuild.sh b/nixos/modules/installer/tools/nixos-rebuild.sh
index 891f374df536..c53dc1000c4a 100644
--- a/nixos/modules/installer/tools/nixos-rebuild.sh
+++ b/nixos/modules/installer/tools/nixos-rebuild.sh
@@ -90,6 +90,11 @@ while [ "$#" -gt 0 ]; do
         targetHost="$1"
         shift 1
         ;;
+      --use-remote-sudo)
+        # note the trailing space
+        maybeSudo="sudo "
+        shift 1
+        ;;
       *)
         echo "$0: unknown option \`$i'"
         exit 1
@@ -97,10 +102,6 @@ while [ "$#" -gt 0 ]; do
     esac
 done
 
-if [ -n "$SUDO_USER" ]; then
-    maybeSudo="sudo "
-fi
-
 if [ -z "$buildHost" -a -n "$targetHost" ]; then
     buildHost="$targetHost"
 fi
diff --git a/nixos/modules/installer/tools/tools.nix b/nixos/modules/installer/tools/tools.nix
index 9e6eead3c4d7..e4db39b5c810 100644
--- a/nixos/modules/installer/tools/tools.nix
+++ b/nixos/modules/installer/tools/tools.nix
@@ -41,10 +41,7 @@ let
     inherit (config.system.nixos-generate-config) configuration;
   };
 
-  nixos-option = makeProg {
-    name = "nixos-option";
-    src = ./nixos-option.sh;
-  };
+  nixos-option = pkgs.callPackage ./nixos-option { };
 
   nixos-version = makeProg {
     name = "nixos-version";
@@ -123,7 +120,7 @@ in
         # programs.gnupg.agent = {
         #   enable = true;
         #   enableSSHSupport = true;
-        #   flavour = "gnome3";
+        #   pinentryFlavor = "gnome3";
         # };
 
         # List services that you want to enable:
diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix
index 3e8a5b07a5ed..a4db2c9d1d87 100644
--- a/nixos/modules/misc/ids.nix
+++ b/nixos/modules/misc/ids.nix
@@ -128,7 +128,7 @@
       tcpcryptd = 93; # tcpcryptd uses a hard-coded uid. We patch it in Nixpkgs to match this choice.
       firebird = 95;
       #keys = 96; # unused
-      haproxy = 97;
+      #haproxy = 97; # DynamicUser as of 2019-11-08
       mongodb = 98;
       openldap = 99;
       #users = 100; # unused
@@ -443,7 +443,7 @@
       #tcpcryptd = 93; # unused
       firebird = 95;
       keys = 96;
-      haproxy = 97;
+      #haproxy = 97; # DynamicUser as of 2019-11-08
       #mongodb = 98; # unused
       openldap = 99;
       munin = 102;
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index d3739ae0960f..f7c66166c5c5 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -1,9 +1,7 @@
 [
   ./config/debug-info.nix
-  ./config/fonts/corefonts.nix
   ./config/fonts/fontconfig.nix
   ./config/fonts/fontconfig-penultimate.nix
-  ./config/fonts/fontconfig-ultimate.nix
   ./config/fonts/fontdir.nix
   ./config/fonts/fonts.nix
   ./config/fonts/ghostscript.nix
@@ -44,6 +42,7 @@
   ./hardware/all-firmware.nix
   ./hardware/bladeRF.nix
   ./hardware/brightnessctl.nix
+  ./hardware/brillo.nix
   ./hardware/ckb-next.nix
   ./hardware/cpu/amd-microcode.nix
   ./hardware/cpu/intel-microcode.nix
@@ -227,6 +226,7 @@
   ./services/backup/rsnapshot.nix
   ./services/backup/tarsnap.nix
   ./services/backup/tsm.nix
+  ./services/backup/zfs-replication.nix
   ./services/backup/znapzend.nix
   ./services/cluster/hadoop/default.nix
   ./services/cluster/kubernetes/addons/dns.nix
@@ -549,6 +549,8 @@
   ./services/network-filesystems/nfsd.nix
   ./services/network-filesystems/openafs/client.nix
   ./services/network-filesystems/openafs/server.nix
+  ./services/network-filesystems/orangefs/server.nix
+  ./services/network-filesystems/orangefs/client.nix
   ./services/network-filesystems/rsyncd.nix
   ./services/network-filesystems/samba.nix
   ./services/network-filesystems/tahoe.nix
@@ -602,6 +604,7 @@
   ./services/networking/gdomap.nix
   ./services/networking/git-daemon.nix
   ./services/networking/gnunet.nix
+  ./services/networking/go-shadowsocks2.nix
   ./services/networking/gogoclient.nix
   ./services/networking/gvpe.nix
   ./services/networking/hans.nix
@@ -615,7 +618,6 @@
   ./services/networking/iodine.nix
   ./services/networking/iperf3.nix
   ./services/networking/ircd-hybrid/default.nix
-  ./services/networking/jormungandr.nix
   ./services/networking/iwd.nix
   ./services/networking/keepalived/default.nix
   ./services/networking/keybase.nix
@@ -714,6 +716,7 @@
   ./services/networking/tinc.nix
   ./services/networking/tinydns.nix
   ./services/networking/tftpd.nix
+  ./services/networking/trickster.nix
   ./services/networking/tox-bootstrapd.nix
   ./services/networking/tox-node.nix
   ./services/networking/toxvpn.nix
@@ -807,8 +810,10 @@
   ./services/web-apps/nexus.nix
   ./services/web-apps/pgpkeyserver-lite.nix
   ./services/web-apps/matomo.nix
+  ./services/web-apps/moinmoin.nix
   ./services/web-apps/restya-board.nix
   ./services/web-apps/tt-rss.nix
+  ./services/web-apps/trac.nix
   ./services/web-apps/selfoss.nix
   ./services/web-apps/shiori.nix
   ./services/web-apps/virtlyst.nix
@@ -859,6 +864,7 @@
   ./services/x11/hardware/multitouch.nix
   ./services/x11/hardware/synaptics.nix
   ./services/x11/hardware/wacom.nix
+  ./services/x11/hardware/digimend.nix
   ./services/x11/hardware/cmt.nix
   ./services/x11/gdk-pixbuf.nix
   ./services/x11/redshift.nix
diff --git a/nixos/modules/programs/gnupg.nix b/nixos/modules/programs/gnupg.nix
index 4fb7c43c8b2e..2d262d906579 100644
--- a/nixos/modules/programs/gnupg.nix
+++ b/nixos/modules/programs/gnupg.nix
@@ -14,7 +14,7 @@ let
       "qt"
     else if xserverCfg.desktopManager.xfce.enable then
       "gtk2"
-    else if xserverCfg.enable then
+    else if xserverCfg.enable || config.programs.sway.enable then
       "gnome3"
     else
       null;
@@ -121,6 +121,8 @@ in
       wantedBy = [ "sockets.target" ];
     };
 
+    services.dbus.packages = mkIf (cfg.agent.pinentryFlavor == "gnome3") [ pkgs.gcr ];
+
     environment.systemPackages = with pkgs; [ cfg.package ];
     systemd.packages = [ cfg.package ];
 
diff --git a/nixos/modules/programs/shadow.nix b/nixos/modules/programs/shadow.nix
index 8ec4169207db..7eaf79d864e7 100644
--- a/nixos/modules/programs/shadow.nix
+++ b/nixos/modules/programs/shadow.nix
@@ -6,17 +6,27 @@ with lib;
 
 let
 
+  /*
+  There are three different sources for user/group id ranges, each of which gets
+  used by different programs:
+  - The login.defs file, used by the useradd, groupadd and newusers commands
+  - The update-users-groups.pl file, used by NixOS in the activation phase to
+    decide on which ids to use for declaratively defined users without a static
+    id
+  - Systemd compile time options -Dsystem-uid-max= and -Dsystem-gid-max=, used
+    by systemd for features like ConditionUser=@system and systemd-sysusers
+  */
   loginDefs =
     ''
       DEFAULT_HOME yes
 
       SYS_UID_MIN  400
-      SYS_UID_MAX  499
+      SYS_UID_MAX  999
       UID_MIN      1000
       UID_MAX      29999
 
       SYS_GID_MIN  400
-      SYS_GID_MAX  499
+      SYS_GID_MAX  999
       GID_MIN      1000
       GID_MAX      29999
 
diff --git a/nixos/modules/programs/ssh.nix b/nixos/modules/programs/ssh.nix
index 733b8f7636fd..80198990ed11 100644
--- a/nixos/modules/programs/ssh.nix
+++ b/nixos/modules/programs/ssh.nix
@@ -115,6 +115,16 @@ in
         '';
       };
 
+      agentPKCS11Whitelist = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "\${pkgs.opensc}/lib/opensc-pkcs11.so";
+        description = ''
+          A pattern-list of acceptable paths for PKCS#11 shared libraries
+          that may be used with the -s option to ssh-add.
+        '';
+      };
+
       package = mkOption {
         type = types.package;
         default = pkgs.openssh;
@@ -241,6 +251,7 @@ in
             ExecStart =
                 "${cfg.package}/bin/ssh-agent " +
                 optionalString (cfg.agentTimeout != null) ("-t ${cfg.agentTimeout} ") +
+                optionalString (cfg.agentPKCS11Whitelist != null) ("-P ${cfg.agentPKCS11Whitelist} ") +
                 "-a %t/ssh-agent";
             StandardOutput = "null";
             Type = "forking";
diff --git a/nixos/modules/programs/x2goserver.nix b/nixos/modules/programs/x2goserver.nix
index 77a1a0da7993..7d74231e956b 100644
--- a/nixos/modules/programs/x2goserver.nix
+++ b/nixos/modules/programs/x2goserver.nix
@@ -69,6 +69,7 @@ in {
     users.users.x2go = {
       home = "/var/lib/x2go/db";
       group = "x2go";
+      isSystemUser = true;
     };
 
     security.wrappers.x2gosqliteWrapper = {
diff --git a/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix b/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix
index 7184e5d9b9a8..c84d26a7921e 100644
--- a/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix
+++ b/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix
@@ -81,7 +81,7 @@ in
     ];
 
     programs.zsh.interactiveShellInit = with pkgs;
-      lib.concatStringsSep "\n" ([
+      lib.mkAfter (lib.concatStringsSep "\n" ([
         "source ${zsh-syntax-highlighting}/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh"
       ] ++ optional (length(cfg.highlighters) > 0)
         "ZSH_HIGHLIGHT_HIGHLIGHTERS=(${concatStringsSep " " cfg.highlighters})"
@@ -95,6 +95,6 @@ in
             styles: design:
             "ZSH_HIGHLIGHT_STYLES[${styles}]='${design}'"
           ) cfg.styles)
-      );
+      ));
   };
 }
diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix
index df8ebe505846..7d8cf55b827a 100644
--- a/nixos/modules/rename.nix
+++ b/nixos/modules/rename.nix
@@ -135,7 +135,8 @@ with lib;
     # piwik was renamed to matomo
     (mkRenamedOptionModule [ "services" "piwik" "enable" ] [ "services" "matomo" "enable" ])
     (mkRenamedOptionModule [ "services" "piwik" "webServerUser" ] [ "services" "matomo" "webServerUser" ])
-    (mkRenamedOptionModule [ "services" "piwik" "phpfpmProcessManagerConfig" ] [ "services" "matomo" "phpfpmProcessManagerConfig" ])
+    (mkRemovedOptionModule [ "services" "piwik" "phpfpmProcessManagerConfig" ] "Use services.phpfpm.pools.<name>.settings")
+    (mkRemovedOptionModule [ "services" "matomo" "phpfpmProcessManagerConfig" ] "Use services.phpfpm.pools.<name>.settings")
     (mkRenamedOptionModule [ "services" "piwik" "nginx" ] [ "services" "matomo" "nginx" ])
 
     # tarsnap
@@ -233,6 +234,7 @@ with lib;
     (mkRemovedOptionModule [ "services" "mysql" "rootPassword" ] "Use socket authentication or set the password outside of the nix store.")
     (mkRemovedOptionModule [ "services" "zabbixServer" "dbPassword" ] "Use services.zabbixServer.database.passwordFile instead.")
     (mkRemovedOptionModule [ "systemd" "generator-packages" ] "Use systemd.packages instead.")
+    (mkRemovedOptionModule [ "fonts" "enableCoreFonts" ] "Use fonts.fonts = [ pkgs.corefonts ]; instead.")
 
     # ZSH
     (mkRenamedOptionModule [ "programs" "zsh" "enableSyntaxHighlighting" ] [ "programs" "zsh" "syntaxHighlighting" "enable" ])
@@ -290,5 +292,14 @@ with lib;
        (opt: mkRemovedOptionModule [ "services" "prometheus" "${opt}" ] ''
          The prometheus exporters are now configured using `services.prometheus.exporters'.
          See the 18.03 release notes for more information.
+       '' ))
+
+    ++ (forEach [ "enable" "substitutions" "preset" ]
+       (opt: mkRemovedOptionModule [ "fonts" "fontconfig" "ultimate" "${opt}" ] ''
+         The fonts.fontconfig.ultimate module and configuration is obsolete.
+         The repository has since been archived and activity has ceased.
+         https://github.com/bohoomil/fontconfig-ultimate/issues/171.
+         No action should be needed for font configuration, as the fonts.fontconfig
+         module is already used by default.
        '' ));
 }
diff --git a/nixos/modules/security/acme.nix b/nixos/modules/security/acme.nix
index e08c7e965eef..d14613f22b05 100644
--- a/nixos/modules/security/acme.nix
+++ b/nixos/modules/security/acme.nix
@@ -20,6 +20,16 @@ let
         '';
       };
 
+      server = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          ACME Directory Resource URI. Defaults to let's encrypt
+          production endpoint,
+          https://acme-v02.api.letsencrypt.org/directory, if unset.
+        '';
+      };
+
       domain = mkOption {
         type = types.str;
         default = name;
@@ -109,7 +119,15 @@ in
 {
 
   ###### interface
-
+  imports = [
+    (mkRemovedOptionModule [ "security" "acme" "production" ] ''
+      Use security.acme.server to define your staging ACME server URL instead.
+
+      To use the let's encrypt staging server, use security.acme.server =
+      "https://acme-staging-v02.api.letsencrypt.org/directory".
+    ''
+    )
+  ];
   options = {
     security.acme = {
 
@@ -129,6 +147,16 @@ in
         '';
       };
 
+      server = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          ACME Directory Resource URI. Defaults to let's encrypt
+          production endpoint,
+          <literal>https://acme-v02.api.letsencrypt.org/directory</literal>, if unset.
+        '';
+      };
+
       preliminarySelfsigned = mkOption {
         type = types.bool;
         default = true;
@@ -142,20 +170,6 @@ in
         '';
       };
 
-      production = mkOption {
-        type = types.bool;
-        default = true;
-        description = ''
-          If set to true, use Let's Encrypt's production environment
-          instead of the staging environment. The main benefit of the
-          staging environment is to get much higher rate limits.
-
-          See
-          <literal>https://letsencrypt.org/docs/staging-environment</literal>
-          for more detail.
-        '';
-      };
-
       certs = mkOption {
         default = { };
         type = with types; attrsOf (submodule certOpts);
@@ -198,7 +212,7 @@ in
                           ++ optionals (data.email != null) [ "--email" data.email ]
                           ++ concatMap (p: [ "-f" p ]) data.plugins
                           ++ concatLists (mapAttrsToList (name: root: [ "-d" (if root == null then name else "${name}:${root}")]) data.extraDomains)
-                          ++ optionals (!cfg.production) ["--server" "https://acme-staging.api.letsencrypt.org/directory"];
+                          ++ optionals (cfg.server != null || data.server != null) ["--server" (if data.server == null then cfg.server else data.server)];
                 acmeService = {
                   description = "Renew ACME Certificate for ${cert}";
                   after = [ "network.target" "network-online.target" ];
diff --git a/nixos/modules/security/pam_mount.nix b/nixos/modules/security/pam_mount.nix
index 8b131c54a2a5..75f58462d13d 100644
--- a/nixos/modules/security/pam_mount.nix
+++ b/nixos/modules/security/pam_mount.nix
@@ -50,9 +50,6 @@ in
           <pam_mount>
           <debug enable="0" />
 
-          ${concatStrings (map userVolumeEntry (attrValues extraUserVolumes))}
-          ${concatStringsSep "\n" cfg.extraVolumes}
-
           <!-- if activated, requires ofl from hxtools to be present -->
           <logout wait="0" hup="no" term="no" kill="no" />
           <!-- set PATH variable for pam_mount module -->
@@ -64,6 +61,9 @@ in
           <cryptmount>${pkgs.pam_mount}/bin/mount.crypt %(VOLUME) %(MNTPT)</cryptmount>
           <cryptumount>${pkgs.pam_mount}/bin/umount.crypt %(MNTPT)</cryptumount>
           <pmvarrun>${pkgs.pam_mount}/bin/pmvarrun -u %(USER) -o %(OPERATION)</pmvarrun>
+
+          ${concatStrings (map userVolumeEntry (attrValues extraUserVolumes))}
+          ${concatStringsSep "\n" cfg.extraVolumes}
           </pam_mount>
           '';
     }];
diff --git a/nixos/modules/services/admin/oxidized.nix b/nixos/modules/services/admin/oxidized.nix
index 39112c3970d5..da81be3f23e8 100644
--- a/nixos/modules/services/admin/oxidized.nix
+++ b/nixos/modules/services/admin/oxidized.nix
@@ -89,6 +89,7 @@ in
       group = cfg.group;
       home = cfg.dataDir;
       createHome = true;
+      isSystemUser = true;
     };
 
     systemd.services.oxidized = {
diff --git a/nixos/modules/services/audio/jack.nix b/nixos/modules/services/audio/jack.nix
index aa3351f401af..ceff366d0bbb 100644
--- a/nixos/modules/services/audio/jack.nix
+++ b/nixos/modules/services/audio/jack.nix
@@ -223,6 +223,7 @@ in {
         group = "jackaudio";
         extraGroups = [ "audio" ];
         description = "JACK Audio system service user";
+        isSystemUser = true;
       };
       # http://jackaudio.org/faq/linux_rt_config.html
       security.pam.loginLimits = [
diff --git a/nixos/modules/services/audio/mpd.nix b/nixos/modules/services/audio/mpd.nix
index 0df8f9688d25..56dc858b6405 100644
--- a/nixos/modules/services/audio/mpd.nix
+++ b/nixos/modules/services/audio/mpd.nix
@@ -181,6 +181,7 @@ in {
         ProtectKernelModules = true;
         RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX AF_NETLINK";
         RestrictNamespaces = true;
+        Restart = "always";
       };
     };
 
diff --git a/nixos/modules/services/backup/automysqlbackup.nix b/nixos/modules/services/backup/automysqlbackup.nix
index 1884f3536a97..e3a8d1f79934 100644
--- a/nixos/modules/services/backup/automysqlbackup.nix
+++ b/nixos/modules/services/backup/automysqlbackup.nix
@@ -99,7 +99,10 @@ in
 
     environment.systemPackages = [ pkg ];
 
-    users.users.${user}.group = group;
+    users.users.${user} = {
+      group = group;
+      isSystemUser = true;
+    };
     users.groups.${group} = { };
 
     systemd.tmpfiles.rules = [
diff --git a/nixos/modules/services/backup/znapzend.nix b/nixos/modules/services/backup/znapzend.nix
index f317078ddda2..203631a577f0 100644
--- a/nixos/modules/services/backup/znapzend.nix
+++ b/nixos/modules/services/backup/znapzend.nix
@@ -34,6 +34,8 @@ let
     description = "string of the form number{b|k|M|G}";
   };
 
+  enabledFeatures = concatLists (mapAttrsToList (name: enabled: optional enabled name) cfg.features);
+
   # Type for a string that must contain certain other strings (the list parameter).
   # Note that these would need regex escaping.
   stringContainingStrings = list: let
@@ -354,6 +356,22 @@ in
         '';
         default = false;
       };
+
+      features.recvu = mkEnableOption ''
+        recvu feature which uses <literal>-u</literal> on the receiving end to keep the destination
+        filesystem unmounted.
+      '';
+      features.compressed = mkEnableOption ''
+        compressed feature which adds the options <literal>-Lce</literal> to
+        the <command>zfs send</command> command. When this is enabled, make
+        sure that both the sending and receiving pool have the same relevant
+        features enabled. Using <literal>-c</literal> will skip unneccessary
+        decompress-compress stages, <literal>-L</literal> is for large block
+        support and -e is for embedded data support. see
+        <citerefentry><refentrytitle>znapzend</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+        and <citerefentry><refentrytitle>zfs</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+        for more info.
+      '';
     };
   };
 
@@ -381,12 +399,22 @@ in
         '';
 
         serviceConfig = {
+          # znapzendzetup --import apparently tries to connect to the backup
+          # host 3 times with a timeout of 30 seconds, leading to a startup
+          # delay of >90s when the host is down, which is just above the default
+          # service timeout of 90 seconds. Increase the timeout so it doesn't
+          # make the service fail in that case.
+          TimeoutStartSec = 180;
+          # Needs to have write access to ZFS
+          User = "root";
           ExecStart = let
             args = concatStringsSep " " [
               "--logto=${cfg.logTo}"
               "--loglevel=${cfg.logLevel}"
               (optionalString cfg.noDestroy "--nodestroy")
               (optionalString cfg.autoCreation "--autoCreation")
+              (optionalString (enabledFeatures != [])
+                "--features=${concatStringsSep "," enabledFeatures}")
             ]; in "${pkgs.znapzend}/bin/znapzend ${args}";
           ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
           Restart = "on-failure";
diff --git a/nixos/modules/services/continuous-integration/buildkite-agent.nix b/nixos/modules/services/continuous-integration/buildkite-agent.nix
index 12cc3d2b1ccc..32f361454bc1 100644
--- a/nixos/modules/services/continuous-integration/buildkite-agent.nix
+++ b/nixos/modules/services/continuous-integration/buildkite-agent.nix
@@ -191,6 +191,7 @@ in
         createHome = true;
         description = "Buildkite agent user";
         extraGroups = [ "keys" ];
+        isSystemUser = true;
       };
 
     environment.systemPackages = [ cfg.package ];
diff --git a/nixos/modules/services/databases/redis.nix b/nixos/modules/services/databases/redis.nix
index 5861323e5ea0..95128a641d94 100644
--- a/nixos/modules/services/databases/redis.nix
+++ b/nixos/modules/services/databases/redis.nix
@@ -185,16 +185,19 @@ in
   ###### implementation
 
   config = mkIf config.services.redis.enable {
-
-    boot.kernel.sysctl = {
-      "vm.nr_hugepages" = "0";
-    } // mkIf cfg.vmOverCommit { "vm.overcommit_memory" = "1"; };
+    boot.kernel.sysctl = (mkMerge [
+      { "vm.nr_hugepages" = "0"; }
+      ( mkIf cfg.vmOverCommit { "vm.overcommit_memory" = "1"; } )
+    ]);
 
     networking.firewall = mkIf cfg.openFirewall {
       allowedTCPPorts = [ cfg.port ];
     };
 
-    users.users.redis.description = "Redis database user";
+    users.users.redis = {
+      description = "Redis database user";
+      isSystemUser = true;
+    };
 
     environment.systemPackages = [ cfg.package ];
 
diff --git a/nixos/modules/services/databases/rethinkdb.nix b/nixos/modules/services/databases/rethinkdb.nix
index 4828e594b328..f18fbaf5b062 100644
--- a/nixos/modules/services/databases/rethinkdb.nix
+++ b/nixos/modules/services/databases/rethinkdb.nix
@@ -99,6 +99,7 @@ in
     users.users.rethinkdb = mkIf (cfg.user == "rethinkdb")
       { name = "rethinkdb";
         description = "RethinkDB server user";
+        isSystemUser = true;
       };
 
     users.groups = optionalAttrs (cfg.group == "rethinkdb") (singleton
diff --git a/nixos/modules/services/desktops/geoclue2.nix b/nixos/modules/services/desktops/geoclue2.nix
index 6007dddf50c0..542b2ead4104 100644
--- a/nixos/modules/services/desktops/geoclue2.nix
+++ b/nixos/modules/services/desktops/geoclue2.nix
@@ -188,34 +188,41 @@ in
 
     systemd.packages = [ package ];
 
-    users.users.geoclue = {
-      isSystemUser = true;
-      home = "/var/lib/geoclue";
-      group = "geoclue";
-      description = "Geoinformation service";
-    };
-
-    users.groups.geoclue = {};
+    # we cannot use DynamicUser as we need the the geoclue user to exist for the dbus policy to work
+    users = {
+      users.geoclue = {
+        isSystemUser = true;
+        home = "/var/lib/geoclue";
+        group = "geoclue";
+        description = "Geoinformation service";
+      };
 
-    systemd.tmpfiles.rules = [
-      "d /var/lib/geoclue 0755 geoclue geoclue"
-    ];
+      groups.geoclue = {};
+    };
 
-    # restart geoclue service when the configuration changes
-    systemd.services.geoclue.restartTriggers = [
-      config.environment.etc."geoclue/geoclue.conf".source
-    ];
+    systemd.services.geoclue = {
+      # restart geoclue service when the configuration changes
+      restartTriggers = [
+        config.environment.etc."geoclue/geoclue.conf".source
+      ];
+      serviceConfig.StateDirectory = "geoclue";
+    };
 
     # this needs to run as a user service, since it's associated with the
     # user who is making the requests
     systemd.user.services = mkIf cfg.enableDemoAgent {
       geoclue-agent = {
         description = "Geoclue agent";
-        script = "${package}/libexec/geoclue-2.0/demos/agent";
         # this should really be `partOf = [ "geoclue.service" ]`, but
         # we can't be part of a system service, and the agent should
         # be okay with the main service coming and going
         wantedBy = [ "default.target" ];
+        serviceConfig = {
+          Type = "exec";
+          ExecStart = "${package}/libexec/geoclue-2.0/demos/agent";
+          Restart = "on-failure";
+          PrivateTmp = true;
+        };
       };
     };
 
@@ -256,4 +263,6 @@ in
         };
       } // mapAttrs' appConfigToINICompatible cfg.appConfig);
   };
+
+  meta.maintainers = with lib.maintainers; [ worldofpeace ];
 }
diff --git a/nixos/modules/services/editors/infinoted.nix b/nixos/modules/services/editors/infinoted.nix
index 9cc8d421270e..be3667616942 100644
--- a/nixos/modules/services/editors/infinoted.nix
+++ b/nixos/modules/services/editors/infinoted.nix
@@ -115,6 +115,7 @@ in {
       { name = "infinoted";
         description = "Infinoted user";
         group = cfg.group;
+        isSystemUser = true;
       };
     users.groups = optional (cfg.group == "infinoted")
       { name = "infinoted";
diff --git a/nixos/modules/services/hardware/fancontrol.nix b/nixos/modules/services/hardware/fancontrol.nix
index 616e4add31e8..bb4541a784da 100644
--- a/nixos/modules/services/hardware/fancontrol.nix
+++ b/nixos/modules/services/hardware/fancontrol.nix
@@ -4,42 +4,41 @@ with lib;
 
 let
   cfg = config.hardware.fancontrol;
-  configFile = pkgs.writeText "fan.conf" cfg.config;
-
-in {
+  configFile = pkgs.writeText "fancontrol.conf" cfg.config;
 
+in{
   options.hardware.fancontrol = {
-    enable = mkEnableOption "fancontrol (requires fancontrol.config)";
+    enable = mkEnableOption "software fan control (requires fancontrol.config)";
 
     config = mkOption {
-      type = types.lines;
       default = null;
+      type = types.lines;
+      description = "Fancontrol configuration file content. See <citerefentry><refentrytitle>pwmconfig</refentrytitle><manvolnum>8</manvolnum></citerefentry> from the lm_sensors package.";
       example = ''
         # Configuration file generated by pwmconfig
-        INTERVAL=1
-        DEVPATH=hwmon0=devices/platform/nct6775.656 hwmon1=devices/pci0000:00/0000:00:18.3
-        DEVNAME=hwmon0=nct6779 hwmon1=k10temp
-        FCTEMPS=hwmon0/pwm2=hwmon1/temp1_input
-        FCFANS=hwmon0/pwm2=hwmon0/fan2_input
-        MINTEMP=hwmon0/pwm2=25
-        MAXTEMP=hwmon0/pwm2=60
-        MINSTART=hwmon0/pwm2=25
-        MINSTOP=hwmon0/pwm2=10
-        MINPWM=hwmon0/pwm2=0
-        MAXPWM=hwmon0/pwm2=255
+        INTERVAL=10
+        DEVPATH=hwmon3=devices/virtual/thermal/thermal_zone2 hwmon4=devices/platform/f71882fg.656
+        DEVNAME=hwmon3=soc_dts1 hwmon4=f71869a
+        FCTEMPS=hwmon4/device/pwm1=hwmon3/temp1_input
+        FCFANS= hwmon4/device/pwm1=hwmon4/device/fan1_input
+        MINTEMP=hwmon4/device/pwm1=35
+        MAXTEMP=hwmon4/device/pwm1=65
+        MINSTART=hwmon4/device/pwm1=150
+        MINSTOP=hwmon4/device/pwm1=0
       '';
-      description = "Contents for configuration file. See <citerefentry><refentrytitle>pwmconfig</refentrytitle><manvolnum>8</manvolnum></citerefentry>.";
     };
   };
 
-
   config = mkIf cfg.enable {
     systemd.services.fancontrol = {
-      description = "Fan speed control from lm_sensors";
+      unitConfig.Documentation = "man:fancontrol(8)";
+      description = "software fan control";
       wantedBy = [ "multi-user.target" ];
+      after = [ "lm_sensors.service" ];
+
       serviceConfig = {
         Type = "simple";
-        ExecStart = "${pkgs.lm_sensors}/bin/fancontrol ${configFile}";
+        ExecStart = "${pkgs.lm_sensors}/sbin/fancontrol ${configFile}";
       };
     };
   };
diff --git a/nixos/modules/services/hardware/trezord.nix b/nixos/modules/services/hardware/trezord.nix
index 62824ed7350a..c517e9fbb2bd 100644
--- a/nixos/modules/services/hardware/trezord.nix
+++ b/nixos/modules/services/hardware/trezord.nix
@@ -44,20 +44,7 @@ in {
   ### implementation
 
   config = mkIf cfg.enable {
-    services.udev.packages = lib.singleton (pkgs.writeTextFile {
-      name = "trezord-udev-rules";
-      destination = "/etc/udev/rules.d/51-trezor.rules";
-      text = ''
-        # TREZOR v1 (One)
-        SUBSYSTEM=="usb", ATTR{idVendor}=="534c", ATTR{idProduct}=="0001", MODE="0660", GROUP="trezord", TAG+="uaccess", SYMLINK+="trezor%n"
-        KERNEL=="hidraw*", ATTRS{idVendor}=="534c", ATTRS{idProduct}=="0001", MODE="0660", GROUP="trezord", TAG+="uaccess"
-
-        # TREZOR v2 (T)
-        SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="53c0", MODE="0660", GROUP="trezord", TAG+="uaccess", SYMLINK+="trezor%n"
-        SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="53c1", MODE="0660", GROUP="trezord", TAG+="uaccess", SYMLINK+="trezor%n"
-        KERNEL=="hidraw*", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="53c1", MODE="0660", GROUP="trezord", TAG+="uaccess"
-      '';
-    });
+    services.udev.packages = [ pkgs.trezor-udev-rules ];
 
     systemd.services.trezord = {
       description = "TREZOR Bridge";
@@ -74,6 +61,7 @@ in {
     users.users.trezord = {
       group = "trezord";
       description = "Trezor bridge daemon user";
+      isSystemUser = true;
     };
 
     users.groups.trezord = {};
diff --git a/nixos/modules/services/hardware/usbmuxd.nix b/nixos/modules/services/hardware/usbmuxd.nix
index 93ced0b9f04d..39bbcaf4627c 100644
--- a/nixos/modules/services/hardware/usbmuxd.nix
+++ b/nixos/modules/services/hardware/usbmuxd.nix
@@ -47,6 +47,7 @@ in
       name = cfg.user;
       description = "usbmuxd user";
       group = cfg.group;
+      isSystemUser = true;
     };
 
     users.groups = optional (cfg.group == defaultUserGroup) {
diff --git a/nixos/modules/services/hardware/vdr.nix b/nixos/modules/services/hardware/vdr.nix
index 6e246f70f515..8a6cde51b06f 100644
--- a/nixos/modules/services/hardware/vdr.nix
+++ b/nixos/modules/services/hardware/vdr.nix
@@ -66,6 +66,7 @@ in {
     users.users.vdr = {
       group = "vdr";
       home = libDir;
+      isSystemUser = true;
     };
 
     users.groups.vdr = {};
diff --git a/nixos/modules/services/mail/mailhog.nix b/nixos/modules/services/mail/mailhog.nix
index b78f4c8e0e66..0f998c6d0ea6 100644
--- a/nixos/modules/services/mail/mailhog.nix
+++ b/nixos/modules/services/mail/mailhog.nix
@@ -27,6 +27,7 @@ in {
     users.users.mailhog = {
       name = cfg.user;
       description = "MailHog service user";
+      isSystemUser = true;
     };
 
     systemd.services.mailhog = {
diff --git a/nixos/modules/services/misc/airsonic.nix b/nixos/modules/services/misc/airsonic.nix
index 919d3b2f6e64..c296e048cea4 100644
--- a/nixos/modules/services/misc/airsonic.nix
+++ b/nixos/modules/services/misc/airsonic.nix
@@ -148,6 +148,7 @@ in {
       name = cfg.user;
       home = cfg.home;
       createHome = true;
+      isSystemUser = true;
     };
   };
 }
diff --git a/nixos/modules/services/misc/docker-registry.nix b/nixos/modules/services/misc/docker-registry.nix
index c87607d2666a..89bac4f47d73 100644
--- a/nixos/modules/services/misc/docker-registry.nix
+++ b/nixos/modules/services/misc/docker-registry.nix
@@ -145,11 +145,13 @@ in {
     };
 
     users.users.docker-registry =
-      if cfg.storagePath != null
+      (if cfg.storagePath != null
       then {
         createHome = true;
         home = cfg.storagePath;
       }
-      else {};
+      else {}) // {
+        isSystemUser = true;
+      };
   };
 }
diff --git a/nixos/modules/services/misc/errbot.nix b/nixos/modules/services/misc/errbot.nix
index 256adce2f02e..b447ba5d438d 100644
--- a/nixos/modules/services/misc/errbot.nix
+++ b/nixos/modules/services/misc/errbot.nix
@@ -76,7 +76,10 @@ in {
   };
 
   config = mkIf (cfg.instances != {}) {
-    users.users.errbot.group = "errbot";
+    users.users.errbot = {
+      group = "errbot";
+      isSystemUser = true;
+    };
     users.groups.errbot = {};
 
     systemd.services = mapAttrs' (name: instanceCfg: nameValuePair "errbot-${name}" (
diff --git a/nixos/modules/services/misc/gitea.nix b/nixos/modules/services/misc/gitea.nix
index 4992b13c9d4a..c8c59fb256e8 100644
--- a/nixos/modules/services/misc/gitea.nix
+++ b/nixos/modules/services/misc/gitea.nix
@@ -409,6 +409,7 @@ in
         home = cfg.stateDir;
         useDefaultShell = true;
         group = "gitea";
+        isSystemUser = true;
       };
     };
 
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index 6ee35aaca565..07ea9c458437 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -608,6 +608,8 @@ in {
     # objects owners and extensions; for now we tack on what's needed
     # here.
     systemd.services.postgresql.postStart = mkAfter (optionalString databaseActuallyCreateLocally ''
+      set -eu
+
       $PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = '${cfg.databaseName}'" | grep -q 1 || $PSQL -tAc 'CREATE DATABASE "${cfg.databaseName}" OWNER "${cfg.databaseUsername}"'
       current_owner=$($PSQL -tAc "SELECT pg_catalog.pg_get_userbyid(datdba) FROM pg_catalog.pg_database WHERE datname = '${cfg.databaseName}'")
       if [[ "$current_owner" != "${cfg.databaseUsername}" ]]; then
@@ -739,7 +741,6 @@ in {
         gitlab-workhorse
       ];
       serviceConfig = {
-        PermissionsStartOnly = true; # preStart must be run as root
         Type = "simple";
         User = cfg.user;
         Group = cfg.group;
@@ -781,13 +782,18 @@ in {
         ExecStartPre = let
           preStartFullPrivileges = ''
             shopt -s dotglob nullglob
+            set -eu
+
             chown --no-dereference '${cfg.user}':'${cfg.group}' '${cfg.statePath}'/*
             chown --no-dereference '${cfg.user}':'${cfg.group}' '${cfg.statePath}'/config/*
           '';
           preStart = ''
+            set -eu
+
             cp -f ${cfg.packages.gitlab}/share/gitlab/VERSION ${cfg.statePath}/VERSION
             rm -rf ${cfg.statePath}/db/*
             rm -rf ${cfg.statePath}/config/initializers/*
+            rm -f ${cfg.statePath}/lib
             cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/config.dist/* ${cfg.statePath}/config
             cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/db/* ${cfg.statePath}/db
 
diff --git a/nixos/modules/services/misc/gollum.nix b/nixos/modules/services/misc/gollum.nix
index 7653b415bf09..f4a9c72b1545 100644
--- a/nixos/modules/services/misc/gollum.nix
+++ b/nixos/modules/services/misc/gollum.nix
@@ -71,6 +71,7 @@ in
       group = config.users.users.gollum.name;
       description = "Gollum user";
       createHome = false;
+      isSystemUser = true;
     };
 
     users.groups.gollum = { };
diff --git a/nixos/modules/services/misc/jellyfin.nix b/nixos/modules/services/misc/jellyfin.nix
index 55559206568d..6ecdfb57dc35 100644
--- a/nixos/modules/services/misc/jellyfin.nix
+++ b/nixos/modules/services/misc/jellyfin.nix
@@ -41,7 +41,10 @@ in
     };
 
     users.users = mkIf (cfg.user == "jellyfin") {
-      jellyfin.group = cfg.group;
+      jellyfin = {
+        group = cfg.group;
+        isSystemUser = true;
+      };
     };
 
     users.groups = mkIf (cfg.group == "jellyfin") {
diff --git a/nixos/modules/services/misc/matrix-synapse.nix b/nixos/modules/services/misc/matrix-synapse.nix
index 0f4eb2ccfcad..50661b873f64 100644
--- a/nixos/modules/services/misc/matrix-synapse.nix
+++ b/nixos/modules/services/misc/matrix-synapse.nix
@@ -407,6 +407,9 @@ in {
           "192.168.0.0/16"
           "100.64.0.0/10"
           "169.254.0.0/16"
+          "::1/128"
+          "fe80::/64"
+          "fc00::/7"
         ];
         description = ''
           List of IP address CIDR ranges that the URL preview spider is denied
diff --git a/nixos/modules/services/misc/osrm.nix b/nixos/modules/services/misc/osrm.nix
index f89f37ccd9df..79c347ab7e0e 100644
--- a/nixos/modules/services/misc/osrm.nix
+++ b/nixos/modules/services/misc/osrm.nix
@@ -59,6 +59,7 @@ in
       group = config.users.users.osrm.name;
       description = "OSRM user";
       createHome = false;
+      isSystemUser = true;
     };
 
     users.groups.osrm = { };
diff --git a/nixos/modules/services/misc/redmine.nix b/nixos/modules/services/misc/redmine.nix
index 24b9e27ac2da..bf9a6914a483 100644
--- a/nixos/modules/services/misc/redmine.nix
+++ b/nixos/modules/services/misc/redmine.nix
@@ -62,20 +62,11 @@ in
     services.redmine = {
       enable = mkEnableOption "Redmine";
 
-      # default to the 4.x series not forcing major version upgrade of those on the 3.x series
       package = mkOption {
         type = types.package;
-        default = if versionAtLeast config.system.stateVersion "19.03"
-          then pkgs.redmine_4
-          else pkgs.redmine
-        ;
-        defaultText = "pkgs.redmine";
-        description = ''
-          Which Redmine package to use. This defaults to version 3.x if
-          <literal>system.stateVersion &lt; 19.03</literal> and version 4.x
-          otherwise.
-        '';
-        example = "pkgs.redmine_4.override { ruby = pkgs.ruby_2_4; }";
+        default = pkgs.redmine;
+        description = "Which Redmine package to use.";
+        example = "pkgs.redmine.override { ruby = pkgs.ruby_2_4; }";
       };
 
       user = mkOption {
diff --git a/nixos/modules/services/misc/zoneminder.nix b/nixos/modules/services/misc/zoneminder.nix
index 3bff04e7127d..d7f7324580c0 100644
--- a/nixos/modules/services/misc/zoneminder.nix
+++ b/nixos/modules/services/misc/zoneminder.nix
@@ -265,7 +265,7 @@ in {
                 }
 
                 location /cache/ {
-                  alias /var/cache/${dirName};
+                  alias /var/cache/${dirName}/;
                 }
 
                 location ~ \.php$ {
diff --git a/nixos/modules/services/monitoring/collectd.nix b/nixos/modules/services/monitoring/collectd.nix
index b2e44a1e3666..731ac743b7c6 100644
--- a/nixos/modules/services/monitoring/collectd.nix
+++ b/nixos/modules/services/monitoring/collectd.nix
@@ -131,6 +131,7 @@ in {
 
     users.users = optional (cfg.user == "collectd") {
       name = "collectd";
+      isSystemUser = true;
     };
   };
 }
diff --git a/nixos/modules/services/monitoring/fusion-inventory.nix b/nixos/modules/services/monitoring/fusion-inventory.nix
index b90579bb70c7..fe19ed561954 100644
--- a/nixos/modules/services/monitoring/fusion-inventory.nix
+++ b/nixos/modules/services/monitoring/fusion-inventory.nix
@@ -49,6 +49,7 @@ in {
     users.users = singleton {
       name = "fusion-inventory";
       description = "FusionInventory user";
+      isSystemUser = true;
     };
 
     systemd.services.fusion-inventory = {
diff --git a/nixos/modules/services/monitoring/netdata.nix b/nixos/modules/services/monitoring/netdata.nix
index 463b1b882acf..3ffde8e9bce2 100644
--- a/nixos/modules/services/monitoring/netdata.nix
+++ b/nixos/modules/services/monitoring/netdata.nix
@@ -138,7 +138,7 @@ in {
       description = "Real time performance monitoring";
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
-      path = (with pkgs; [ gawk curl ]) ++ lib.optional cfg.python.enable
+      path = (with pkgs; [ curl gawk which ]) ++ lib.optional cfg.python.enable
         (pkgs.python3.withPackages cfg.python.extraPackages);
       serviceConfig = {
         Environment="PYTHONPATH=${pkgs.netdata}/libexec/netdata/python.d/python_modules";
@@ -181,6 +181,7 @@ in {
 
     users.users = optional (cfg.user == defaultUser) {
       name = defaultUser;
+      isSystemUser = true;
     };
 
     users.groups = optional (cfg.group == defaultUser) {
diff --git a/nixos/modules/services/monitoring/zabbix-agent.nix b/nixos/modules/services/monitoring/zabbix-agent.nix
index 856b9432892b..b3383ed628b2 100644
--- a/nixos/modules/services/monitoring/zabbix-agent.nix
+++ b/nixos/modules/services/monitoring/zabbix-agent.nix
@@ -131,6 +131,7 @@ in
     users.users.${user} = {
       description = "Zabbix Agent daemon user";
       inherit group;
+      isSystemUser = true;
     };
 
     users.groups.${group} = { };
diff --git a/nixos/modules/services/network-filesystems/orangefs/client.nix b/nixos/modules/services/network-filesystems/orangefs/client.nix
new file mode 100644
index 000000000000..b69d9e713c3d
--- /dev/null
+++ b/nixos/modules/services/network-filesystems/orangefs/client.nix
@@ -0,0 +1,97 @@
+{ config, lib, pkgs, ...} :
+
+with lib;
+
+let
+  cfg = config.services.orangefs.client;
+
+in {
+  ###### interface
+
+  options = {
+    services.orangefs.client = {
+      enable = mkEnableOption "OrangeFS client daemon";
+
+      extraOptions = mkOption {
+        type = with types; listOf str;
+        default = [];
+        description = "Extra command line options for pvfs2-client.";
+      };
+
+      fileSystems = mkOption {
+        description = ''
+          The orangefs file systems to be mounted.
+          This option is prefered over using <option>fileSystems</option> directly since
+          the pvfs client service needs to be running for it to be mounted.
+        '';
+
+        example = [{
+          mountPoint = "/orangefs";
+          target = "tcp://server:3334/orangefs";
+        }];
+
+        type = with types; listOf (submodule ({ ... } : {
+          options = {
+
+            mountPoint = mkOption {
+              type = types.str;
+              default = "/orangefs";
+              description = "Mount point.";
+            };
+
+            options = mkOption {
+              type = with types; listOf str;
+              default = [];
+              description = "Mount options";
+            };
+
+            target = mkOption {
+              type = types.str;
+              default = null;
+              example = "tcp://server:3334/orangefs";
+              description = "Target URL";
+            };
+          };
+        }));
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.orangefs ];
+
+    boot.supportedFilesystems = [ "pvfs2" ];
+    boot.kernelModules = [ "orangefs" ];
+
+    systemd.services.orangefs-client = {
+      requires = [ "network-online.target" ];
+      after = [ "network-online.target" ];
+
+      serviceConfig = {
+        Type = "simple";
+
+         ExecStart = ''
+           ${pkgs.orangefs}/bin/pvfs2-client-core \
+              --logtype=syslog ${concatStringsSep " " cfg.extraOptions}
+        '';
+
+        TimeoutStopSec = "120";
+      };
+    };
+
+    systemd.mounts = map (fs: {
+      requires = [ "orangefs-client.service" ];
+      after = [ "orangefs-client.service" ];
+      bindsTo = [ "orangefs-client.service" ];
+      wantedBy = [ "remote-fs.target" ];
+      type = "pvfs2";
+      options = concatStringsSep "," fs.options;
+      what = fs.target;
+      where = fs.mountPoint;
+    }) cfg.fileSystems;
+  };
+}
+
diff --git a/nixos/modules/services/network-filesystems/orangefs/server.nix b/nixos/modules/services/network-filesystems/orangefs/server.nix
new file mode 100644
index 000000000000..74ebdc134024
--- /dev/null
+++ b/nixos/modules/services/network-filesystems/orangefs/server.nix
@@ -0,0 +1,225 @@
+{ config, lib, pkgs, ...} :
+
+with lib;
+
+let
+  cfg = config.services.orangefs.server;
+
+  aliases = mapAttrsToList (alias: url: alias) cfg.servers;
+
+  # Maximum handle number is 2^63
+  maxHandle = 9223372036854775806;
+
+  # One range of handles for each meta/data instance
+  handleStep = maxHandle / (length aliases) / 2;
+
+  fileSystems = mapAttrsToList (name: fs: ''
+    <FileSystem>
+      Name ${name}
+      ID ${toString fs.id}
+      RootHandle ${toString fs.rootHandle}
+
+      ${fs.extraConfig}
+
+      <MetaHandleRanges>
+      ${concatStringsSep "\n" (
+          imap0 (i: alias:
+            let
+              begin = i * handleStep + 3;
+              end = begin + handleStep - 1;
+            in "Range ${alias} ${toString begin}-${toString end}") aliases
+       )}
+      </MetaHandleRanges>
+
+      <DataHandleRanges>
+      ${concatStringsSep "\n" (
+          imap0 (i: alias:
+            let
+              begin = i * handleStep + 3 + (length aliases) * handleStep;
+              end = begin + handleStep - 1;
+            in "Range ${alias} ${toString begin}-${toString end}") aliases
+       )}
+      </DataHandleRanges>
+
+      <StorageHints>
+      TroveSyncMeta ${if fs.troveSyncMeta then "yes" else "no"}
+      TroveSyncData ${if fs.troveSyncData then "yes" else "no"}
+      ${fs.extraStorageHints}
+      </StorageHints>
+
+    </FileSystem>
+  '') cfg.fileSystems;
+
+  configFile = ''
+    <Defaults>
+    LogType ${cfg.logType}
+    DataStorageSpace ${cfg.dataStorageSpace}
+    MetaDataStorageSpace ${cfg.metadataStorageSpace}
+
+    BMIModules ${concatStringsSep "," cfg.BMIModules}
+    ${cfg.extraDefaults}
+    </Defaults>
+
+    ${cfg.extraConfig}
+
+    <Aliases>
+    ${concatStringsSep "\n" (mapAttrsToList (alias: url: "Alias ${alias} ${url}") cfg.servers)}
+    </Aliases>
+
+    ${concatStringsSep "\n" fileSystems}
+  '';
+
+in {
+  ###### interface
+
+  options = {
+    services.orangefs.server = {
+      enable = mkEnableOption "OrangeFS server";
+
+      logType = mkOption {
+        type = with types; enum [ "file" "syslog" ];
+        default = "syslog";
+        description = "Destination for log messages.";
+      };
+
+      dataStorageSpace = mkOption {
+        type = types.str;
+        default = null;
+        example = "/data/storage";
+        description = "Directory for data storage.";
+      };
+
+      metadataStorageSpace = mkOption {
+        type = types.str;
+        default = null;
+        example = "/data/meta";
+        description = "Directory for meta data storage.";
+      };
+
+      BMIModules = mkOption {
+        type = with types; listOf str;
+        default = [ "bmi_tcp" ];
+        example = [ "bmi_tcp" "bmi_ib"];
+        description = "List of BMI modules to load.";
+      };
+
+      extraDefaults = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Extra config for <literal>&lt;Defaults&gt;</literal> section.";
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Extra config for the global section.";
+      };
+
+      servers = mkOption {
+        type = with types; attrsOf types.str;
+        default = {};
+        example = ''
+          {
+            node1="tcp://node1:3334";
+            node2="tcp://node2:3334";
+          }
+        '';
+        description = "URLs for storage server including port. The attribute names define the server alias.";
+      };
+
+      fileSystems = mkOption {
+        description = ''
+          These options will create the <literal>&lt;FileSystem&gt;</literal> sections of config file.
+        '';
+        default = { orangefs = {}; };
+        defaultText = literalExample "{ orangefs = {}; }";
+        example = literalExample ''
+          {
+            fs1 = {
+              id = 101;
+            };
+
+            fs2 = {
+              id = 102;
+            };
+          }
+        '';
+        type = with types; attrsOf (submodule ({ ... } : {
+          options = {
+            id = mkOption {
+              type = types.int;
+              default = 1;
+              description = "File system ID (must be unique within configuration).";
+            };
+
+            rootHandle = mkOption {
+              type = types.int;
+              default = 3;
+              description = "File system root ID.";
+            };
+
+            extraConfig = mkOption {
+              type = types.lines;
+              default = "";
+              description = "Extra config for <literal>&lt;FileSystem&gt;</literal> section.";
+            };
+
+            troveSyncMeta = mkOption {
+              type = types.bool;
+              default = true;
+              description = "Sync meta data.";
+            };
+
+            troveSyncData = mkOption {
+              type = types.bool;
+              default = false;
+              description = "Sync data.";
+            };
+
+            extraStorageHints = mkOption {
+              type = types.lines;
+              default = "";
+              description = "Extra config for <literal>&lt;StorageHints&gt;</literal> section.";
+            };
+          };
+        }));
+      };
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.orangefs ];
+
+    # orangefs daemon will run as user
+    users.users.orangefs.isSystemUser = true;
+    users.groups.orangefs = {};
+
+    # To format the file system the config file is needed.
+    environment.etc."orangefs/server.conf" = {
+      text = configFile;
+      user = "orangefs";
+      group = "orangefs";
+    };
+
+    systemd.services.orangefs-server = {
+      wantedBy = [ "multi-user.target" ];
+      requires = [ "network-online.target" ];
+      after = [ "network-online.target" ];
+
+      serviceConfig = {
+        # Run as "simple" in forground mode.
+        # This is more reliable
+        ExecStart = ''
+          ${pkgs.orangefs}/bin/pvfs2-server -d \
+            /etc/orangefs/server.conf
+        '';
+        TimeoutStopSec = "120";
+        User = "orangefs";
+        Group = "orangefs";
+      };
+    };
+  };
+
+}
diff --git a/nixos/modules/services/networking/bitcoind.nix b/nixos/modules/services/networking/bitcoind.nix
index 1439d739da9d..90f1291c0198 100644
--- a/nixos/modules/services/networking/bitcoind.nix
+++ b/nixos/modules/services/networking/bitcoind.nix
@@ -187,6 +187,7 @@ in {
       group = cfg.group;
       description = "Bitcoin daemon user";
       home = cfg.dataDir;
+      isSystemUser = true;
     };
     users.groups.${cfg.group} = {
       name = cfg.group;
diff --git a/nixos/modules/services/networking/dnscache.nix b/nixos/modules/services/networking/dnscache.nix
index 5051fc916d96..d123bca93219 100644
--- a/nixos/modules/services/networking/dnscache.nix
+++ b/nixos/modules/services/networking/dnscache.nix
@@ -84,7 +84,7 @@ in {
 
   config = mkIf config.services.dnscache.enable {
     environment.systemPackages = [ pkgs.djbdns ];
-    users.users.dnscache = {};
+    users.users.dnscache.isSystemUser = true;
 
     systemd.services.dnscache = {
       description = "djbdns dnscache server";
diff --git a/nixos/modules/services/networking/dnscrypt-wrapper.nix b/nixos/modules/services/networking/dnscrypt-wrapper.nix
index bf13d5c6f5fe..79f9e1a43083 100644
--- a/nixos/modules/services/networking/dnscrypt-wrapper.nix
+++ b/nixos/modules/services/networking/dnscrypt-wrapper.nix
@@ -142,6 +142,7 @@ in {
       description = "dnscrypt-wrapper daemon user";
       home = "${dataDir}";
       createHome = true;
+      isSystemUser = true;
     };
     users.groups.dnscrypt-wrapper = { };
 
diff --git a/nixos/modules/services/networking/dnsdist.nix b/nixos/modules/services/networking/dnsdist.nix
index 12eee136e639..8249da69bc1a 100644
--- a/nixos/modules/services/networking/dnsdist.nix
+++ b/nixos/modules/services/networking/dnsdist.nix
@@ -46,11 +46,10 @@ in {
         RestartSec="1";
         DynamicUser = true;
         StartLimitInterval="0";
-        PrivateTmp=true;
         PrivateDevices=true;
-        CapabilityBoundingSet="CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID";
+        AmbientCapabilities="CAP_NET_BIND_SERVICE";
+        CapabilityBoundingSet="CAP_NET_BIND_SERVICE";
         ExecStart = "${pkgs.dnsdist}/bin/dnsdist --supervised --disable-syslog --config ${configFile}";
-        ProtectSystem="full";
         ProtectHome=true;
         RestrictAddressFamilies="AF_UNIX AF_INET AF_INET6";
         LimitNOFILE="16384";
diff --git a/nixos/modules/services/networking/go-shadowsocks2.nix b/nixos/modules/services/networking/go-shadowsocks2.nix
new file mode 100644
index 000000000000..afbd7ea27c65
--- /dev/null
+++ b/nixos/modules/services/networking/go-shadowsocks2.nix
@@ -0,0 +1,30 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.go-shadowsocks2.server;
+in {
+  options.services.go-shadowsocks2.server = {
+    enable = mkEnableOption "go-shadowsocks2 server";
+
+    listenAddress = mkOption {
+      type = types.str;
+      description = "Server listen address or URL";
+      example = "ss://AEAD_CHACHA20_POLY1305:your-password@:8488";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.go-shadowsocks2-server = {
+      description = "go-shadowsocks2 server";
+
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        ExecStart = "${pkgs.go-shadowsocks2}/bin/go-shadowsocks2 -s '${cfg.listenAddress}'";
+        DynamicUser = true;
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/hans.nix b/nixos/modules/services/networking/hans.nix
index 20e57e4626ef..4f60300f5ff4 100644
--- a/nixos/modules/services/networking/hans.nix
+++ b/nixos/modules/services/networking/hans.nix
@@ -138,6 +138,7 @@ in
     users.users = singleton {
       name = hansUser;
       description = "Hans daemon user";
+      isSystemUser = true;
     };
   };
 
diff --git a/nixos/modules/services/networking/haproxy.nix b/nixos/modules/services/networking/haproxy.nix
index 0438d0bf8d86..aff71e5e97da 100644
--- a/nixos/modules/services/networking/haproxy.nix
+++ b/nixos/modules/services/networking/haproxy.nix
@@ -1,7 +1,16 @@
 { config, lib, pkgs, ... }:
+
 let
   cfg = config.services.haproxy;
-  haproxyCfg = pkgs.writeText "haproxy.conf" cfg.config;
+
+  haproxyCfg = pkgs.writeText "haproxy.conf" ''
+    global
+      # needed for hot-reload to work without dropping packets in multi-worker mode
+      stats socket /run/haproxy/haproxy.sock mode 600 expose-fd listeners level user
+
+    ${cfg.config}
+  '';
+
 in
 with lib;
 {
@@ -25,9 +34,7 @@ with lib;
           <filename>haproxy.conf</filename>.
         '';
       };
-
     };
-
   };
 
   config = mkIf cfg.enable {
@@ -42,21 +49,16 @@ with lib;
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       serviceConfig = {
-        Type = "forking";
-        PIDFile = "/run/haproxy.pid";
-        ExecStartPre = "${pkgs.haproxy}/sbin/haproxy -c -q -f ${haproxyCfg}";
-        ExecStart = "${pkgs.haproxy}/sbin/haproxy -D -f ${haproxyCfg} -p /run/haproxy.pid";
-        ExecReload = "-${pkgs.bash}/bin/bash -c \"exec ${pkgs.haproxy}/sbin/haproxy -D -f ${haproxyCfg} -p /run/haproxy.pid -sf $MAINPID\"";
+        DynamicUser = true;
+        Type = "notify";
+        # when running the config test, don't be quiet so we can see what goes wrong
+        ExecStartPre = "${pkgs.haproxy}/sbin/haproxy -c -f ${haproxyCfg}";
+        ExecStart = "${pkgs.haproxy}/sbin/haproxy -Ws -f ${haproxyCfg}";
+        Restart = "on-failure";
+        RuntimeDirectory = "haproxy";
+        # needed in case we bind to port < 1024
+        AmbientCapabilities = "CAP_NET_BIND_SERVICE";
       };
     };
-
-    environment.systemPackages = [ pkgs.haproxy ];
-
-    users.users.haproxy = {
-      group = "haproxy";
-      uid = config.ids.uids.haproxy;
-    };
-
-    users.groups.haproxy.gid = config.ids.uids.haproxy;
   };
 }
diff --git a/nixos/modules/services/networking/jormungandr.nix b/nixos/modules/services/networking/jormungandr.nix
deleted file mode 100644
index 152cceb4bf91..000000000000
--- a/nixos/modules/services/networking/jormungandr.nix
+++ /dev/null
@@ -1,102 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-let
-  cfg = config.services.jormungandr;
-
-  inherit (lib) mkEnableOption mkIf mkOption;
-  inherit (lib) optionalString types;
-
-  dataDir = "/var/lib/jormungandr";
-
-  # Default settings so far, as the service matures we will
-  # move these out as separate settings
-  configSettings = {
-    storage = dataDir;
-    p2p = {
-      public_address = "/ip4/127.0.0.1/tcp/8299";
-      topics_of_interest = {
-        messages = "high";
-        blocks = "high";
-      };
-    };
-    rest = {
-      listen = "127.0.0.1:8607";
-    };
-  };
-
-  configFile = if cfg.configFile == null then
-    pkgs.writeText "jormungandr.yaml" (builtins.toJSON configSettings)
-  else cfg.configFile;
-
-in {
-
-  options = {
-
-    services.jormungandr = {
-      enable = mkEnableOption "jormungandr service";
-
-      configFile = mkOption {
-       type = types.nullOr types.path;
-       default = null;
-       example = "/var/lib/jormungandr/node.yaml";
-       description = ''
-         The path of the jormungandr blockchain configuration file in YAML format.
-         If no file is specified, a file is generated using the other options.
-       '';
-     };
-
-      secretFile = mkOption {
-       type = types.nullOr types.path;
-       default = null;
-       example = "/etc/secret/jormungandr.yaml";
-       description = ''
-         The path of the jormungandr blockchain secret node configuration file in
-         YAML format. Do not store this in nix store!
-       '';
-     };
-
-      genesisBlockHash = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        example = "d70495af81ae8600aca3e642b2427327cb6001ec4d7a0037e96a00dabed163f9";
-        description = ''
-          Set the genesis block hash (the hash of the block0) so we can retrieve
-          the genesis block (and the blockchain configuration) from the existing
-          storage or from the network.
-        '';
-      };
-
-      genesisBlockFile = mkOption {
-        type = types.nullOr types.path;
-        default = null;
-        example = "/var/lib/jormungandr/block-0.bin";
-        description = ''
-          The path of the genesis block file if we are hosting it locally.
-        '';
-      };
-
-    };
-  };
-
-  config = mkIf cfg.enable {
-
-    systemd.services.jormungandr = {
-      description = "jormungandr server";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "network-online.target" ];
-      environment = {
-        RUST_BACKTRACE = "full";
-      };
-      serviceConfig = {
-        DynamicUser = true;
-        StateDirectory = baseNameOf dataDir;
-        ExecStart = ''
-          ${pkgs.jormungandr}/bin/jormungandr --config ${configFile} \
-            ${optionalString (cfg.secretFile != null) " --secret ${cfg.secretFile}"} \
-            ${optionalString (cfg.genesisBlockHash != null) " --genesis-block-hash ${cfg.genesisBlockHash}"} \
-            ${optionalString (cfg.genesisBlockFile != null) " --genesis-block ${cfg.genesisBlockFile}"}
-        '';
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/networking/matterbridge.nix b/nixos/modules/services/networking/matterbridge.nix
index 1fd63348c16c..682eaa6eb297 100644
--- a/nixos/modules/services/networking/matterbridge.nix
+++ b/nixos/modules/services/networking/matterbridge.nix
@@ -95,6 +95,7 @@ in
     users.users = optional (cfg.user == "matterbridge")
       { name = "matterbridge";
         group = "matterbridge";
+        isSystemUser = true;
       };
 
     users.groups = optional (cfg.group == "matterbridge")
diff --git a/nixos/modules/services/networking/morty.nix b/nixos/modules/services/networking/morty.nix
index 1b3084fe9abb..e3a6444c1163 100644
--- a/nixos/modules/services/networking/morty.nix
+++ b/nixos/modules/services/networking/morty.nix
@@ -74,6 +74,7 @@ in
       { description = "Morty user";
         createHome = true;
         home = "/var/lib/morty";
+        isSystemUser = true;
       };
 
     systemd.services.morty =
diff --git a/nixos/modules/services/networking/nat.nix b/nixos/modules/services/networking/nat.nix
index 89d8590093dd..5681bda51cb4 100644
--- a/nixos/modules/services/networking/nat.nix
+++ b/nixos/modules/services/networking/nat.nix
@@ -29,7 +29,7 @@ let
     iptables -w -t nat -N nixos-nat-post
 
     # We can't match on incoming interface in POSTROUTING, so
-    # mark packets coming from the external interfaces.
+    # mark packets coming from the internal interfaces.
     ${concatMapStrings (iface: ''
       iptables -w -t nat -A nixos-nat-pre \
         -i '${iface}' -j MARK --set-mark 1
diff --git a/nixos/modules/services/networking/networkmanager.nix b/nixos/modules/services/networking/networkmanager.nix
index 918bf891b103..90d1032c41b4 100644
--- a/nixos/modules/services/networking/networkmanager.nix
+++ b/nixos/modules/services/networking/networkmanager.nix
@@ -456,15 +456,19 @@ in {
     };
 
     # Turn off NixOS' network management when networking is managed entirely by NetworkManager
-    networking = (mkIf (!delegateWireless) {
-      useDHCP = false;
-      # Use mkDefault to trigger the assertion about the conflict above
-      wireless.enable = mkDefault false;
-    }) // (mkIf cfg.enableStrongSwan {
-      networkmanager.packages = [ pkgs.networkmanager_strongswan ];
-    }) // (mkIf enableIwd {
-      wireless.iwd.enable = true;
-    });
+    networking = mkMerge [
+      (mkIf (!delegateWireless) {
+        useDHCP = false;
+      })
+
+      (mkIf cfg.enableStrongSwan {
+        networkmanager.packages = [ pkgs.networkmanager_strongswan ];
+      })
+
+      (mkIf enableIwd {
+        wireless.iwd.enable = true;
+      })
+    ];
 
     security.polkit.extraConfig = polkitConf;
 
diff --git a/nixos/modules/services/networking/nghttpx/default.nix b/nixos/modules/services/networking/nghttpx/default.nix
index d6e1906e3881..881a2670f5db 100644
--- a/nixos/modules/services/networking/nghttpx/default.nix
+++ b/nixos/modules/services/networking/nghttpx/default.nix
@@ -96,6 +96,7 @@ in
     users.groups.nghttpx = { };
     users.users.nghttpx = {
       group = config.users.groups.nghttpx.name;
+      isSystemUser = true;
     };
       
 
diff --git a/nixos/modules/services/networking/owamp.nix b/nixos/modules/services/networking/owamp.nix
index 821a0258f4be..dbb2e3b4c409 100644
--- a/nixos/modules/services/networking/owamp.nix
+++ b/nixos/modules/services/networking/owamp.nix
@@ -21,6 +21,7 @@ in
       name = "owamp";
       group = "owamp";
       description = "Owamp daemon";
+      isSystemUser = true;
     };
 
     users.groups = singleton {
diff --git a/nixos/modules/services/networking/thelounge.nix b/nixos/modules/services/networking/thelounge.nix
index b1d23372955e..875d8f661697 100644
--- a/nixos/modules/services/networking/thelounge.nix
+++ b/nixos/modules/services/networking/thelounge.nix
@@ -56,6 +56,7 @@ in {
     users.users.thelounge = {
       description = "thelounge service user";
       group = "thelounge";
+      isSystemUser = true;
     };
     users.groups.thelounge = {};
     systemd.services.thelounge = {
diff --git a/nixos/modules/services/networking/tinydns.nix b/nixos/modules/services/networking/tinydns.nix
index 7d5db71601ef..7b2c464ab46b 100644
--- a/nixos/modules/services/networking/tinydns.nix
+++ b/nixos/modules/services/networking/tinydns.nix
@@ -32,7 +32,7 @@ with lib;
   config = mkIf config.services.tinydns.enable {
     environment.systemPackages = [ pkgs.djbdns ];
 
-    users.users.tinydns = {};
+    users.users.tinydns.isSystemUser = true;
 
     systemd.services.tinydns = {
       description = "djbdns tinydns server";
diff --git a/nixos/modules/services/networking/trickster.nix b/nixos/modules/services/networking/trickster.nix
new file mode 100644
index 000000000000..8760dd5a9382
--- /dev/null
+++ b/nixos/modules/services/networking/trickster.nix
@@ -0,0 +1,112 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.trickster;
+in
+{
+
+  options = {
+    services.trickster = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable Trickster.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.trickster;
+        defaultText = "pkgs.trickster";
+        description = ''
+          Package that should be used for trickster.
+        '';
+      };
+
+      configFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          Path to configuration file.
+        '';
+      };
+
+      instance-id = mkOption {
+        type = types.nullOr types.int;
+        default = null;
+        description = ''
+          Instance ID for when running multiple processes (default null).
+        '';
+      };
+
+      log-level = mkOption {
+        type = types.str;
+        default = "info";
+        description = ''
+          Level of Logging to use (debug, info, warn, error) (default "info").
+        '';
+      };
+
+      metrics-port = mkOption {
+        type = types.port;
+        default = 8082;
+        description = ''
+          Port that the /metrics endpoint will listen on.
+        '';
+      };
+
+      origin = mkOption {
+        type = types.str;
+        default = "http://prometheus:9090";
+        description = ''
+          URL to the Prometheus Origin. Enter it like you would in grafana, e.g., http://prometheus:9090 (default http://prometheus:9090).
+        '';
+      };
+
+      profiler-port = mkOption {
+        type = types.nullOr types.port;
+        default = null;
+        description = ''
+          Port that the /debug/pprof endpoint will listen on.
+        '';
+      };
+
+      proxy-port = mkOption {
+        type = types.port;
+        default = 9090;
+        description = ''
+          Port that the Proxy server will listen on.
+        '';
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.trickster = {
+      description = "Dashboard Accelerator for Prometheus";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        DynamicUser = true;
+        ExecStart = ''
+          ${cfg.package}/bin/trickster \
+          -log-level ${cfg.log-level} \
+          -metrics-port ${toString cfg.metrics-port} \
+          -origin ${cfg.origin} \
+          -proxy-port ${toString cfg.proxy-port} \
+          ${optionalString (cfg.configFile != null) "-config ${cfg.configFile}"} \
+          ${optionalString (cfg.profiler-port != null) "-profiler-port ${cfg.profiler-port}"} \
+          ${optionalString (cfg.instance-id != null) "-instance-id ${cfg.instance-id}"}
+        '';
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        Restart = "always";
+      };
+    };
+
+  };  
+}
+
diff --git a/nixos/modules/services/printing/cupsd.nix b/nixos/modules/services/printing/cupsd.nix
index 3fcae611dc79..1071c05d514e 100644
--- a/nixos/modules/services/printing/cupsd.nix
+++ b/nixos/modules/services/printing/cupsd.nix
@@ -31,7 +31,7 @@ let
   # part of CUPS itself, e.g. the SMB backend is part of Samba.  Since
   # we can't update ${cups.out}/lib/cups itself, we create a symlink tree
   # here and add the additional programs.  The ServerBin directive in
-  # cupsd.conf tells cupsd to use this tree.
+  # cups-files.conf tells cupsd to use this tree.
   bindir = pkgs.buildEnv {
     name = "cups-progs";
     paths =
diff --git a/nixos/modules/services/scheduling/marathon.nix b/nixos/modules/services/scheduling/marathon.nix
index 0961a67770e1..2e0d20c64b23 100644
--- a/nixos/modules/services/scheduling/marathon.nix
+++ b/nixos/modules/services/scheduling/marathon.nix
@@ -93,6 +93,6 @@ in {
       };
     };
 
-    users.users.${cfg.user} = { };
+    users.users.${cfg.user}.isSystemUser = true;
   };
 }
diff --git a/nixos/modules/services/security/bitwarden_rs/default.nix b/nixos/modules/services/security/bitwarden_rs/default.nix
index 80fd65891ff8..d1817db07555 100644
--- a/nixos/modules/services/security/bitwarden_rs/default.nix
+++ b/nixos/modules/services/security/bitwarden_rs/default.nix
@@ -74,7 +74,10 @@ in {
       webVaultEnabled = mkDefault true;
     };
 
-    users.users.bitwarden_rs = { inherit group; };
+    users.users.bitwarden_rs = {
+      inherit group;
+      isSystemUser = true;
+    };
     users.groups.bitwarden_rs = { };
 
     systemd.services.bitwarden_rs = {
diff --git a/nixos/modules/services/security/oauth2_proxy.nix b/nixos/modules/services/security/oauth2_proxy.nix
index bb03f7fc9e43..2abb9ec32aca 100644
--- a/nixos/modules/services/security/oauth2_proxy.nix
+++ b/nixos/modules/services/security/oauth2_proxy.nix
@@ -546,6 +546,7 @@ in
 
     users.users.oauth2_proxy = {
       description = "OAuth2 Proxy";
+      isSystemUser = true;
     };
 
     systemd.services.oauth2_proxy = {
diff --git a/nixos/modules/services/security/vault.nix b/nixos/modules/services/security/vault.nix
index d5962ba9af90..b0ab8fadcbec 100644
--- a/nixos/modules/services/security/vault.nix
+++ b/nixos/modules/services/security/vault.nix
@@ -119,9 +119,8 @@ in
     };
     users.groups.vault.gid = config.ids.gids.vault;
 
-    systemd.tmpfiles.rules = optional (cfg.storagePath != null) [
-      "d '${cfg.storagePath}' 0700 vault vault - -"
-    ];
+    systemd.tmpfiles.rules = optional (cfg.storagePath != null)
+      "d '${cfg.storagePath}' 0700 vault vault - -";
 
     systemd.services.vault = {
       description = "Vault server daemon";
diff --git a/nixos/modules/services/torrent/magnetico.nix b/nixos/modules/services/torrent/magnetico.nix
index 02fa2ac0750a..a7acdb78b316 100644
--- a/nixos/modules/services/torrent/magnetico.nix
+++ b/nixos/modules/services/torrent/magnetico.nix
@@ -171,6 +171,7 @@ in {
 
     users.users.magnetico = {
       description = "Magnetico daemons user";
+      isSystemUser = true;
     };
 
     systemd.services.magneticod = {
diff --git a/nixos/modules/services/web-apps/codimd.nix b/nixos/modules/services/web-apps/codimd.nix
index 7ae7cd9c52d8..5f56f8ed5a09 100644
--- a/nixos/modules/services/web-apps/codimd.nix
+++ b/nixos/modules/services/web-apps/codimd.nix
@@ -893,6 +893,7 @@ in
       extraGroups = cfg.groups;
       home = cfg.workDir;
       createHome = true;
+      isSystemUser = true;
     };
 
     systemd.services.codimd = {
diff --git a/nixos/modules/services/web-apps/frab.nix b/nixos/modules/services/web-apps/frab.nix
index 7914e5cc0ee1..a9a30b409220 100644
--- a/nixos/modules/services/web-apps/frab.nix
+++ b/nixos/modules/services/web-apps/frab.nix
@@ -177,6 +177,7 @@ in
       { name = cfg.user;
         group = cfg.group;
         home = "${cfg.statePath}";
+        isSystemUser = true;
       }
     ];
 
diff --git a/nixos/modules/services/web-apps/limesurvey.nix b/nixos/modules/services/web-apps/limesurvey.nix
index 68b57a9b90dd..bd524524130d 100644
--- a/nixos/modules/services/web-apps/limesurvey.nix
+++ b/nixos/modules/services/web-apps/limesurvey.nix
@@ -277,7 +277,10 @@ in
 
     systemd.services.httpd.after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
 
-    users.users.${user}.group = group;
+    users.users.${user} = {
+      group = group;
+      isSystemUser = true;
+    };
 
   };
 }
diff --git a/nixos/modules/services/web-apps/matomo-doc.xml b/nixos/modules/services/web-apps/matomo-doc.xml
index 8485492c51c7..79cece551d34 100644
--- a/nixos/modules/services/web-apps/matomo-doc.xml
+++ b/nixos/modules/services/web-apps/matomo-doc.xml
@@ -105,7 +105,7 @@ GRANT ALL PRIVILEGES ON matomo.* TO 'matomo'@'localhost';
   <para>
    You can use other web servers by forwarding calls for
    <filename>index.php</filename> and <filename>piwik.php</filename> to the
-   <literal>/run/phpfpm-matomo.sock</literal> fastcgi unix socket. You can use
+   <literal><link linkend="opt-services.phpfpm.pools._name_.socket">services.phpfpm.pools.&lt;name&gt;.socket</link></literal> fastcgi unix socket. You can use
    the nginx configuration in the module code as a reference to what else
    should be configured.
   </para>
diff --git a/nixos/modules/services/web-apps/matomo.nix b/nixos/modules/services/web-apps/matomo.nix
index 1e34aff8d171..352cc4c647bc 100644
--- a/nixos/modules/services/web-apps/matomo.nix
+++ b/nixos/modules/services/web-apps/matomo.nix
@@ -2,15 +2,13 @@
 with lib;
 let
   cfg = config.services.matomo;
+  fpm = config.services.phpfpm.pools.${pool};
 
   user = "matomo";
   dataDir = "/var/lib/${user}";
   deprecatedDataDir = "/var/lib/piwik";
 
   pool = user;
-  # it's not possible to use /run/phpfpm/${pool}.sock because /run/phpfpm/ is root:root 0770,
-  # and therefore is not accessible by the web server.
-  phpSocket = "/run/phpfpm-${pool}.sock";
   phpExecutionUnit = "phpfpm-${pool}";
   databaseService = "mysql.service";
 
@@ -50,7 +48,7 @@ in {
         default = null;
         example = "lighttpd";
         description = ''
-          Name of the web server user that forwards requests to the ${phpSocket} fastcgi socket for Matomo if the nginx
+          Name of the web server user that forwards requests to <option>services.phpfpm.pools.&lt;name&gt;.socket</option> the fastcgi socket for Matomo if the nginx
           option is not used. Either this option or the nginx option is mandatory.
           If you want to use another webserver than nginx, you need to set this to that server's user
           and pass fastcgi requests to `index.php`, `matomo.php` and `piwik.php` (legacy name) to this socket.
@@ -71,25 +69,6 @@ in {
         '';
       };
 
-      phpfpmProcessManagerConfig = mkOption {
-        type = types.str;
-        default = ''
-          ; default phpfpm process manager settings
-          pm = dynamic
-          pm.max_children = 75
-          pm.start_servers = 10
-          pm.min_spare_servers = 5
-          pm.max_spare_servers = 20
-          pm.max_requests = 500
-
-          ; log worker's stdout, but this has a performance hit
-          catch_workers_output = yes
-        '';
-        description = ''
-          Settings for phpfpm's process manager. You might need to change this depending on the load for Matomo.
-        '';
-      };
-
       nginx = mkOption {
         type = types.nullOr (types.submodule (
           recursiveUpdate
@@ -233,15 +212,24 @@ in {
       else if (cfg.webServerUser != null) then cfg.webServerUser else "";
     in {
       ${pool} = {
-        listen = phpSocket;
-        extraConfig = ''
-          listen.owner = ${socketOwner}
-          listen.group = root
-          listen.mode = 0600
-          user = ${user}
-          env[PIWIK_USER_PATH] = ${dataDir}
-          ${cfg.phpfpmProcessManagerConfig}
+        inherit user;
+        phpOptions = ''
+          error_log = 'stderr'
+          log_errors = on
         '';
+        settings = mapAttrs (name: mkDefault) {
+          "listen.owner" = socketOwner;
+          "listen.group" = "root";
+          "listen.mode" = "0660";
+          "pm" = "dynamic";
+          "pm.max_children" = 75;
+          "pm.start_servers" = 10;
+          "pm.min_spare_servers" = 5;
+          "pm.max_spare_servers" = 20;
+          "pm.max_requests" = 500;
+          "catch_workers_output" = true;
+        };
+        phpEnv.PIWIK_USER_PATH = dataDir;
       };
     };
 
@@ -264,15 +252,15 @@ in {
         };
         # allow index.php for webinterface
         locations."= /index.php".extraConfig = ''
-          fastcgi_pass unix:${phpSocket};
+          fastcgi_pass unix:${fpm.socket};
         '';
         # allow matomo.php for tracking
         locations."= /matomo.php".extraConfig = ''
-          fastcgi_pass unix:${phpSocket};
+          fastcgi_pass unix:${fpm.socket};
         '';
         # allow piwik.php for tracking (deprecated name)
         locations."= /piwik.php".extraConfig = ''
-          fastcgi_pass unix:${phpSocket};
+          fastcgi_pass unix:${fpm.socket};
         '';
         # Any other attempt to access any php files is forbidden
         locations."~* ^.+\\.php$".extraConfig = ''
diff --git a/nixos/modules/services/web-apps/mediawiki.nix b/nixos/modules/services/web-apps/mediawiki.nix
index ec2568bf952d..43edc04e1a49 100644
--- a/nixos/modules/services/web-apps/mediawiki.nix
+++ b/nixos/modules/services/web-apps/mediawiki.nix
@@ -461,7 +461,10 @@ in
 
     systemd.services.httpd.after = optional (cfg.database.createLocally && cfg.database.type == "mysql") "mysql.service";
 
-    users.users.${user}.group = group;
+    users.users.${user} = {
+      group = group;
+      isSystemUser = true;
+    };
 
     environment.systemPackages = [ mediawikiScripts ];
   };
diff --git a/nixos/modules/services/web-apps/moinmoin.nix b/nixos/modules/services/web-apps/moinmoin.nix
new file mode 100644
index 000000000000..0fee64be0bb2
--- /dev/null
+++ b/nixos/modules/services/web-apps/moinmoin.nix
@@ -0,0 +1,303 @@
+{ config, lib, pkgs, ... }:
+with lib;
+
+let
+  cfg = config.services.moinmoin;
+  python = pkgs.python27;
+  pkg = python.pkgs.moinmoin;
+  dataDir = "/var/lib/moin";
+  usingGunicorn = cfg.webServer == "nginx-gunicorn" || cfg.webServer == "gunicorn";
+  usingNginx = cfg.webServer == "nginx-gunicorn";
+  user = "moin";
+  group = "moin";
+
+  uLit = s: ''u"${s}"'';
+  indentLines = n: str: concatMapStrings (line: "${fixedWidthString n " " " "}${line}\n") (splitString "\n" str);
+
+  moinCliWrapper = wikiIdent: pkgs.writeShellScriptBin "moin-${wikiIdent}" ''
+    ${pkgs.su}/bin/su -s ${pkgs.runtimeShell} -c "${pkg}/bin/moin --config-dir=/var/lib/moin/${wikiIdent}/config $*" ${user}
+  '';
+
+  wikiConfig = wikiIdent: w: ''
+    # -*- coding: utf-8 -*-
+
+    from MoinMoin.config import multiconfig, url_prefix_static
+
+    class Config(multiconfig.DefaultConfig):
+        ${optionalString (w.webLocation != "/") ''
+          url_prefix_static = '${w.webLocation}' + url_prefix_static
+        ''}
+
+        sitename = u'${w.siteName}'
+        page_front_page = u'${w.frontPage}'
+
+        data_dir = '${dataDir}/${wikiIdent}/data'
+        data_underlay_dir = '${dataDir}/${wikiIdent}/underlay'
+
+        language_default = u'${w.languageDefault}'
+        ${optionalString (w.superUsers != []) ''
+          superuser = [${concatMapStringsSep ", " uLit w.superUsers}]
+        ''}
+
+    ${indentLines 4 w.extraConfig}
+  '';
+  wikiConfigFile = name: wiki: pkgs.writeText "${name}.py" (wikiConfig name wiki);
+
+in
+{
+  options.services.moinmoin = with types; {
+    enable = mkEnableOption "MoinMoin Wiki Engine";
+
+    webServer = mkOption {
+      type = enum [ "nginx-gunicorn" "gunicorn" "none" ];
+      default = "nginx-gunicorn";
+      example = "none";
+      description = ''
+        Which web server to use to serve the wiki.
+        Use <literal>none</literal> if you want to configure this yourself.
+      '';
+    };
+
+    gunicorn.workers = mkOption {
+      type = ints.positive;
+      default = 3;
+      example = 10;
+      description = ''
+        The number of worker processes for handling requests.
+      '';
+    };
+
+    wikis = mkOption {
+      type = attrsOf (submodule ({ name, ... }: {
+        options = {
+          siteName = mkOption {
+            type = str;
+            default = "Untitled Wiki";
+            example = "ExampleWiki";
+            description = ''
+              Short description of your wiki site, displayed below the logo on each page, and
+              used in RSS documents as the channel title.
+            '';
+          };
+
+          webHost = mkOption {
+            type = str;
+            description = "Host part of the wiki URL. If undefined, the name of the attribute set will be used.";
+            example = "wiki.example.org";
+          };
+
+          webLocation = mkOption {
+            type = str;
+            default = "/";
+            example = "/moin";
+            description = "Location part of the wiki URL.";
+          };
+
+          frontPage = mkOption {
+            type = str;
+            default = "LanguageSetup";
+            example = "FrontPage";
+            description = ''
+              Front page name. Set this to something like <literal>FrontPage</literal> once languages are
+              configured.
+            '';
+          };
+
+          superUsers = mkOption {
+            type = listOf str;
+            default = [];
+            example = [ "elvis" ];
+            description = ''
+              List of trusted user names with wiki system administration super powers.
+
+              Please note that accounts for these users need to be created using the <command>moin</command> command-line utility, e.g.:
+              <command>moin-<replaceable>WIKINAME</replaceable> account create --name=<replaceable>NAME</replaceable> --email=<replaceable>EMAIL</replaceable> --password=<replaceable>PASSWORD</replaceable></command>.
+            '';
+          };
+
+          languageDefault = mkOption {
+            type = str;
+            default = "en";
+            example = "de";
+            description = "The ISO-639-1 name of the main wiki language. Languages that MoinMoin does not support are ignored.";
+          };
+
+          extraConfig = mkOption {
+            type = lines;
+            default = "";
+            example = ''
+              show_hosts = True
+              search_results_per_page = 100
+              acl_rights_default = u"Known:read,write,delete,revert All:read"
+              logo_string = u"<h2>\U0001f639</h2>"
+              theme_default = u"modernized"
+
+              user_checkbox_defaults = {'show_page_trail': 0, 'edit_on_doubleclick': 0}
+              navi_bar = [u'SomePage'] + multiconfig.DefaultConfig.navi_bar
+              actions_excluded = multiconfig.DefaultConfig.actions_excluded + ['newaccount']
+
+              mail_smarthost = "mail.example.org"
+              mail_from = u"Example.Org Wiki <wiki@example.org>"
+            '';
+            description = ''
+              Additional configuration to be appended verbatim to this wiki's config.
+
+              See <link xlink:href='http://moinmo.in/HelpOnConfiguration' /> for documentation.
+            '';
+          };
+
+        };
+        config = {
+          webHost = mkDefault name;
+        };
+      }));
+      example = literalExample ''
+        {
+          "mywiki" = {
+            siteName = "Example Wiki";
+            webHost = "wiki.example.org";
+            superUsers = [ "admin" ];
+            frontPage = "Index";
+            extraConfig = "page_category_regex = ur'(?P<all>(Category|Kategorie)(?P<key>(?!Template)\S+))'"
+          };
+        }
+      '';
+      description = ''
+        Configurations of the individual wikis. Attribute names must be valid Python
+        identifiers of the form <literal>[A-Za-z_][A-Za-z0-9_]*</literal>.
+
+        For every attribute <replaceable>WIKINAME</replaceable>, a helper script
+        moin-<replaceable>WIKINAME</replaceable> is created which runs the
+        <command>moin</command> command under the <literal>moin</literal> user (to avoid
+        file ownership issues) and with the right configuration directory passed to it.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = forEach (attrNames cfg.wikis) (wname:
+      { assertion = builtins.match "[A-Za-z_][A-Za-z0-9_]*" wname != null;
+        message = "${wname} is not valid Python identifier";
+      }
+    );
+
+    users.users = {
+      moin = {
+        description = "MoinMoin wiki";
+        home = dataDir;
+        group = group;
+        isSystemUser = true;
+      };
+    };
+
+    users.groups = {
+      moin = {
+        members = mkIf usingNginx [ config.services.nginx.user ];
+      };
+    };
+
+    environment.systemPackages = [ pkg ] ++ map moinCliWrapper (attrNames cfg.wikis);
+
+    systemd.services = mkIf usingGunicorn
+      (flip mapAttrs' cfg.wikis (wikiIdent: wiki:
+        nameValuePair "moin-${wikiIdent}"
+          {
+            description = "MoinMoin wiki ${wikiIdent} - gunicorn process";
+            wantedBy = [ "multi-user.target" ];
+            after = [ "network.target" ];
+            restartIfChanged = true;
+            restartTriggers = [ (wikiConfigFile wikiIdent wiki) ];
+
+            environment = let
+              penv = python.buildEnv.override {
+                # setuptools: https://github.com/benoitc/gunicorn/issues/1716
+                extraLibs = [ python.pkgs.gevent python.pkgs.setuptools pkg ];
+              };
+            in {
+              PYTHONPATH = "${dataDir}/${wikiIdent}/config:${penv}/${python.sitePackages}";
+            };
+
+            preStart = ''
+              umask 0007
+              rm -rf ${dataDir}/${wikiIdent}/underlay
+              cp -r ${pkg}/share/moin/underlay ${dataDir}/${wikiIdent}/
+              chmod -R u+w ${dataDir}/${wikiIdent}/underlay
+            '';
+
+            serviceConfig = {
+              User = user;
+              Group = group;
+              WorkingDirectory = "${dataDir}/${wikiIdent}";
+              ExecStart = ''${python.pkgs.gunicorn}/bin/gunicorn moin_wsgi \
+                --name gunicorn-${wikiIdent} \
+                --workers ${toString cfg.gunicorn.workers} \
+                --worker-class gevent \
+                --bind unix:/run/moin/${wikiIdent}/gunicorn.sock
+              '';
+
+              Restart = "on-failure";
+              RestartSec = "2s";
+              StartLimitIntervalSec = "30s";
+
+              StateDirectory = "moin/${wikiIdent}";
+              StateDirectoryMode = "0750";
+              RuntimeDirectory = "moin/${wikiIdent}";
+              RuntimeDirectoryMode = "0750";
+
+              NoNewPrivileges = true;
+              ProtectSystem = "strict";
+              ProtectHome = true;
+              PrivateTmp = true;
+              PrivateDevices = true;
+              PrivateNetwork = true;
+              ProtectKernelTunables = true;
+              ProtectKernelModules = true;
+              ProtectControlGroups = true;
+              RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+              RestrictNamespaces = true;
+              LockPersonality = true;
+              MemoryDenyWriteExecute = true;
+              RestrictRealtime = true;
+            };
+          }
+      ));
+
+    services.nginx = mkIf usingNginx {
+      enable = true;
+      virtualHosts = flip mapAttrs' cfg.wikis (name: w: nameValuePair w.webHost {
+        forceSSL = mkDefault true;
+        enableACME = mkDefault true;
+        locations."${w.webLocation}" = {
+          extraConfig = ''
+            proxy_set_header Host $host;
+            proxy_set_header X-Real-IP $remote_addr;
+            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+            proxy_set_header X-Forwarded-Proto $scheme;
+            proxy_set_header X-Forwarded-Host $host;
+            proxy_set_header X-Forwarded-Server $host;
+
+            proxy_pass http://unix:/run/moin/${name}/gunicorn.sock;
+          '';
+        };
+      });
+    };
+
+    systemd.tmpfiles.rules = [
+      "d  /run/moin            0750 ${user} ${group} - -"
+      "d  ${dataDir}           0550 ${user} ${group} - -"
+    ]
+    ++ (concatLists (flip mapAttrsToList cfg.wikis (wikiIdent: wiki: [
+      "d  ${dataDir}/${wikiIdent}                      0750 ${user} ${group} - -"
+      "d  ${dataDir}/${wikiIdent}/config               0550 ${user} ${group} - -"
+      "L+ ${dataDir}/${wikiIdent}/config/wikiconfig.py -    -       -        - ${wikiConfigFile wikiIdent wiki}"
+      # needed in order to pass module name to gunicorn
+      "L+ ${dataDir}/${wikiIdent}/config/moin_wsgi.py  -    -       -        - ${pkg}/share/moin/server/moin.wsgi"
+      # seed data files
+      "C  ${dataDir}/${wikiIdent}/data                 0770 ${user} ${group} - ${pkg}/share/moin/data"
+      # fix nix store permissions
+      "Z  ${dataDir}/${wikiIdent}/data                 0770 ${user} ${group} - -"
+    ])));
+  };
+
+  meta.maintainers = with lib.maintainers; [ b42 ];
+}
diff --git a/nixos/modules/services/web-apps/moodle.nix b/nixos/modules/services/web-apps/moodle.nix
index 211bc17ee192..ac59f9e0012a 100644
--- a/nixos/modules/services/web-apps/moodle.nix
+++ b/nixos/modules/services/web-apps/moodle.nix
@@ -309,7 +309,9 @@ in
 
     systemd.services.httpd.after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
 
-    users.users.${user}.group = group;
-
+    users.users.${user} = {
+      group = group;
+      isSystemUser = true;
+    };
   };
 }
diff --git a/nixos/modules/services/web-apps/nexus.nix b/nixos/modules/services/web-apps/nexus.nix
index 3af97e146d0a..d4d507362c97 100644
--- a/nixos/modules/services/web-apps/nexus.nix
+++ b/nixos/modules/services/web-apps/nexus.nix
@@ -68,6 +68,7 @@ in
           -Dkaraf.data=${cfg.home}/nexus3
           -Djava.io.tmpdir=${cfg.home}/nexus3/tmp
           -Dkaraf.startLocalConsole=false
+          -Djava.endorsed.dirs=${cfg.package}/lib/endorsed
         '';
 
         description = ''
diff --git a/nixos/modules/services/web-apps/trac.nix b/nixos/modules/services/web-apps/trac.nix
new file mode 100644
index 000000000000..207fb857438a
--- /dev/null
+++ b/nixos/modules/services/web-apps/trac.nix
@@ -0,0 +1,79 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.trac;
+
+  inherit (lib) mkEnableOption mkIf mkOption types;
+
+in {
+
+  options = {
+
+    services.trac = {
+      enable = mkEnableOption "Trac service";
+
+      listen = {
+        ip = mkOption {
+          type = types.str;
+          default = "0.0.0.0";
+          description = ''
+            IP address that Trac should listen on.
+          '';
+        };
+
+        port = mkOption {
+          type = types.port;
+          default = 8000;
+          description = ''
+            Listen port for Trac.
+          '';
+        };
+      };
+
+      dataDir = mkOption {
+        default = "/var/lib/trac";
+        type = types.path;
+        description = ''
+            The directory for storing the Trac data.
+        '';
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Open ports in the firewall for Trac.
+        '';
+      };
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.services.trac = {
+      description = "Trac server";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        DynamicUser = true;
+        StateDirectory = baseNameOf cfg.dataDir;
+        ExecStart = ''
+          ${pkgs.trac}/bin/tracd -s \
+            -b ${toString cfg.listen.ip} \
+            -p ${toString cfg.listen.port} \
+            ${cfg.dataDir}
+        '';
+      };
+      preStart = ''
+        if [ ! -e ${cfg.dataDir}/VERSION ]; then
+          ${pkgs.trac}/bin/trac-admin ${cfg.dataDir} initenv Trac "sqlite:db/trac.db"
+        fi
+      '';
+    };
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.listen.port ];
+    };
+
+  };
+}
diff --git a/nixos/modules/services/web-apps/virtlyst.nix b/nixos/modules/services/web-apps/virtlyst.nix
index e5c0bff2168a..37bdbb0e3b42 100644
--- a/nixos/modules/services/web-apps/virtlyst.nix
+++ b/nixos/modules/services/web-apps/virtlyst.nix
@@ -54,6 +54,7 @@ in
       home = stateDir;
       createHome = true;
       group = mkIf config.virtualisation.libvirtd.enable "libvirtd";
+      isSystemUser = true;
     };
 
     systemd.services.virtlyst = {
diff --git a/nixos/modules/services/web-apps/wordpress.nix b/nixos/modules/services/web-apps/wordpress.nix
index e311dd917dd0..f1370c2854b8 100644
--- a/nixos/modules/services/web-apps/wordpress.nix
+++ b/nixos/modules/services/web-apps/wordpress.nix
@@ -367,7 +367,10 @@ in
       })
     ];
 
-    users.users.${user}.group = group;
+    users.users.${user} = {
+      group = group;
+      isSystemUser = true;
+    };
 
   };
 }
diff --git a/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixos/modules/services/web-servers/apache-httpd/default.nix
index 99304d0e48ae..f5a6051b4b5f 100644
--- a/nixos/modules/services/web-servers/apache-httpd/default.nix
+++ b/nixos/modules/services/web-servers/apache-httpd/default.nix
@@ -6,6 +6,8 @@ let
 
   mainCfg = config.services.httpd;
 
+  runtimeDir = "/run/httpd";
+
   httpd = mainCfg.package.out;
 
   httpdConf = mainCfg.configFile;
@@ -27,41 +29,29 @@ let
 
   listenToString = l: "${l.ip}:${toString l.port}";
 
-  extraModules = attrByPath ["extraModules"] [] mainCfg;
-  extraForeignModules = filter isAttrs extraModules;
-  extraApacheModules = filter isString extraModules;
-
   allHosts = [mainCfg] ++ mainCfg.virtualHosts;
 
   enableSSL = any (vhost: vhost.enableSSL) allHosts;
 
+  enableUserDir = any (vhost: vhost.enableUserDir) allHosts;
 
-  # Names of modules from ${httpd}/modules that we want to load.
-  apacheModules =
-    [ # HTTP authentication mechanisms: basic and digest.
-      "auth_basic" "auth_digest"
-
-      # Authentication: is the user who he claims to be?
-      "authn_file" "authn_dbm" "authn_anon" "authn_core"
-
-      # Authorization: is the user allowed access?
-      "authz_user" "authz_groupfile" "authz_host" "authz_core"
-
-      # Other modules.
-      "ext_filter" "include" "log_config" "env" "mime_magic"
-      "cern_meta" "expires" "headers" "usertrack" /* "unique_id" */ "setenvif"
-      "mime" "dav" "status" "autoindex" "asis" "info" "dav_fs"
-      "vhost_alias" "negotiation" "dir" "imagemap" "actions" "speling"
-      "userdir" "alias" "rewrite" "proxy" "proxy_http"
-      "unixd" "cache" "cache_disk" "slotmem_shm" "socache_shmcb"
+  # NOTE: generally speaking order of modules is very important
+  modules =
+    [ # required apache modules our httpd service cannot run without
+      "authn_core" "authz_core"
+      "log_config"
+      "mime" "autoindex" "negotiation" "dir"
+      "alias" "rewrite"
+      "unixd" "slotmem_shm" "socache_shmcb"
       "mpm_${mainCfg.multiProcessingModule}"
-
-      # For compatibility with old configurations, the new module mod_access_compat is provided.
-      "access_compat"
     ]
     ++ (if mainCfg.multiProcessingModule == "prefork" then [ "cgi" ] else [ "cgid" ])
     ++ optional enableSSL "ssl"
-    ++ extraApacheModules;
+    ++ optional enableUserDir "userdir"
+    ++ optional mainCfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; }
+    ++ optional mainCfg.enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; }
+    ++ optional mainCfg.enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; }
+    ++ mainCfg.extraModules;
 
 
   allDenied = "Require all denied";
@@ -85,20 +75,22 @@ let
 
 
   browserHacks = ''
-    BrowserMatch "Mozilla/2" nokeepalive
-    BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0
-    BrowserMatch "RealPlayer 4\.0" force-response-1.0
-    BrowserMatch "Java/1\.0" force-response-1.0
-    BrowserMatch "JDK/1\.0" force-response-1.0
-    BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully
-    BrowserMatch "^WebDrive" redirect-carefully
-    BrowserMatch "^WebDAVFS/1.[012]" redirect-carefully
-    BrowserMatch "^gnome-vfs" redirect-carefully
+    <IfModule mod_setenvif.c>
+        BrowserMatch "Mozilla/2" nokeepalive
+        BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0
+        BrowserMatch "RealPlayer 4\.0" force-response-1.0
+        BrowserMatch "Java/1\.0" force-response-1.0
+        BrowserMatch "JDK/1\.0" force-response-1.0
+        BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully
+        BrowserMatch "^WebDrive" redirect-carefully
+        BrowserMatch "^WebDAVFS/1.[012]" redirect-carefully
+        BrowserMatch "^gnome-vfs" redirect-carefully
+    </IfModule>
   '';
 
 
   sslConf = ''
-    SSLSessionCache shmcb:${mainCfg.stateDir}/ssl_scache(512000)
+    SSLSessionCache shmcb:${runtimeDir}/ssl_scache(512000)
 
     Mutex posixsem
 
@@ -239,13 +231,13 @@ let
 
     ServerRoot ${httpd}
 
-    DefaultRuntimeDir ${mainCfg.stateDir}/runtime
+    DefaultRuntimeDir ${runtimeDir}/runtime
 
-    PidFile ${mainCfg.stateDir}/httpd.pid
+    PidFile ${runtimeDir}/httpd.pid
 
     ${optionalString (mainCfg.multiProcessingModule != "prefork") ''
       # mod_cgid requires this.
-      ScriptSock ${mainCfg.stateDir}/cgisock
+      ScriptSock ${runtimeDir}/cgisock
     ''}
 
     <IfModule prefork.c>
@@ -264,13 +256,12 @@ let
     Group ${mainCfg.group}
 
     ${let
-        load = {name, path}: "LoadModule ${name}_module ${path}\n";
-        allModules = map (name: {inherit name; path = "${httpd}/modules/mod_${name}.so";}) apacheModules
-          ++ optional mainCfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; }
-          ++ optional mainCfg.enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; }
-          ++ optional mainCfg.enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; }
-          ++ extraForeignModules;
-      in concatMapStrings load (unique allModules)
+        mkModule = module:
+          if isString module then { name = module; path = "${httpd}/modules/mod_${module}.so"; }
+          else if isAttrs module then { inherit (module) name path; }
+          else throw "Expecting either a string or attribute set including a name and path.";
+      in
+        concatMapStringsSep "\n" (module: "LoadModule ${module.name}_module ${module.path}") (unique (map mkModule modules))
     }
 
     AddHandler type-map var
@@ -337,6 +328,7 @@ in
 
   imports = [
     (mkRemovedOptionModule [ "services" "httpd" "extraSubservices" ] "Most existing subservices have been ported to the NixOS module system. Please update your configuration accordingly.")
+    (mkRemovedOptionModule [ "services" "httpd" "stateDir" ] "The httpd module now uses /run/httpd as a runtime directory.")
   ];
 
   ###### interface
@@ -384,7 +376,12 @@ in
       extraModules = mkOption {
         type = types.listOf types.unspecified;
         default = [];
-        example = literalExample ''[ "proxy_connect" { name = "php5"; path = "''${pkgs.php}/modules/libphp5.so"; } ]'';
+        example = literalExample ''
+          [
+            "proxy_connect"
+            { name = "jk"; path = "''${pkgs.tomcat_connectors}/modules/mod_jk.so"; }
+          ]
+        '';
         description = ''
           Additional Apache modules to be used.  These can be
           specified as a string in the case of modules distributed
@@ -431,16 +428,6 @@ in
         '';
       };
 
-      stateDir = mkOption {
-        type = types.path;
-        default = "/run/httpd";
-        description = ''
-          Directory for Apache's transient runtime state (such as PID
-          files).  It is created automatically.  Note that the default,
-          <filename>/run/httpd</filename>, is deleted at boot time.
-        '';
-      };
-
       virtualHosts = mkOption {
         type = types.listOf (types.submodule (
           { options = import ./per-server-options.nix {
@@ -595,6 +582,28 @@ in
         date.timezone = "${config.time.timeZone}"
       '';
 
+    services.httpd.extraModules = mkBefore [
+      # HTTP authentication mechanisms: basic and digest.
+      "auth_basic" "auth_digest"
+
+      # Authentication: is the user who he claims to be?
+      "authn_file" "authn_dbm" "authn_anon"
+
+      # Authorization: is the user allowed access?
+      "authz_user" "authz_groupfile" "authz_host"
+
+      # Other modules.
+      "ext_filter" "include" "env" "mime_magic"
+      "cern_meta" "expires" "headers" "usertrack" "setenvif"
+      "dav" "status" "asis" "info" "dav_fs"
+      "vhost_alias" "imagemap" "actions" "speling"
+      "proxy" "proxy_http"
+      "cache" "cache_disk"
+
+      # For compatibility with old configurations, the new module mod_access_compat is provided.
+      "access_compat"
+    ];
+
     systemd.services.httpd =
       { description = "Apache HTTPD";
 
@@ -611,12 +620,6 @@ in
 
         preStart =
           ''
-            mkdir -m 0750 -p ${mainCfg.stateDir}
-            [ $(id -u) != 0 ] || chown root.${mainCfg.group} ${mainCfg.stateDir}
-
-            mkdir -m 0750 -p "${mainCfg.stateDir}/runtime"
-            [ $(id -u) != 0 ] || chown root.${mainCfg.group} "${mainCfg.stateDir}/runtime"
-
             mkdir -m 0700 -p ${mainCfg.logDir}
 
             # Get rid of old semaphores.  These tend to accumulate across
@@ -630,10 +633,13 @@ in
         serviceConfig.ExecStart = "@${httpd}/bin/httpd httpd -f ${httpdConf}";
         serviceConfig.ExecStop = "${httpd}/bin/httpd -f ${httpdConf} -k graceful-stop";
         serviceConfig.ExecReload = "${httpd}/bin/httpd -f ${httpdConf} -k graceful";
+        serviceConfig.Group = mainCfg.group;
         serviceConfig.Type = "forking";
-        serviceConfig.PIDFile = "${mainCfg.stateDir}/httpd.pid";
+        serviceConfig.PIDFile = "${runtimeDir}/httpd.pid";
         serviceConfig.Restart = "always";
         serviceConfig.RestartSec = "5s";
+        serviceConfig.RuntimeDirectory = "httpd httpd/runtime";
+        serviceConfig.RuntimeDirectoryMode = "0750";
       };
 
   };
diff --git a/nixos/modules/services/web-servers/hitch/default.nix b/nixos/modules/services/web-servers/hitch/default.nix
index a6c4cbea1225..1812f225b74d 100644
--- a/nixos/modules/services/web-servers/hitch/default.nix
+++ b/nixos/modules/services/web-servers/hitch/default.nix
@@ -102,7 +102,10 @@ with lib;
 
     environment.systemPackages = [ pkgs.hitch ];
 
-    users.users.hitch.group = "hitch";
+    users.users.hitch = {
+      group = "hitch";
+      isSystemUser = true;
+    };
     users.groups.hitch = {};
   };
 }
diff --git a/nixos/modules/services/web-servers/traefik.nix b/nixos/modules/services/web-servers/traefik.nix
index 8de7df0d446c..5b0fc467ea46 100644
--- a/nixos/modules/services/web-servers/traefik.nix
+++ b/nixos/modules/services/web-servers/traefik.nix
@@ -117,6 +117,7 @@ in {
       group = "traefik";
       home = cfg.dataDir;
       createHome = true;
+      isSystemUser = true;
     };
 
     users.groups.traefik = {};
diff --git a/nixos/modules/services/web-servers/unit/default.nix b/nixos/modules/services/web-servers/unit/default.nix
index a4a9d370d644..32f6d475b34e 100644
--- a/nixos/modules/services/web-servers/unit/default.nix
+++ b/nixos/modules/services/web-servers/unit/default.nix
@@ -116,6 +116,7 @@ in {
     users.users = optionalAttrs (cfg.user == "unit") (singleton {
       name = "unit";
       group = cfg.group;
+      isSystemUser = true;
     });
 
     users.groups = optionalAttrs (cfg.group == "unit") (singleton {
diff --git a/nixos/modules/services/x11/display-managers/sddm.nix b/nixos/modules/services/x11/display-managers/sddm.nix
index 8847acb0c604..899dd8665a25 100644
--- a/nixos/modules/services/x11/display-managers/sddm.nix
+++ b/nixos/modules/services/x11/display-managers/sddm.nix
@@ -14,7 +14,7 @@ let
   xserverWrapper = pkgs.writeScript "xserver-wrapper" ''
     #!/bin/sh
     ${concatMapStrings (n: "export ${n}=\"${getAttr n xEnv}\"\n") (attrNames xEnv)}
-    exec systemd-cat ${dmcfg.xserverBin} ${toString dmcfg.xserverArgs} "$@"
+    exec systemd-cat -t xserver-wrapper ${dmcfg.xserverBin} ${toString dmcfg.xserverArgs} "$@"
   '';
 
   Xsetup = pkgs.writeScript "Xsetup" ''
diff --git a/nixos/modules/services/x11/hardware/digimend.nix b/nixos/modules/services/x11/hardware/digimend.nix
new file mode 100644
index 000000000000..a9f5640905aa
--- /dev/null
+++ b/nixos/modules/services/x11/hardware/digimend.nix
@@ -0,0 +1,43 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.xserver.digimend;
+
+  pkg = config.boot.kernelPackages.digimend;
+
+in
+
+{
+
+  options = {
+
+    services.xserver.digimend = {
+
+      enable = mkOption {
+        default = false;
+        description = ''
+          Whether to enable the digimend drivers for Huion/XP-Pen/etc. tablets.
+        '';
+      };
+
+    };
+
+  };
+
+
+  config = mkIf cfg.enable {
+
+    # digimend drivers use xsetwacom and wacom X11 drivers
+    services.xserver.wacom.enable = true;
+
+    boot.extraModulePackages = [ pkg ];
+
+    environment.etc."X11/xorg.conf.d/50-digimend.conf".source =
+      "${pkg}/usr/share/X11/xorg.conf.d/50-digimend.conf";
+
+  };
+
+}
diff --git a/nixos/modules/services/x11/hardware/libinput.nix b/nixos/modules/services/x11/hardware/libinput.nix
index bd289976532b..4a25232383d3 100644
--- a/nixos/modules/services/x11/hardware/libinput.nix
+++ b/nixos/modules/services/x11/hardware/libinput.nix
@@ -122,7 +122,7 @@ in {
         description =
           ''
             Specify the scrolling method: <literal>twofinger</literal>, <literal>edge</literal>,
-            or <literal>none</literal>
+            <literal>button</literal>, or <literal>none</literal>
           '';
       };
 
diff --git a/nixos/modules/services/x11/window-managers/xmonad.nix b/nixos/modules/services/x11/window-managers/xmonad.nix
index 0e1314122767..30c59b88f82f 100644
--- a/nixos/modules/services/x11/window-managers/xmonad.nix
+++ b/nixos/modules/services/x11/window-managers/xmonad.nix
@@ -86,7 +86,7 @@ in
           ${xmonadBin}
           waitPID=$!
         '' else ''
-          ${xmonad}/bin/xmonad &
+          systemd-cat -t xmonad ${xmonad}/bin/xmonad &
           waitPID=$!
         '';
       }];
diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix
index f2060e21509c..85a106527fe0 100644
--- a/nixos/modules/system/boot/networkd.nix
+++ b/nixos/modules/system/boot/networkd.nix
@@ -187,7 +187,7 @@ let
     # Note: For DHCP the values both, none, v4, v6 are deprecated
     (assertValueOneOf "DHCP" ["yes" "no" "ipv4" "ipv6" "both" "none" "v4" "v6"])
     (assertValueOneOf "DHCPServer" boolValues)
-    (assertValueOneOf "LinkLocalAddressing" ["yes" "no" "ipv4" "ipv6"])
+    (assertValueOneOf "LinkLocalAddressing" ["yes" "no" "ipv4" "ipv6" "ipv4-fallback" "fallback"])
     (assertValueOneOf "IPv4LLRoute" boolValues)
     (assertValueOneOf "LLMNR" ["yes" "resolve" "no"])
     (assertValueOneOf "MulticastDNS" ["yes" "resolve" "no"])
diff --git a/nixos/modules/system/boot/plymouth.nix b/nixos/modules/system/boot/plymouth.nix
index adca3c3f66e7..23fce22366d8 100644
--- a/nixos/modules/system/boot/plymouth.nix
+++ b/nixos/modules/system/boot/plymouth.nix
@@ -25,6 +25,7 @@ let
     [Daemon]
     ShowDelay=0
     Theme=${cfg.theme}
+    ${cfg.extraConfig}
   '';
 
 in
@@ -65,6 +66,15 @@ in
         '';
       };
 
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Literal string to append to <literal>configFile</literal>
+          and the config file generated by the plymouth module.
+        '';
+      };
+
     };
 
   };
diff --git a/nixos/modules/system/boot/systemd-unit-options.nix b/nixos/modules/system/boot/systemd-unit-options.nix
index c1f2c98afcd8..bee21f1a8f36 100644
--- a/nixos/modules/system/boot/systemd-unit-options.nix
+++ b/nixos/modules/system/boot/systemd-unit-options.nix
@@ -24,7 +24,7 @@ in rec {
       in
         if isList (head defs'')
         then concatLists defs''
-        else mergeOneOption loc defs';
+        else mergeEqualOption loc defs';
   };
 
   sharedOptions = {
diff --git a/nixos/modules/virtualisation/amazon-image.nix b/nixos/modules/virtualisation/amazon-image.nix
index aadfc5add350..20d48add7129 100644
--- a/nixos/modules/virtualisation/amazon-image.nix
+++ b/nixos/modules/virtualisation/amazon-image.nix
@@ -135,6 +135,9 @@ in
     services.openssh.enable = true;
     services.openssh.permitRootLogin = "prohibit-password";
 
+    # Creates symlinks for block device names.
+    services.udev.packages = [ pkgs.ec2-utils ];
+
     # Force getting the hostname from EC2.
     networking.hostName = mkDefault "";
 
diff --git a/nixos/modules/virtualisation/ec2-amis.nix b/nixos/modules/virtualisation/ec2-amis.nix
index f640bb21b133..3b4e55d39d7b 100644
--- a/nixos/modules/virtualisation/ec2-amis.nix
+++ b/nixos/modules/virtualisation/ec2-amis.nix
@@ -291,5 +291,21 @@ let self = {
   "19.03".sa-east-1.hvm-ebs = "ami-0c6a43c6e0ad1f4e2";
   "19.03".ap-south-1.hvm-ebs = "ami-0303deb1b5890f878";
 
-  latest = self."19.03";
+  # 19.09.981.205691b7cbe
+  "19.09".eu-west-1.hvm-ebs = "ami-0ebd3156e21e9642f";
+  "19.09".eu-west-2.hvm-ebs = "ami-02a2b5480a79084b7";
+  "19.09".eu-west-3.hvm-ebs = "ami-09aa175c7588734f7";
+  "19.09".eu-central-1.hvm-ebs = "ami-00a7fafd7e237a330";
+  "19.09".us-east-1.hvm-ebs = "ami-00a8eeaf232a74f84";
+  "19.09".us-east-2.hvm-ebs = "ami-093efd3a57a1e03a8";
+  "19.09".us-west-1.hvm-ebs = "ami-0913e9a2b677fac30";
+  "19.09".us-west-2.hvm-ebs = "ami-02d9a19f77b47882a";
+  "19.09".ca-central-1.hvm-ebs = "ami-0627dd3f7b3627a29";
+  "19.09".ap-southeast-1.hvm-ebs = "ami-083614e4d08f2164d";
+  "19.09".ap-southeast-2.hvm-ebs = "ami-0048c704185ded6dc";
+  "19.09".ap-northeast-1.hvm-ebs = "ami-0329e7fc2d7f60bd0";
+  "19.09".ap-northeast-2.hvm-ebs = "ami-03d4ae7d0b5fc364f";
+  "19.09".ap-south-1.hvm-ebs = "ami-0b599690b35aeef23";
+
+  latest = self."19.09";
 }; in self
diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix
index ed3431554be4..e313d2b411bb 100644
--- a/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixos/modules/virtualisation/qemu-vm.nix
@@ -23,24 +23,56 @@ let
 
   cfg = config.virtualisation;
 
-  qemuGraphics = lib.optionalString (!cfg.graphics) "-nographic";
-
   consoles = lib.concatMapStringsSep " " (c: "console=${c}") cfg.qemu.consoles;
 
-  # XXX: This is very ugly and in the future we really should use attribute
-  # sets to build ALL of the QEMU flags instead of this mixed mess of Nix
-  # expressions and shell script stuff.
-  mkDiskIfaceDriveFlag = idx: driveArgs: let
-    inherit (cfg.qemu) diskInterface;
-    # The drive identifier created by incrementing the index by one using the
-    # shell.
-    drvId = "drive$((${idx} + 1))";
-    # NOTE: DO NOT shell escape, because this may contain shell variables.
-    commonArgs = "index=${idx},id=${drvId},${driveArgs}";
-    isSCSI = diskInterface == "scsi";
-    devArgs = "${diskInterface}-hd,drive=${drvId}";
-    args = "-drive ${commonArgs},if=none -device lsi53c895a -device ${devArgs}";
-  in if isSCSI then args else "-drive ${commonArgs},if=${diskInterface}";
+  driveOpts = { ... }: {
+
+    options = {
+
+      file = mkOption {
+        type = types.str;
+        description = "The file image used for this drive.";
+      };
+
+      driveExtraOpts = mkOption {
+        type = types.attrsOf types.str;
+        default = {};
+        description = "Extra options passed to drive flag.";
+      };
+
+      deviceExtraOpts = mkOption {
+        type = types.attrsOf types.str;
+        default = {};
+        description = "Extra options passed to device flag.";
+      };
+
+    };
+
+  };
+
+  driveCmdline = idx: { file, driveExtraOpts, deviceExtraOpts, ... }:
+    let
+      drvId = "drive${toString idx}";
+      mkKeyValue = generators.mkKeyValueDefault {} "=";
+      mkOpts = opts: concatStringsSep "," (mapAttrsToList mkKeyValue opts);
+      driveOpts = mkOpts (driveExtraOpts // {
+        index = idx;
+        id = drvId;
+        "if" = "none";
+        inherit file;
+      });
+      deviceOpts = mkOpts (deviceExtraOpts // {
+        drive = drvId;
+      });
+      device =
+        if cfg.qemu.diskInterface == "scsi" then
+          "-device lsi53c895a -device scsi-hd,${deviceOpts}"
+        else
+          "-device virtio-blk-pci,${deviceOpts}";
+    in
+      "-drive ${driveOpts} ${device}";
+
+  drivesCmdLine = drives: concatStringsSep " " (imap1 driveCmdline drives);
 
   # Shell script to start the VM.
   startVM =
@@ -77,13 +109,11 @@ let
       ''}
 
       cd $TMPDIR
-      idx=2
-      extraDisks=""
+      idx=0
       ${flip concatMapStrings cfg.emptyDiskImages (size: ''
         if ! test -e "empty$idx.qcow2"; then
             ${qemu}/bin/qemu-img create -f qcow2 "empty$idx.qcow2" "${toString size}M"
         fi
-        extraDisks="$extraDisks ${mkDiskIfaceDriveFlag "$idx" "file=$(pwd)/empty$idx.qcow2,werror=report"}"
         idx=$((idx + 1))
       '')}
 
@@ -97,21 +127,7 @@ let
           -virtfs local,path=/nix/store,security_model=none,mount_tag=store \
           -virtfs local,path=$TMPDIR/xchg,security_model=none,mount_tag=xchg \
           -virtfs local,path=''${SHARED_DIR:-$TMPDIR/xchg},security_model=none,mount_tag=shared \
-          ${if cfg.useBootLoader then ''
-            ${mkDiskIfaceDriveFlag "0" "file=$NIX_DISK_IMAGE,cache=writeback,werror=report"} \
-            ${mkDiskIfaceDriveFlag "1" "file=$TMPDIR/disk.img,media=disk"} \
-            ${if cfg.useEFIBoot then ''
-              -pflash $TMPDIR/bios.bin \
-            '' else ''
-            ''}
-          '' else ''
-            ${mkDiskIfaceDriveFlag "0" "file=$NIX_DISK_IMAGE,cache=writeback,werror=report"} \
-            -kernel ${config.system.build.toplevel}/kernel \
-            -initrd ${config.system.build.toplevel}/initrd \
-            -append "$(cat ${config.system.build.toplevel}/kernel-params) init=${config.system.build.toplevel}/init regInfo=${regInfo}/registration ${consoles} $QEMU_KERNEL_PARAMS" \
-          ''} \
-          $extraDisks \
-          ${qemuGraphics} \
+          ${drivesCmdLine config.virtualisation.qemu.drives} \
           ${toString config.virtualisation.qemu.options} \
           $QEMU_OPTS \
           "$@"
@@ -367,6 +383,12 @@ in
           '';
         };
 
+      drives =
+        mkOption {
+          type = types.listOf (types.submodule driveOpts);
+          description = "Drives passed to qemu.";
+        };
+
       diskInterface =
         mkOption {
           default = "virtio";
@@ -476,8 +498,49 @@ in
 
     # FIXME: Consolidate this one day.
     virtualisation.qemu.options = mkMerge [
-      (mkIf (pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) [ "-vga std" "-usb" "-device usb-tablet,bus=usb-bus.0" ])
-      (mkIf (pkgs.stdenv.isAarch32 || pkgs.stdenv.isAarch64) [ "-device virtio-gpu-pci" "-device usb-ehci,id=usb0" "-device usb-kbd" "-device usb-tablet" ])
+      (mkIf (pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) [
+        "-vga std" "-usb" "-device usb-tablet,bus=usb-bus.0"
+      ])
+      (mkIf (pkgs.stdenv.isAarch32 || pkgs.stdenv.isAarch64) [
+        "-device virtio-gpu-pci" "-device usb-ehci,id=usb0" "-device usb-kbd" "-device usb-tablet"
+      ])
+      (mkIf (!cfg.useBootLoader) [
+        "-kernel ${config.system.build.toplevel}/kernel"
+        "-initrd ${config.system.build.toplevel}/initrd"
+        ''-append "$(cat ${config.system.build.toplevel}/kernel-params) init=${config.system.build.toplevel}/init regInfo=${regInfo}/registration ${consoles} $QEMU_KERNEL_PARAMS"''
+      ])
+      (mkIf cfg.useEFIBoot [
+        "-pflash $TMPDIR/bios.bin"
+      ])
+      (mkIf (!cfg.graphics) [
+        "-nographic"
+      ])
+    ];
+
+    virtualisation.qemu.drives = mkMerge [
+      (mkIf cfg.useBootLoader [
+        {
+          file = "$NIX_DISK_IMAGE";
+          driveExtraOpts.cache = "writeback";
+          driveExtraOpts.werror = "report";
+        }
+        {
+          file = "$TMPDIR/disk.img";
+          driveExtraOpts.media = "disk";
+          deviceExtraOpts.bootindex = "1";
+        }
+      ])
+      (mkIf (!cfg.useBootLoader) [
+        {
+          file = "$NIX_DISK_IMAGE";
+          driveExtraOpts.cache = "writeback";
+          driveExtraOpts.werror = "report";
+        }
+      ])
+      (imap0 (idx: _: {
+        file = "$(pwd)/empty${toString idx}.qcow2";
+        driveExtraOpts.werror = "report";
+      }) cfg.emptyDiskImages)
     ];
 
     # Mount the host filesystem via 9P, and bind-mount the Nix store