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/fontconfig-penultimate.nix2
-rw-r--r--nixos/modules/config/fonts/fontconfig.nix2
-rw-r--r--nixos/modules/config/malloc.nix97
-rw-r--r--nixos/modules/config/pulseaudio.nix3
-rw-r--r--nixos/modules/config/sysctl.nix2
-rw-r--r--nixos/modules/config/xdg/mime.nix2
-rw-r--r--nixos/modules/hardware/all-firmware.nix3
-rw-r--r--nixos/modules/hardware/video/nvidia.nix9
-rw-r--r--nixos/modules/installer/cd-dvd/iso-image.nix4
-rw-r--r--nixos/modules/installer/cd-dvd/system-tarball.nix2
-rw-r--r--nixos/modules/installer/netboot/netboot.nix3
-rw-r--r--nixos/modules/installer/tools/nixos-generate-config.pl20
-rw-r--r--nixos/modules/installer/tools/tools.nix2
-rw-r--r--nixos/modules/misc/ids.nix14
-rw-r--r--nixos/modules/misc/nixpkgs.nix7
-rw-r--r--nixos/modules/module-list.nix23
-rw-r--r--nixos/modules/profiles/hardened.nix2
-rw-r--r--nixos/modules/programs/bash/bash.nix2
-rw-r--r--nixos/modules/programs/browserpass.nix10
-rw-r--r--nixos/modules/programs/clickshare.nix21
-rw-r--r--nixos/modules/programs/dconf.nix2
-rw-r--r--nixos/modules/programs/fish.nix2
-rw-r--r--nixos/modules/programs/ssmtp.nix2
-rw-r--r--nixos/modules/programs/sway.nix6
-rw-r--r--nixos/modules/programs/tmux.nix2
-rw-r--r--nixos/modules/programs/xss-lock.nix19
-rw-r--r--nixos/modules/programs/zmap.nix18
-rw-r--r--nixos/modules/programs/zsh/zsh.nix6
-rw-r--r--nixos/modules/rename.nix11
-rw-r--r--nixos/modules/security/apparmor.nix12
-rw-r--r--nixos/modules/security/misc.nix30
-rw-r--r--nixos/modules/security/rngd.nix38
-rw-r--r--nixos/modules/services/admin/oxidized.nix2
-rw-r--r--nixos/modules/services/amqp/rabbitmq.nix5
-rw-r--r--nixos/modules/services/audio/jack.nix290
-rw-r--r--nixos/modules/services/audio/liquidsoap.nix7
-rw-r--r--nixos/modules/services/audio/mpd.nix10
-rw-r--r--nixos/modules/services/backup/automysqlbackup.nix115
-rw-r--r--nixos/modules/services/backup/mysql-backup.nix8
-rw-r--r--nixos/modules/services/backup/postgresql-backup.nix11
-rw-r--r--nixos/modules/services/backup/znapzend.nix37
-rw-r--r--nixos/modules/services/cluster/kubernetes/addon-manager.nix85
-rw-r--r--nixos/modules/services/cluster/kubernetes/addons/dashboard.nix36
-rw-r--r--nixos/modules/services/cluster/kubernetes/addons/dns.nix8
-rw-r--r--nixos/modules/services/cluster/kubernetes/apiserver.nix77
-rw-r--r--nixos/modules/services/cluster/kubernetes/controller-manager.nix41
-rw-r--r--nixos/modules/services/cluster/kubernetes/default.nix25
-rw-r--r--nixos/modules/services/cluster/kubernetes/flannel.nix70
-rw-r--r--nixos/modules/services/cluster/kubernetes/kubelet.nix92
-rw-r--r--nixos/modules/services/cluster/kubernetes/pki.nix171
-rw-r--r--nixos/modules/services/cluster/kubernetes/proxy.nix39
-rw-r--r--nixos/modules/services/cluster/kubernetes/scheduler.nix36
-rw-r--r--nixos/modules/services/continuous-integration/buildkite-agent.nix2
-rw-r--r--nixos/modules/services/continuous-integration/jenkins/default.nix2
-rw-r--r--nixos/modules/services/databases/cassandra.nix22
-rw-r--r--nixos/modules/services/databases/clickhouse.nix17
-rw-r--r--nixos/modules/services/databases/cockroachdb.nix4
-rw-r--r--nixos/modules/services/databases/couchdb.nix21
-rw-r--r--nixos/modules/services/databases/foundationdb.nix23
-rw-r--r--nixos/modules/services/databases/influxdb.nix13
-rw-r--r--nixos/modules/services/databases/memcached.nix8
-rw-r--r--nixos/modules/services/databases/mongodb.nix67
-rw-r--r--nixos/modules/services/databases/mysql.nix126
-rw-r--r--nixos/modules/services/databases/openldap.nix33
-rw-r--r--nixos/modules/services/databases/pgmanage.nix4
-rw-r--r--nixos/modules/services/databases/postgresql.nix91
-rw-r--r--nixos/modules/services/databases/stanchion.nix20
-rw-r--r--nixos/modules/services/desktops/deepin/dde-daemon.nix41
-rw-r--r--nixos/modules/services/desktops/deepin/deepin-menu.nix29
-rw-r--r--nixos/modules/services/desktops/deepin/deepin.nix121
-rw-r--r--nixos/modules/services/desktops/geoclue2.nix201
-rw-r--r--nixos/modules/services/desktops/gnome3/gnome-keyring.nix5
-rw-r--r--nixos/modules/services/desktops/gnome3/gnome-settings-daemon.nix2
-rw-r--r--nixos/modules/services/games/factorio.nix44
-rw-r--r--nixos/modules/services/games/minecraft-server.nix4
-rw-r--r--nixos/modules/services/hardware/bluetooth.nix2
-rw-r--r--nixos/modules/services/hardware/throttled.nix21
-rw-r--r--nixos/modules/services/hardware/vdr.nix4
-rw-r--r--nixos/modules/services/logging/journalbeat.nix43
-rw-r--r--nixos/modules/services/logging/journalwatch.nix42
-rw-r--r--nixos/modules/services/logging/logcheck.nix2
-rw-r--r--nixos/modules/services/mail/dovecot.nix12
-rw-r--r--nixos/modules/services/mail/mailcatcher.nix2
-rw-r--r--nixos/modules/services/mail/nullmailer.nix6
-rw-r--r--nixos/modules/services/mail/offlineimap.nix2
-rw-r--r--nixos/modules/services/mail/rss2email.nix10
-rw-r--r--nixos/modules/services/misc/beanstalkd.nix2
-rw-r--r--nixos/modules/services/misc/bepasty.nix2
-rw-r--r--nixos/modules/services/misc/emby.nix76
-rw-r--r--nixos/modules/services/misc/errbot.nix2
-rw-r--r--nixos/modules/services/misc/etcd.nix10
-rw-r--r--nixos/modules/services/misc/ethminer.nix115
-rw-r--r--nixos/modules/services/misc/gitea.nix105
-rw-r--r--nixos/modules/services/misc/home-assistant.nix35
-rw-r--r--nixos/modules/services/misc/jackett.nix13
-rw-r--r--nixos/modules/services/misc/jellyfin.nix54
-rw-r--r--nixos/modules/services/misc/lidarr.nix7
-rw-r--r--nixos/modules/services/misc/matrix-synapse.nix5
-rw-r--r--nixos/modules/services/misc/mesos-master.nix7
-rw-r--r--nixos/modules/services/misc/mesos-slave.nix7
-rw-r--r--nixos/modules/services/misc/nix-daemon.nix24
-rw-r--r--nixos/modules/services/misc/nzbget.nix96
-rw-r--r--nixos/modules/services/misc/octoprint.nix2
-rw-r--r--nixos/modules/services/misc/packagekit.nix51
-rw-r--r--nixos/modules/services/misc/paperless.nix185
-rw-r--r--nixos/modules/services/misc/plex.nix104
-rw-r--r--nixos/modules/services/misc/radarr.nix13
-rw-r--r--nixos/modules/services/misc/sonarr.nix13
-rw-r--r--nixos/modules/services/misc/sssd.nix2
-rw-r--r--nixos/modules/services/misc/taskserver/default.nix4
-rw-r--r--nixos/modules/services/misc/zoneminder.nix4
-rw-r--r--nixos/modules/services/misc/zookeeper.nix7
-rw-r--r--nixos/modules/services/monitoring/collectd.nix11
-rw-r--r--nixos/modules/services/monitoring/grafana-reporter.nix2
-rw-r--r--nixos/modules/services/monitoring/grafana.nix2
-rw-r--r--nixos/modules/services/monitoring/graphite.nix6
-rw-r--r--nixos/modules/services/monitoring/munin.nix2
-rw-r--r--nixos/modules/services/monitoring/prometheus/default.nix640
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters.nix1
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix2
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix42
-rw-r--r--nixos/modules/services/monitoring/prometheus/pushgateway.nix166
-rw-r--r--nixos/modules/services/monitoring/vnstat.nix19
-rw-r--r--nixos/modules/services/network-filesystems/ipfs.nix14
-rw-r--r--nixos/modules/services/network-filesystems/tahoe.nix13
-rw-r--r--nixos/modules/services/networking/bind.nix2
-rw-r--r--nixos/modules/services/networking/bird.nix21
-rw-r--r--nixos/modules/services/networking/bitcoind.nix195
-rw-r--r--nixos/modules/services/networking/btsync.nix324
-rw-r--r--nixos/modules/services/networking/ejabberd.nix28
-rw-r--r--nixos/modules/services/networking/flannel.nix2
-rw-r--r--nixos/modules/services/networking/hostapd.nix8
-rw-r--r--nixos/modules/services/networking/i2pd.nix12
-rw-r--r--nixos/modules/services/networking/miredo.nix2
-rw-r--r--nixos/modules/services/networking/monero.nix2
-rw-r--r--nixos/modules/services/networking/mosquitto.nix4
-rw-r--r--nixos/modules/services/networking/mtprotoproxy.nix110
-rw-r--r--nixos/modules/services/networking/murmur.nix28
-rw-r--r--nixos/modules/services/networking/mxisd.nix11
-rw-r--r--nixos/modules/services/networking/namecoind.nix3
-rw-r--r--nixos/modules/services/networking/networkmanager.nix4
-rw-r--r--nixos/modules/services/networking/nullidentdmod.nix2
-rw-r--r--nixos/modules/services/networking/openvpn.nix2
-rw-r--r--nixos/modules/services/networking/prosody.nix8
-rw-r--r--nixos/modules/services/networking/smokeping.nix3
-rw-r--r--nixos/modules/services/networking/softether.nix2
-rw-r--r--nixos/modules/services/networking/ssh/sshd.nix11
-rw-r--r--nixos/modules/services/networking/strongswan-swanctl/param-constructors.nix2
-rw-r--r--nixos/modules/services/networking/strongswan-swanctl/param-lib.nix4
-rw-r--r--nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix50
-rw-r--r--nixos/modules/services/networking/syncthing.nix281
-rw-r--r--nixos/modules/services/networking/tedicross.nix100
-rw-r--r--nixos/modules/services/networking/tinc.nix1
-rw-r--r--nixos/modules/services/networking/toxvpn.nix2
-rw-r--r--nixos/modules/services/networking/wg-quick.nix312
-rw-r--r--nixos/modules/services/networking/wireguard.nix179
-rw-r--r--nixos/modules/services/networking/xinetd.nix2
-rw-r--r--nixos/modules/services/networking/zeronet.nix13
-rw-r--r--nixos/modules/services/networking/znc/default.nix4
-rw-r--r--nixos/modules/services/printing/cupsd.nix4
-rw-r--r--nixos/modules/services/search/kibana.nix4
-rw-r--r--nixos/modules/services/search/solr.nix2
-rw-r--r--nixos/modules/services/security/bitwarden_rs/backup.sh17
-rw-r--r--nixos/modules/services/security/bitwarden_rs/default.nix126
-rw-r--r--nixos/modules/services/security/fprintd.nix35
-rw-r--r--nixos/modules/services/security/munge.nix13
-rw-r--r--nixos/modules/services/security/oauth2_proxy.nix8
-rw-r--r--nixos/modules/services/security/sks.nix24
-rw-r--r--nixos/modules/services/security/tor.nix11
-rw-r--r--nixos/modules/services/security/vault.nix9
-rw-r--r--nixos/modules/services/system/earlyoom.nix15
-rw-r--r--nixos/modules/services/system/localtime.nix8
-rw-r--r--nixos/modules/services/torrent/deluge.nix152
-rw-r--r--nixos/modules/services/torrent/peerflix.nix6
-rw-r--r--nixos/modules/services/web-apps/codimd.nix5
-rw-r--r--nixos/modules/services/web-apps/cryptpad.nix54
-rw-r--r--nixos/modules/services/web-apps/documize.nix171
-rw-r--r--nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix2
-rw-r--r--nixos/modules/services/web-apps/limesurvey.nix288
-rw-r--r--nixos/modules/services/web-apps/miniflux.nix2
-rw-r--r--nixos/modules/services/web-apps/nextcloud.nix22
-rw-r--r--nixos/modules/services/web-apps/nextcloud.xml6
-rw-r--r--nixos/modules/services/web-apps/nexus.nix5
-rw-r--r--nixos/modules/services/web-apps/restya-board.nix12
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/default.nix57
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/limesurvey.nix196
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/wordpress.nix2
-rw-r--r--nixos/modules/services/web-servers/hydron.nix46
-rw-r--r--nixos/modules/services/web-servers/lighttpd/cgit.nix2
-rw-r--r--nixos/modules/services/web-servers/minio.nix13
-rw-r--r--nixos/modules/services/web-servers/nginx/default.nix15
-rw-r--r--nixos/modules/services/web-servers/traefik.nix10
-rw-r--r--nixos/modules/services/web-servers/winstone.nix129
-rw-r--r--nixos/modules/services/x11/colord.nix1
-rw-r--r--nixos/modules/services/x11/compton.nix56
-rw-r--r--nixos/modules/services/x11/desktop-managers/enlightenment.nix10
-rw-r--r--nixos/modules/services/x11/desktop-managers/gnome3.nix25
-rw-r--r--nixos/modules/services/x11/desktop-managers/lumina.nix15
-rw-r--r--nixos/modules/services/x11/desktop-managers/lxqt.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/mate.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/maxx.nix8
-rw-r--r--nixos/modules/services/x11/desktop-managers/pantheon.nix11
-rw-r--r--nixos/modules/services/x11/desktop-managers/plasma5.nix3
-rw-r--r--nixos/modules/services/x11/desktop-managers/xterm.nix2
-rw-r--r--nixos/modules/services/x11/display-managers/gdm.nix22
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm.nix5
-rw-r--r--nixos/modules/services/x11/display-managers/sddm.nix9
-rw-r--r--nixos/modules/services/x11/display-managers/startx.nix4
-rw-r--r--nixos/modules/services/x11/display-managers/xpra.nix2
-rw-r--r--nixos/modules/services/x11/redshift.nix8
-rw-r--r--nixos/modules/services/x11/window-managers/default.nix1
-rw-r--r--nixos/modules/services/x11/window-managers/i3.nix5
-rw-r--r--nixos/modules/services/x11/window-managers/leftwm.nix25
-rw-r--r--nixos/modules/services/x11/window-managers/xmonad.nix33
-rw-r--r--nixos/modules/services/x11/xserver.nix3
-rw-r--r--nixos/modules/system/activation/switch-to-configuration.pl18
-rw-r--r--nixos/modules/system/boot/binfmt.nix314
-rw-r--r--nixos/modules/system/boot/kernel.nix18
-rw-r--r--nixos/modules/system/boot/kexec.nix12
-rw-r--r--nixos/modules/system/boot/loader/generations-dir/generations-dir.nix4
-rw-r--r--nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix2
-rw-r--r--nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix4
-rw-r--r--nixos/modules/system/boot/loader/grub/grub.nix1
-rw-r--r--nixos/modules/system/boot/loader/init-script/init-script.nix4
-rw-r--r--nixos/modules/system/boot/loader/raspberrypi/uboot-builder.nix7
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py29
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix20
-rw-r--r--nixos/modules/system/boot/luksroot.nix179
-rw-r--r--nixos/modules/system/boot/networkd.nix2
-rw-r--r--nixos/modules/system/boot/resolved.nix5
-rw-r--r--nixos/modules/system/boot/stage-1-init.sh2
-rw-r--r--nixos/modules/system/boot/stage-1.nix2
-rw-r--r--nixos/modules/tasks/filesystems/btrfs.nix2
-rw-r--r--nixos/modules/tasks/filesystems/zfs.nix6
-rw-r--r--nixos/modules/virtualisation/containers.nix51
-rw-r--r--nixos/modules/virtualisation/docker-containers.nix19
-rw-r--r--nixos/modules/virtualisation/docker.nix4
-rw-r--r--nixos/modules/virtualisation/ec2-amis.nix19
-rw-r--r--nixos/modules/virtualisation/google-compute-config.nix1
-rw-r--r--nixos/modules/virtualisation/google-compute-image.nix2
-rw-r--r--nixos/modules/virtualisation/virtualbox-host.nix2
-rw-r--r--nixos/modules/virtualisation/virtualbox-image.nix5
242 files changed, 6194 insertions, 2451 deletions
diff --git a/nixos/modules/config/fonts/fontconfig-penultimate.nix b/nixos/modules/config/fonts/fontconfig-penultimate.nix
index 2c18244621af..04fa8b9559a9 100644
--- a/nixos/modules/config/fonts/fontconfig-penultimate.nix
+++ b/nixos/modules/config/fonts/fontconfig-penultimate.nix
@@ -31,7 +31,7 @@ let
   # use latest when no version is passed
   makeCacheConf = { version ? null }:
     let
-      fcPackage = if builtins.isNull version
+      fcPackage = if version == null
                   then "fontconfig"
                   else "fontconfig_${version}";
       makeCache = fontconfig: pkgs.makeFontsCache { inherit fontconfig; fontDirectories = config.fonts.fonts; };
diff --git a/nixos/modules/config/fonts/fontconfig.nix b/nixos/modules/config/fonts/fontconfig.nix
index d79c43c0b5b9..724158f73821 100644
--- a/nixos/modules/config/fonts/fontconfig.nix
+++ b/nixos/modules/config/fonts/fontconfig.nix
@@ -46,7 +46,7 @@ let cfg = config.fonts.fontconfig;
     # use latest when no version is passed
     makeCacheConf = { version ? null }:
       let
-        fcPackage = if builtins.isNull version
+        fcPackage = if version == null
                     then "fontconfig"
                     else "fontconfig_${version}";
         makeCache = fontconfig: pkgs.makeFontsCache { inherit fontconfig; fontDirectories = config.fonts.fonts; };
diff --git a/nixos/modules/config/malloc.nix b/nixos/modules/config/malloc.nix
new file mode 100644
index 000000000000..dfa86a44a326
--- /dev/null
+++ b/nixos/modules/config/malloc.nix
@@ -0,0 +1,97 @@
+{ config, lib, pkgs, ... }:
+with lib;
+
+let
+  cfg = config.environment.memoryAllocator;
+
+  # The set of alternative malloc(3) providers.
+  providers = {
+    "graphene-hardened" = rec {
+      libPath = "${pkgs.graphene-hardened-malloc}/lib/libhardened_malloc.so";
+      description = ''
+        An allocator designed to mitigate memory corruption attacks, such as
+        those caused by use-after-free bugs.
+      '';
+    };
+
+    "jemalloc" = {
+      libPath = "${pkgs.jemalloc}/lib/libjemalloc.so";
+      description = ''
+        A general purpose allocator that emphasizes fragmentation avoidance
+        and scalable concurrency support.
+      '';
+    };
+
+    "scudo" = {
+      libPath = "${pkgs.llvmPackages.compiler-rt}/lib/linux/libclang_rt.scudo-x86_64.so";
+      description = ''
+        A user-mode allocator based on LLVM Sanitizer’s CombinedAllocator,
+        which aims at providing additional mitigations against heap based
+        vulnerabilities, while maintaining good performance.
+      '';
+    };
+  };
+
+  providerConf = providers."${cfg.provider}";
+
+  # An output that contains only the shared library, to avoid
+  # needlessly bloating the system closure
+  mallocLib = pkgs.runCommand "malloc-provider-${cfg.provider}"
+    rec {
+      preferLocalBuild = true;
+      allowSubstitutes = false;
+      origLibPath = providerConf.libPath;
+      libName = baseNameOf origLibPath;
+    }
+    ''
+      mkdir -p $out/lib
+      cp -L $origLibPath $out/lib/$libName
+    '';
+
+  # The full path to the selected provider shlib.
+  providerLibPath = "${mallocLib}/lib/${mallocLib.libName}";
+in
+
+{
+  meta = {
+    maintainers = [ maintainers.joachifm ];
+  };
+
+  options = {
+    environment.memoryAllocator.provider = mkOption {
+      type = types.enum ([ "libc" ] ++ attrNames providers);
+      default = "libc";
+      description = ''
+        The system-wide memory allocator.
+
+        Briefly, the system-wide memory allocator providers are:
+        <itemizedlist>
+        <listitem><para><literal>libc</literal>: the standard allocator provided by libc</para></listitem>
+        ${toString (mapAttrsToList
+            (name: value: "<listitem><para><literal>${name}</literal>: ${value.description}</para></listitem>")
+            providers)}
+        </itemizedlist>
+
+        <warning>
+        <para>
+        Selecting an alternative allocator (i.e., anything other than
+        <literal>libc</literal>) may result in instability, data loss,
+        and/or service failure.
+        </para>
+        </warning>
+
+        <note>
+        <para>
+        Changing this option does not affect the current session.
+        </para>
+        </note>
+      '';
+    };
+  };
+
+  config = mkIf (cfg.provider != "libc") {
+    environment.variables.LD_PRELOAD = providerLibPath;
+    systemd.extraConfig = "DefaultEnvironment=\"LD_PRELOAD=${providerLibPath}\"";
+    systemd.user.extraConfig = "DefaultEnvironment=\"LD_PRELOAD=${providerLibPath}\"";
+  };
+}
diff --git a/nixos/modules/config/pulseaudio.nix b/nixos/modules/config/pulseaudio.nix
index e61a3a731201..5c3e39302583 100644
--- a/nixos/modules/config/pulseaudio.nix
+++ b/nixos/modules/config/pulseaudio.nix
@@ -245,6 +245,9 @@ in {
       # Disable flat volumes to enable relative ones
       hardware.pulseaudio.daemon.config.flat-volumes = mkDefault "no";
 
+      # Upstream defaults to speex-float-1 which results in audible artifacts
+      hardware.pulseaudio.daemon.config.resample-method = mkDefault "speex-float-5";
+
       # Allow PulseAudio to get realtime priority using rtkit.
       security.rtkit.enable = true;
 
diff --git a/nixos/modules/config/sysctl.nix b/nixos/modules/config/sysctl.nix
index 74bff602a477..0c6a7e2431aa 100644
--- a/nixos/modules/config/sysctl.nix
+++ b/nixos/modules/config/sysctl.nix
@@ -8,7 +8,7 @@ let
     name = "sysctl option value";
     check = val:
       let
-        checkType = x: isBool x || isString x || isInt x || isNull x;
+        checkType = x: isBool x || isString x || isInt x || x == null;
       in
         checkType val || (val._type or "" == "override" && checkType val.content);
     merge = loc: defs: mergeOneOption loc (filterOverrides defs);
diff --git a/nixos/modules/config/xdg/mime.nix b/nixos/modules/config/xdg/mime.nix
index cd1064630fbc..a5374c2b468d 100644
--- a/nixos/modules/config/xdg/mime.nix
+++ b/nixos/modules/config/xdg/mime.nix
@@ -24,7 +24,7 @@ with lib;
 
     environment.extraSetup = ''
       if [ -w $out/share/mime ] && [ -d $out/share/mime/packages ]; then
-          XDG_DATA_DIRS=$out/share ${pkgs.buildPackages.shared-mime-info}/bin/update-mime-database -V $out/share/mime > /dev/null
+          XDG_DATA_DIRS=$out/share PKGSYSTEM_ENABLE_FSYNC=0 ${pkgs.buildPackages.shared-mime-info}/bin/update-mime-database -V $out/share/mime > /dev/null
       fi
 
       if [ -w $out/share/applications ]; then
diff --git a/nixos/modules/hardware/all-firmware.nix b/nixos/modules/hardware/all-firmware.nix
index c79be810eefd..534fcc34276b 100644
--- a/nixos/modules/hardware/all-firmware.nix
+++ b/nixos/modules/hardware/all-firmware.nix
@@ -63,8 +63,7 @@ in {
         b43Firmware_5_1_138
         b43Firmware_6_30_163_46
         b43FirmwareCutter
-        facetimehd-firmware
-      ];
+      ] ++ optional (pkgs.stdenv.hostPlatform.isi686 || pkgs.stdenv.hostPlatform.isx86_64) facetimehd-firmware;
     })
   ];
 }
diff --git a/nixos/modules/hardware/video/nvidia.nix b/nixos/modules/hardware/video/nvidia.nix
index 80ea7bc5d5c9..9f2360f41c6e 100644
--- a/nixos/modules/hardware/video/nvidia.nix
+++ b/nixos/modules/hardware/video/nvidia.nix
@@ -79,6 +79,14 @@ in
       '';
     };
 
+    hardware.nvidia.optimus_prime.allowExternalGpu = lib.mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = ''
+        Configure X to allow external NVIDIA GPUs when using optimus.
+      '';
+    };
+
     hardware.nvidia.optimus_prime.nvidiaBusId = lib.mkOption {
       type = lib.types.string;
       default = "";
@@ -134,6 +142,7 @@ in
       deviceSection = optionalString optimusCfg.enable
         ''
           BusID "${optimusCfg.nvidiaBusId}"
+          ${optionalString optimusCfg.allowExternalGpu "Option \"AllowExternalGpus\""}
         '';
       screenSection =
         ''
diff --git a/nixos/modules/installer/cd-dvd/iso-image.nix b/nixos/modules/installer/cd-dvd/iso-image.nix
index fd780be20825..d5c92cfc1d9e 100644
--- a/nixos/modules/installer/cd-dvd/iso-image.nix
+++ b/nixos/modules/installer/cd-dvd/iso-image.nix
@@ -198,7 +198,7 @@ let
     fi
 
     ${ # When there is a theme configured, use it, otherwise use the background image.
-    if (!isNull config.isoImage.grubTheme) then ''
+    if config.isoImage.grubTheme != null then ''
       # Sets theme.
       set theme=(hd0)/EFI/boot/grub-theme/theme.txt
       # Load theme fonts
@@ -622,7 +622,7 @@ in
         { source = "${pkgs.memtest86plus}/memtest.bin";
           target = "/boot/memtest.bin";
         }
-      ] ++ optionals (!isNull config.isoImage.grubTheme) [
+      ] ++ optionals (config.isoImage.grubTheme != null) [
         { source = config.isoImage.grubTheme;
           target = "/EFI/boot/grub-theme";
         }
diff --git a/nixos/modules/installer/cd-dvd/system-tarball.nix b/nixos/modules/installer/cd-dvd/system-tarball.nix
index e72d4a5b4910..b84096861f56 100644
--- a/nixos/modules/installer/cd-dvd/system-tarball.nix
+++ b/nixos/modules/installer/cd-dvd/system-tarball.nix
@@ -68,7 +68,7 @@ in
 
     # Create the tarball
     system.build.tarball = import ../../../lib/make-system-tarball.nix {
-      inherit (pkgs) stdenv perl xz pathsFromGraph;
+      inherit (pkgs) stdenv closureInfo pixz;
 
       inherit (config.tarball) contents storeContents;
     };
diff --git a/nixos/modules/installer/netboot/netboot.nix b/nixos/modules/installer/netboot/netboot.nix
index 303d9fce3f9a..cdfba5ab9e31 100644
--- a/nixos/modules/installer/netboot/netboot.nix
+++ b/nixos/modules/installer/netboot/netboot.nix
@@ -65,8 +65,7 @@ with lib;
       [ config.system.build.toplevel ];
 
     # Create the squashfs image that contains the Nix store.
-    system.build.squashfsStore = import ../../../lib/make-squashfs.nix {
-      inherit (pkgs) stdenv squashfsTools closureInfo;
+    system.build.squashfsStore = pkgs.callPackage ../../../lib/make-squashfs.nix {
       storeContents = config.netboot.storeContents;
     };
 
diff --git a/nixos/modules/installer/tools/nixos-generate-config.pl b/nixos/modules/installer/tools/nixos-generate-config.pl
index 686204ee0342..0ccdac30d915 100644
--- a/nixos/modules/installer/tools/nixos-generate-config.pl
+++ b/nixos/modules/installer/tools/nixos-generate-config.pl
@@ -258,6 +258,11 @@ foreach my $path (glob "/sys/class/{block,mmc_host}/*") {
     }
 }
 
+# Add bcache module, if needed.
+my @bcacheDevices = glob("/dev/bcache*");
+if (scalar @bcacheDevices > 0) {
+    push @initrdAvailableKernelModules, "bcache";
+}
 
 my $virt = `systemd-detect-virt`;
 chomp $virt;
@@ -464,6 +469,21 @@ EOF
     }
 }
 
+# For lack of a better way to determine it, guess whether we should use a
+# bigger font for the console from the display mode on the first
+# framebuffer. A way based on the physical size/actual DPI reported by
+# the monitor would be nice, but I don't know how to do this without X :)
+my $fb_modes_file = "/sys/class/graphics/fb0/modes";
+if (-f $fb_modes_file && -r $fb_modes_file) {
+    my $modes = read_file($fb_modes_file);
+    $modes =~ m/([0-9]+)x([0-9]+)/;
+    my $console_width = $1, my $console_height = $2;
+    if ($console_width > 1920) {
+        push @attrs, "# High-DPI console";
+        push @attrs, 'i18n.consoleFont = lib.mkDefault "${pkgs.terminus_font}/share/consolefonts/ter-u28n.psf.gz";';
+    }
+}
+
 
 # Generate the hardware configuration file.
 
diff --git a/nixos/modules/installer/tools/tools.nix b/nixos/modules/installer/tools/tools.nix
index 00c4d5018bf5..59eb4a63af40 100644
--- a/nixos/modules/installer/tools/tools.nix
+++ b/nixos/modules/installer/tools/tools.nix
@@ -36,7 +36,7 @@ let
   nixos-generate-config = makeProg {
     name = "nixos-generate-config";
     src = ./nixos-generate-config.pl;
-    path = [ pkgs.btrfs-progs ];
+    path = lib.optionals (lib.elem "btrfs" config.boot.supportedFilesystems) [ pkgs.btrfs-progs ];
     perl = "${pkgs.perl}/bin/perl -I${pkgs.perlPackages.FileSlurp}/${pkgs.perl.libPrefix}";
     inherit (config.system.nixos) release;
   };
diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix
index e78673514e3b..f1118f472e44 100644
--- a/nixos/modules/misc/ids.nix
+++ b/nixos/modules/misc/ids.nix
@@ -145,7 +145,7 @@
       #notbit = 111; # unused
       aerospike = 111;
       ngircd = 112;
-      btsync = 113;
+      #btsync = 113; # unused
       minecraft = 114;
       vault = 115;
       rippled = 116;
@@ -265,8 +265,8 @@
       syncthing = 237;
       caddy = 239;
       taskd = 240;
-      factorio = 241;
-      emby = 242;
+      # factorio = 241; # DynamicUser = true
+      # emby = 242; # unusued, removed 2019-05-01
       graylog = 243;
       sniproxy = 244;
       nzbget = 245;
@@ -339,6 +339,7 @@
       rss2email = 312;
       cockroachdb = 313;
       zoneminder = 314;
+      paperless = 315;
 
       # When adding a uid, make sure it doesn't match an existing gid. And don't use uids above 399!
 
@@ -456,7 +457,7 @@
       #notbit = 111; # unused
       aerospike = 111;
       #ngircd = 112; # unused
-      btsync = 113;
+      #btsync = 113; # unused
       #minecraft = 114; # unused
       vault = 115;
       #ripped = 116; # unused
@@ -566,8 +567,8 @@
       syncthing = 237;
       caddy = 239;
       taskd = 240;
-      factorio = 241;
-      emby = 242;
+      # factorio = 241; # unused
+      # emby = 242; # unused, removed 2019-05-01
       sniproxy = 244;
       nzbget = 245;
       mosquitto = 246;
@@ -638,6 +639,7 @@
       rss2email = 312;
       cockroachdb = 313;
       zoneminder = 314;
+      paperless = 315;
 
       # When adding a gid, make sure it doesn't match an existing
       # uid. Users and groups with the same name should have equal
diff --git a/nixos/modules/misc/nixpkgs.nix b/nixos/modules/misc/nixpkgs.nix
index 3a717fddaba2..9d19dd2a7ff2 100644
--- a/nixos/modules/misc/nixpkgs.nix
+++ b/nixos/modules/misc/nixpkgs.nix
@@ -127,13 +127,14 @@ in
       default = [];
       example = literalExample
         ''
-          [ (self: super: {
+          [
+            (self: super: {
               openssh = super.openssh.override {
                 hpnSupport = true;
                 kerberos = self.libkrb5;
               };
-            };
-          ) ]
+            })
+          ]
         '';
       type = types.listOf overlayType;
       description = ''
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 9ba4eb5fe86e..bb2c0a8f180f 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -19,6 +19,7 @@
   ./config/iproute2.nix
   ./config/krb5/default.nix
   ./config/ldap.nix
+  ./config/malloc.nix
   ./config/networking.nix
   ./config/no-x-libs.nix
   ./config/nsswitch.nix
@@ -94,6 +95,7 @@
   ./programs/ccache.nix
   ./programs/cdemu.nix
   ./programs/chromium.nix
+  ./programs/clickshare.nix
   ./programs/command-not-found/command-not-found.nix
   ./programs/criu.nix
   ./programs/dconf.nix
@@ -145,6 +147,7 @@
   ./programs/xonsh.nix
   ./programs/xss-lock.nix
   ./programs/yabar.nix
+  ./programs/zmap.nix
   ./programs/zsh/oh-my-zsh.nix
   ./programs/zsh/zsh.nix
   ./programs/zsh/zsh-autoenv.nix
@@ -181,6 +184,7 @@
   ./services/amqp/activemq/default.nix
   ./services/amqp/rabbitmq.nix
   ./services/audio/alsa.nix
+  ./services/audio/jack.nix
   ./services/audio/icecast.nix
   ./services/audio/liquidsoap.nix
   ./services/audio/mpd.nix
@@ -189,6 +193,7 @@
   ./services/audio/snapserver.nix
   ./services/audio/squeezelite.nix
   ./services/audio/ympd.nix
+  ./services/backup/automysqlbackup.nix
   ./services/backup/bacula.nix
   ./services/backup/borgbackup.nix
   ./services/backup/duplicati.nix
@@ -256,8 +261,7 @@
   ./services/databases/virtuoso.nix
   ./services/desktops/accountsservice.nix
   ./services/desktops/bamf.nix
-  ./services/desktops/deepin/dde-daemon.nix
-  ./services/desktops/deepin/deepin-menu.nix
+  ./services/desktops/deepin/deepin.nix
   ./services/desktops/dleyna-renderer.nix
   ./services/desktops/dleyna-server.nix
   ./services/desktops/pantheon/contractor.nix
@@ -323,6 +327,7 @@
   ./services/hardware/tcsd.nix
   ./services/hardware/tlp.nix
   ./services/hardware/thinkfan.nix
+  ./services/hardware/throttled.nix
   ./services/hardware/trezord.nix
   ./services/hardware/triggerhappy.nix
   ./services/hardware/u2f.nix
@@ -392,9 +397,9 @@
   ./services/misc/dysnomia.nix
   ./services/misc/disnix.nix
   ./services/misc/docker-registry.nix
-  ./services/misc/emby.nix
   ./services/misc/errbot.nix
   ./services/misc/etcd.nix
+  ./services/misc/ethminer.nix
   ./services/misc/exhibitor.nix
   ./services/misc/felix.nix
   ./services/misc/folding-at-home.nix
@@ -414,6 +419,7 @@
   ./services/misc/ihaskell.nix
   ./services/misc/irkerd.nix
   ./services/misc/jackett.nix
+  ./services/misc/jellyfin.nix
   ./services/misc/logkeys.nix
   ./services/misc/leaps.nix
   ./services/misc/lidarr.nix
@@ -435,6 +441,7 @@
   ./services/misc/octoprint.nix
   ./services/misc/osrm.nix
   ./services/misc/packagekit.nix
+  ./services/misc/paperless.nix
   ./services/misc/parsoid.nix
   ./services/misc/phd.nix
   ./services/misc/plex.nix
@@ -492,6 +499,7 @@
   ./services/monitoring/prometheus/default.nix
   ./services/monitoring/prometheus/alertmanager.nix
   ./services/monitoring/prometheus/exporters.nix
+  ./services/monitoring/prometheus/pushgateway.nix
   ./services/monitoring/riemann.nix
   ./services/monitoring/riemann-dash.nix
   ./services/monitoring/riemann-tools.nix
@@ -532,10 +540,10 @@
   ./services/networking/avahi-daemon.nix
   ./services/networking/babeld.nix
   ./services/networking/bind.nix
+  ./services/networking/bitcoind.nix
   ./services/networking/autossh.nix
   ./services/networking/bird.nix
   ./services/networking/bitlbee.nix
-  ./services/networking/btsync.nix
   ./services/networking/charybdis.nix
   ./services/networking/chrony.nix
   ./services/networking/cjdns.nix
@@ -604,6 +612,7 @@
   ./services/networking/morty.nix
   ./services/networking/miredo.nix
   ./services/networking/mstpd.nix
+  ./services/networking/mtprotoproxy.nix
   ./services/networking/murmur.nix
   ./services/networking/mxisd.nix
   ./services/networking/namecoind.nix
@@ -672,6 +681,7 @@
   ./services/networking/syncthing-relay.nix
   ./services/networking/tcpcrypt.nix
   ./services/networking/teamspeak3.nix
+  ./services/networking/tedicross.nix
   ./services/networking/tinc.nix
   ./services/networking/tinydns.nix
   ./services/networking/tftpd.nix
@@ -684,6 +694,7 @@
   ./services/networking/vsftpd.nix
   ./services/networking/wakeonlan.nix
   ./services/networking/websockify.nix
+  ./services/networking/wg-quick.nix
   ./services/networking/wicd.nix
   ./services/networking/wireguard.nix
   ./services/networking/wpa_supplicant.nix
@@ -705,6 +716,7 @@
   ./services/search/hound.nix
   ./services/search/kibana.nix
   ./services/search/solr.nix
+  ./services/security/bitwarden_rs/default.nix
   ./services/security/certmgr.nix
   ./services/security/cfssl.nix
   ./services/security/clamav.nix
@@ -749,10 +761,12 @@
   ./services/web-apps/atlassian/crowd.nix
   ./services/web-apps/atlassian/jira.nix
   ./services/web-apps/codimd.nix
+  ./services/web-apps/cryptpad.nix
   ./services/web-apps/documize.nix
   ./services/web-apps/frab.nix
   ./services/web-apps/icingaweb2/icingaweb2.nix
   ./services/web-apps/icingaweb2/module-monitoring.nix
+  ./services/web-apps/limesurvey.nix
   ./services/web-apps/mattermost.nix
   ./services/web-apps/miniflux.nix
   ./services/web-apps/nextcloud.nix
@@ -786,7 +800,6 @@
   ./services/web-servers/traefik.nix
   ./services/web-servers/uwsgi.nix
   ./services/web-servers/varnish/default.nix
-  ./services/web-servers/winstone.nix
   ./services/web-servers/zope2.nix
   ./services/x11/colord.nix
   ./services/x11/compton.nix
diff --git a/nixos/modules/profiles/hardened.nix b/nixos/modules/profiles/hardened.nix
index 9ab2ee87a19e..87bf66333c61 100644
--- a/nixos/modules/profiles/hardened.nix
+++ b/nixos/modules/profiles/hardened.nix
@@ -14,6 +14,8 @@ with lib;
 
   nix.allowedUsers = mkDefault [ "@users" ];
 
+  environment.memoryAllocator.provider = mkDefault "graphene-hardened";
+
   security.hideProcessInformation = mkDefault true;
 
   security.lockKernelModules = mkDefault true;
diff --git a/nixos/modules/programs/bash/bash.nix b/nixos/modules/programs/bash/bash.nix
index 27b5f9e4b642..a7e57b8608d7 100644
--- a/nixos/modules/programs/bash/bash.nix
+++ b/nixos/modules/programs/bash/bash.nix
@@ -34,7 +34,7 @@ let
 
   bashAliases = concatStringsSep "\n" (
     mapAttrsFlatten (k: v: "alias ${k}=${escapeShellArg v}")
-      (filterAttrs (k: v: !isNull v) cfg.shellAliases)
+      (filterAttrs (k: v: v != null) cfg.shellAliases)
   );
 
 in
diff --git a/nixos/modules/programs/browserpass.nix b/nixos/modules/programs/browserpass.nix
index 47b9d1ccb1f6..e1456d3c1848 100644
--- a/nixos/modules/programs/browserpass.nix
+++ b/nixos/modules/programs/browserpass.nix
@@ -26,16 +26,6 @@ with lib;
       # brave
       "opt/brave/native-messaging-hosts/${appId}".source = source "hosts/chromium";
       "opt/brave/policies/managed/${appId}".source = source "policies/chromium";
-    }
-    # As with the v2 backwards compatibility in the pkgs.browserpass
-    # declaration, this part can be removed once the browser extension
-    # auto-updates to v3 (planned 2019-04-13, see
-    # https://github.com/browserpass/browserpass-native/issues/31)
-    // {
-      "chromium/native-messaging-hosts/com.dannyvankooten.browserpass.json".source = "${pkgs.browserpass}/etc/chrome-host.json";
-      "chromium/policies/managed/com.dannyvankooten.browserpass.json".source = "${pkgs.browserpass}/etc/chrome-policy.json";
-      "opt/chrome/native-messaging-hosts/com.dannyvankooten.browserpass.json".source = "${pkgs.browserpass}/etc/chrome-host.json";
-      "opt/chrome/policies/managed/com.dannyvankooten.browserpass.json".source = "${pkgs.browserpass}/etc/chrome-policy.json";
     };
     nixpkgs.config.firefox.enableBrowserpass = true;
   };
diff --git a/nixos/modules/programs/clickshare.nix b/nixos/modules/programs/clickshare.nix
new file mode 100644
index 000000000000..9980a7daf525
--- /dev/null
+++ b/nixos/modules/programs/clickshare.nix
@@ -0,0 +1,21 @@
+{ config, lib, pkgs, ... }:
+
+{
+
+  options.programs.clickshare-csc1.enable =
+    lib.options.mkEnableOption ''
+      Barco ClickShare CSC-1 driver/client.
+      This allows users in the <literal>clickshare</literal>
+      group to access and use a ClickShare USB dongle
+      that is connected to the machine
+    '';
+
+  config = lib.modules.mkIf config.programs.clickshare-csc1.enable {
+    environment.systemPackages = [ pkgs.clickshare-csc1 ];
+    services.udev.packages = [ pkgs.clickshare-csc1 ];
+    users.groups.clickshare = {};
+  };
+
+  meta.maintainers = [ lib.maintainers.yarny ];
+
+}
diff --git a/nixos/modules/programs/dconf.nix b/nixos/modules/programs/dconf.nix
index 9c9765b06b6f..b7bfb8504b6f 100644
--- a/nixos/modules/programs/dconf.nix
+++ b/nixos/modules/programs/dconf.nix
@@ -38,7 +38,7 @@ in
       "${pkgs.gnome3.dconf.lib}/lib/gio/modules";
     # https://github.com/NixOS/nixpkgs/pull/31891
     #environment.variables.XDG_DATA_DIRS = optional cfg.enable
-    #  "$(echo ${pkgs.gnome3.gsettings-desktop-schemas}/share/gsettings-schemas/gsettings-desktop-schemas-*)";
+    #  "$(echo ${pkgs.gsettings-desktop-schemas}/share/gsettings-schemas/gsettings-desktop-schemas-*)";
   };
 
 }
diff --git a/nixos/modules/programs/fish.nix b/nixos/modules/programs/fish.nix
index 622d2f96fe41..87f6816e4ac0 100644
--- a/nixos/modules/programs/fish.nix
+++ b/nixos/modules/programs/fish.nix
@@ -10,7 +10,7 @@ let
 
   fishAliases = concatStringsSep "\n" (
     mapAttrsFlatten (k: v: "alias ${k} ${escapeShellArg v}")
-      (filterAttrs (k: v: !isNull v) cfg.shellAliases)
+      (filterAttrs (k: v: v != null) cfg.shellAliases)
   );
 
 in
diff --git a/nixos/modules/programs/ssmtp.nix b/nixos/modules/programs/ssmtp.nix
index 44756171b74c..0e060e3f5226 100644
--- a/nixos/modules/programs/ssmtp.nix
+++ b/nixos/modules/programs/ssmtp.nix
@@ -148,7 +148,7 @@ in
         UseSTARTTLS=${yesNo cfg.useSTARTTLS}
         #Debug=YES
         ${optionalString (cfg.authUser != "")       "AuthUser=${cfg.authUser}"}
-        ${optionalString (!isNull cfg.authPassFile) "AuthPassFile=${cfg.authPassFile}"}
+        ${optionalString (cfg.authPassFile != null) "AuthPassFile=${cfg.authPassFile}"}
       '';
 
     environment.systemPackages = [pkgs.ssmtp];
diff --git a/nixos/modules/programs/sway.nix b/nixos/modules/programs/sway.nix
index 457faaa3c102..b4f03151cdc1 100644
--- a/nixos/modules/programs/sway.nix
+++ b/nixos/modules/programs/sway.nix
@@ -78,9 +78,9 @@ in {
     environment = {
       systemPackages = [ swayJoined ] ++ cfg.extraPackages;
       etc = {
-        "sway/config".source = "${swayPackage}/etc/sway/config";
-        #"sway/security.d".source = "${swayPackage}/etc/sway/security.d/";
-        #"sway/config.d".source = "${swayPackage}/etc/sway/config.d/";
+        "sway/config".source = mkOptionDefault "${swayPackage}/etc/sway/config";
+        #"sway/security.d".source = mkOptionDefault "${swayPackage}/etc/sway/security.d/";
+        #"sway/config.d".source = mkOptionDefault "${swayPackage}/etc/sway/config.d/";
       };
     };
     security.pam.services.swaylock = {};
diff --git a/nixos/modules/programs/tmux.nix b/nixos/modules/programs/tmux.nix
index 3d5a37274ae2..ed077e3daa76 100644
--- a/nixos/modules/programs/tmux.nix
+++ b/nixos/modules/programs/tmux.nix
@@ -177,7 +177,7 @@ in {
       systemPackages = [ pkgs.tmux ];
 
       variables = {
-        TMUX_TMPDIR = lib.optional cfg.secureSocket ''''${XDG_RUNTIME_DIR:-"/run/user/\$(id -u)"}'';
+        TMUX_TMPDIR = lib.optional cfg.secureSocket ''''${XDG_RUNTIME_DIR:-"/run/user/$(id -u)"}'';
       };
     };
   };
diff --git a/nixos/modules/programs/xss-lock.nix b/nixos/modules/programs/xss-lock.nix
index c290df01b960..070463311db5 100644
--- a/nixos/modules/programs/xss-lock.nix
+++ b/nixos/modules/programs/xss-lock.nix
@@ -8,12 +8,23 @@ in
 {
   options.programs.xss-lock = {
     enable = mkEnableOption "xss-lock";
+
     lockerCommand = mkOption {
       default = "${pkgs.i3lock}/bin/i3lock";
       example = literalExample ''''${pkgs.i3lock-fancy}/bin/i3lock-fancy'';
       type = types.string;
       description = "Locker to be used with xsslock";
     };
+
+    extraOptions = mkOption {
+      default = [ ];
+      example = [ "--ignore-sleep" ];
+      type = types.listOf types.str;
+      description = ''
+        Additional command-line arguments to pass to
+        <command>xss-lock</command>.
+      '';
+    };
   };
 
   config = mkIf cfg.enable {
@@ -21,7 +32,13 @@ in
       description = "XSS Lock Daemon";
       wantedBy = [ "graphical-session.target" ];
       partOf = [ "graphical-session.target" ];
-      serviceConfig.ExecStart = "${pkgs.xss-lock}/bin/xss-lock ${cfg.lockerCommand}";
+      serviceConfig.ExecStart = with lib;
+        strings.concatStringsSep " " ([
+            "${pkgs.xss-lock}/bin/xss-lock"
+          ] ++ (map escapeShellArg cfg.extraOptions) ++ [
+            "--"
+            cfg.lockerCommand
+        ]);
     };
   };
 }
diff --git a/nixos/modules/programs/zmap.nix b/nixos/modules/programs/zmap.nix
new file mode 100644
index 000000000000..2e27fce4d7c6
--- /dev/null
+++ b/nixos/modules/programs/zmap.nix
@@ -0,0 +1,18 @@
+{ pkgs, config, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.zmap;
+in {
+  options.programs.zmap = {
+    enable = mkEnableOption "ZMap";
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.zmap ];
+
+    environment.etc."zmap/blacklist.conf".source = "${pkgs.zmap}/etc/zmap/blacklist.conf";
+    environment.etc."zmap/zmap.conf".source = "${pkgs.zmap}/etc/zmap.conf";
+  };
+}
diff --git a/nixos/modules/programs/zsh/zsh.nix b/nixos/modules/programs/zsh/zsh.nix
index b7117e5f90d7..bd1a77680663 100644
--- a/nixos/modules/programs/zsh/zsh.nix
+++ b/nixos/modules/programs/zsh/zsh.nix
@@ -12,7 +12,7 @@ let
 
   zshAliases = concatStringsSep "\n" (
     mapAttrsFlatten (k: v: "alias ${k}=${escapeShellArg v}")
-      (filterAttrs (k: v: !isNull v) cfg.shellAliases)
+      (filterAttrs (k: v: v != null) cfg.shellAliases)
   );
 
 in
@@ -194,8 +194,6 @@ in
         HISTSIZE=${toString cfg.histSize}
         HISTFILE=${cfg.histFile}
 
-        ${optionalString (cfg.setOptions != []) "setopt ${concatStringsSep " " cfg.setOptions}"}
-
         HELPDIR="${pkgs.zsh}/share/zsh/$ZSH_VERSION/help"
 
         # Tell zsh how to find installed completions
@@ -209,6 +207,8 @@ in
 
         ${cfg.interactiveShellInit}
 
+        ${optionalString (cfg.setOptions != []) "setopt ${concatStringsSep " " cfg.setOptions}"}
+
         ${zshAliases}
 
         ${cfg.promptInit}
diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix
index f6c112d9cfab..a424e86038e7 100644
--- a/nixos/modules/rename.nix
+++ b/nixos/modules/rename.nix
@@ -45,6 +45,9 @@ with lib;
     (mkRemovedOptionModule [ "services" "neo4j" "port" ] "Use services.neo4j.http.listenAddress instead.")
     (mkRemovedOptionModule [ "services" "neo4j" "boltPort" ] "Use services.neo4j.bolt.listenAddress instead.")
     (mkRemovedOptionModule [ "services" "neo4j" "httpsPort" ] "Use services.neo4j.https.listenAddress instead.")
+    (mkRemovedOptionModule [ "services" "misc" "nzbget" "configFile" ] "The configuration of nzbget is now managed by users through the web interface.")
+    (mkRemovedOptionModule [ "services" "misc" "nzbget" "dataDir" ] "The data directory for nzbget is now /var/lib/nzbget.")
+    (mkRemovedOptionModule [ "services" "misc" "nzbget" "openFirewall" ] "The port used by nzbget is managed through the web interface so you should adjust your firewall rules accordingly.")
     (mkRemovedOptionModule [ "services" "prometheus" "alertmanager" "user" ] "The alertmanager service is now using systemd's DynamicUser mechanism which obviates a user setting.")
     (mkRemovedOptionModule [ "services" "prometheus" "alertmanager" "group" ] "The alertmanager service is now using systemd's DynamicUser mechanism which obviates a group setting.")
     (mkRenamedOptionModule [ "services" "tor" "relay" "portSpec" ] [ "services" "tor" "relay" "port" ])
@@ -110,9 +113,10 @@ with lib;
 
     # murmur
     (mkRenamedOptionModule [ "services" "murmur" "welcome" ] [ "services" "murmur" "welcometext" ])
+    (mkRemovedOptionModule [ "services" "murmur" "pidfile" ] "Hardcoded to /run/murmur/murmurd.pid now")
 
     # parsoid
-    (mkRemovedOptionModule [ "services" "parsoid" "interwikis" ] [ "services" "parsoid" "wikis" ])
+    (mkRemovedOptionModule [ "services" "parsoid" "interwikis" ] "Use services.parsoid.wikis instead")
 
     # plexpy / tautulli
     (mkRenamedOptionModule [ "services" "plexpy" ] [ "services" "tautulli" ])
@@ -207,6 +211,8 @@ with lib;
     (mkRemovedOptionModule [ "virtualisation" "xen" "qemu" ] "You don't need this option anymore, it will work without it.")
     (mkRemovedOptionModule [ "services" "logstash" "enableWeb" ] "The web interface was removed from logstash")
     (mkRemovedOptionModule [ "boot" "zfs" "enableLegacyCrypto" ] "The corresponding package was removed from nixpkgs.")
+    (mkRemovedOptionModule [ "services" "winstone" ] "The corresponding package was removed from nixpkgs.")
+    (mkRemovedOptionModule [ "services" "mysql" "pidDir" ] "Don't wait for pidfiles, describe dependencies through systemd")
 
     # ZSH
     (mkRenamedOptionModule [ "programs" "zsh" "enableSyntaxHighlighting" ] [ "programs" "zsh" "syntaxHighlighting" "enable" ])
@@ -231,6 +237,9 @@ with lib;
     (mkRenamedOptionModule [ "hardware" "ckb" "enable" ] [ "hardware" "ckb-next" "enable" ])
     (mkRenamedOptionModule [ "hardware" "ckb" "package" ] [ "hardware" "ckb-next" "package" ])
 
+    # binfmt
+    (mkRenamedOptionModule [ "boot" "binfmtMiscRegistrations" ] [ "boot" "binfmt" "registrations" ])
+
   ] ++ (flip map [ "blackboxExporter" "collectdExporter" "fritzboxExporter"
                    "jsonExporter" "minioExporter" "nginxExporter" "nodeExporter"
                    "snmpExporter" "unifiExporter" "varnishExporter" ]
diff --git a/nixos/modules/security/apparmor.nix b/nixos/modules/security/apparmor.nix
index d323a158a4df..cfc65b347bc6 100644
--- a/nixos/modules/security/apparmor.nix
+++ b/nixos/modules/security/apparmor.nix
@@ -29,11 +29,18 @@ in
    config = mkIf cfg.enable {
      environment.systemPackages = [ pkgs.apparmor-utils ];
 
+     boot.kernelParams = [ "apparmor=1" "security=apparmor" ];
+
      systemd.services.apparmor = let
        paths = concatMapStrings (s: " -I ${s}/etc/apparmor.d")
          ([ pkgs.apparmor-profiles ] ++ cfg.packages);
      in {
-       wantedBy = [ "local-fs.target" ];
+       after = [ "local-fs.target" ];
+       before = [ "sysinit.target" ];
+       wantedBy = [ "multi-user.target" ];
+       unitConfig = {
+         DefaultDependencies = "no";
+       };
        serviceConfig = {
          Type = "oneshot";
          RemainAfterExit = "yes";
@@ -43,6 +50,9 @@ in
          ExecStop = map (p:
            ''${pkgs.apparmor-parser}/bin/apparmor_parser -Rv "${p}"''
          ) cfg.profiles;
+         ExecReload = map (p:
+           ''${pkgs.apparmor-parser}/bin/apparmor_parser --reload ${paths} "${p}"''
+         ) cfg.profiles;
        };
      };
    };
diff --git a/nixos/modules/security/misc.nix b/nixos/modules/security/misc.nix
index 4506a67487d4..bf474ac0a546 100644
--- a/nixos/modules/security/misc.nix
+++ b/nixos/modules/security/misc.nix
@@ -12,14 +12,18 @@ with lib;
       type = types.bool;
       default = true;
       description = ''
-        Whether to allow creation of user namespaces.  A recurring problem
-        with user namespaces is the presence of code paths where the kernel's
-        permission checking logic fails to account for namespacing, instead
-        permitting a namespaced process to act outside the namespace with the
-        same privileges as it would have inside it.  This is particularly
+        Whether to allow creation of user namespaces.
+
+        The motivation for disabling user namespaces is the potential
+        presence of code paths where the kernel's permission checking
+        logic fails to account for namespacing, instead permitting a
+        namespaced process to act outside the namespace with the same
+        privileges as it would have inside it.  This is particularly
         damaging in the common case of running as root within the namespace.
-        When user namespace creation is disallowed, attempting to create
-        a user namespace fails with "no space left on device" (ENOSPC).
+
+        When user namespace creation is disallowed, attempting to create a
+        user namespace fails with "no space left on device" (ENOSPC).
+        root may re-enable user namespace creation at runtime.
       '';
     };
 
@@ -38,21 +42,15 @@ with lib;
         Whether to allow SMT/hyperthreading.  Disabling SMT means that only
         physical CPU cores will be usable at runtime, potentially at
         significant performance cost.
-        </para>
 
-        <para>
         The primary motivation for disabling SMT is to mitigate the risk of
         leaking data between threads running on the same CPU core (due to
         e.g., shared caches).  This attack vector is unproven.
-        </para>
 
-        <para>
         Disabling SMT is a supplement to the L1 data cache flushing mitigation
         (see <xref linkend="opt-security.virtualization.flushL1DataCache"/>)
         versus malicious VM guests (SMT could "bring back" previously flushed
         data).
-        </para>
-        <para>
       '';
     };
 
@@ -63,10 +61,8 @@ with lib;
         Whether the hypervisor should flush the L1 data cache before
         entering guests.
         See also <xref linkend="opt-security.allowSimultaneousMultithreading"/>.
-        </para>
 
-        <para>
-          <variablelist>
+        <variablelist>
           <varlistentry>
             <term><literal>null</literal></term>
             <listitem><para>uses the kernel default</para></listitem>
@@ -88,7 +84,7 @@ with lib;
             enters the guest.  May incur significant performance cost.
             </para></listitem>
           </varlistentry>
-          </variablelist>
+        </variablelist>
       '';
     };
   };
diff --git a/nixos/modules/security/rngd.nix b/nixos/modules/security/rngd.nix
index a54ef2e6fcad..d9d6d9c9f253 100644
--- a/nixos/modules/security/rngd.nix
+++ b/nixos/modules/security/rngd.nix
@@ -2,20 +2,30 @@
 
 with lib;
 
+let
+  cfg = config.security.rngd;
+in
 {
   options = {
-    security.rngd.enable = mkOption {
-      type = types.bool;
-      default = true;
-      description = ''
-        Whether to enable the rng daemon, which adds entropy from
-        hardware sources of randomness to the kernel entropy pool when
-        available.
-      '';
+    security.rngd = {
+      enable = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable the rng daemon, which adds entropy from
+          hardware sources of randomness to the kernel entropy pool when
+          available.
+        '';
+      };
+      debug = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable debug output (-d).";
+      };
     };
   };
 
-  config = mkIf config.security.rngd.enable {
+  config = mkIf cfg.enable {
     services.udev.extraRules = ''
       KERNEL=="random", TAG+="systemd"
       SUBSYSTEM=="cpu", ENV{MODALIAS}=="cpu:type:x86,*feature:*009E*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="rngd.service"
@@ -29,7 +39,15 @@ with lib;
 
       description = "Hardware RNG Entropy Gatherer Daemon";
 
-      serviceConfig.ExecStart = "${pkgs.rng-tools}/sbin/rngd -f";
+      serviceConfig = {
+        ExecStart = "${pkgs.rng-tools}/sbin/rngd -f"
+          + optionalString cfg.debug " -d";
+        NoNewPrivileges = true;
+        PrivateNetwork = true;
+        PrivateTmp = true;
+        ProtectSystem = "full";
+        ProtectHome = true;
+      };
     };
   };
 }
diff --git a/nixos/modules/services/admin/oxidized.nix b/nixos/modules/services/admin/oxidized.nix
index 70f7dd9e3647..687cdfb5ba57 100644
--- a/nixos/modules/services/admin/oxidized.nix
+++ b/nixos/modules/services/admin/oxidized.nix
@@ -7,7 +7,7 @@ let
 in
 {
   options.services.oxidized = {
-    enable = mkEnableOption "the oxidized configuation backup service.";
+    enable = mkEnableOption "the oxidized configuration backup service";
 
     user = mkOption {
       type = types.str;
diff --git a/nixos/modules/services/amqp/rabbitmq.nix b/nixos/modules/services/amqp/rabbitmq.nix
index 7373be2a9b0b..302b94de1965 100644
--- a/nixos/modules/services/amqp/rabbitmq.nix
+++ b/nixos/modules/services/amqp/rabbitmq.nix
@@ -179,11 +179,11 @@ in {
       } //  optionalAttrs (cfg.config != "") { RABBITMQ_ADVANCED_CONFIG_FILE = advanced_config_file; };
 
       serviceConfig = {
-        PermissionsStartOnly = true; # preStart must be run as root
         ExecStart = "${cfg.package}/sbin/rabbitmq-server";
         ExecStop = "${cfg.package}/sbin/rabbitmqctl shutdown";
         User = "rabbitmq";
         Group = "rabbitmq";
+        LogsDirectory = "rabbitmq";
         WorkingDirectory = cfg.dataDir;
         Type = "notify";
         NotifyAccess = "all";
@@ -197,11 +197,8 @@ in {
       preStart = ''
         ${optionalString (cfg.cookie != "") ''
             echo -n ${cfg.cookie} > ${cfg.dataDir}/.erlang.cookie
-            chown rabbitmq:rabbitmq ${cfg.dataDir}/.erlang.cookie
             chmod 600 ${cfg.dataDir}/.erlang.cookie
         ''}
-        mkdir -p /var/log/rabbitmq
-        chown rabbitmq:rabbitmq /var/log/rabbitmq
       '';
     };
 
diff --git a/nixos/modules/services/audio/jack.nix b/nixos/modules/services/audio/jack.nix
new file mode 100644
index 000000000000..aa3351f401af
--- /dev/null
+++ b/nixos/modules/services/audio/jack.nix
@@ -0,0 +1,290 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.jack;
+
+  pcmPlugin = cfg.jackd.enable && cfg.alsa.enable;
+  loopback = cfg.jackd.enable && cfg.loopback.enable;
+
+  enable32BitAlsaPlugins = cfg.alsa.support32Bit && pkgs.stdenv.isx86_64 && pkgs.pkgsi686Linux.alsaLib != null;
+
+  umaskNeeded = versionOlder cfg.jackd.package.version "1.9.12";
+  bridgeNeeded = versionAtLeast cfg.jackd.package.version "1.9.12";
+in {
+  options = {
+    services.jack = {
+      jackd = {
+        enable = mkEnableOption ''
+          JACK Audio Connection Kit. You need to add yourself to the "jackaudio" group
+        '';
+
+        package = mkOption {
+          # until jack1 promiscuous mode is fixed
+          internal = true;
+          type = types.package;
+          default = pkgs.jack2;
+          defaultText = "pkgs.jack2";
+          example = literalExample "pkgs.jack1";
+          description = ''
+            The JACK package to use.
+          '';
+        };
+
+        extraOptions = mkOption {
+          type = types.listOf types.str;
+          default = [
+            "-dalsa"
+          ];
+          example = literalExample ''
+            [ "-dalsa" "--device" "hw:1" ];
+          '';
+          description = ''
+            Specifies startup command line arguments to pass to JACK server.
+          '';
+        };
+
+        session = mkOption {
+          type = types.lines;
+          description = ''
+            Commands to run after JACK is started.
+          '';
+        };
+
+      };
+
+      alsa = {
+        enable = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            Route audio to/from generic ALSA-using applications using ALSA JACK PCM plugin.
+          '';
+        };
+
+        support32Bit = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Whether to support sound for 32-bit ALSA applications on 64-bit system.
+          '';
+        };
+      };
+
+      loopback = {
+        enable = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Create ALSA loopback device, instead of using PCM plugin. Has broader
+            application support (things like Steam will work), but may need fine-tuning
+            for concrete hardware.
+          '';
+        };
+
+        index = mkOption {
+          type = types.int;
+          default = 10;
+          description = ''
+            Index of an ALSA loopback device.
+          '';
+        };
+
+        config = mkOption {
+          type = types.lines;
+          description = ''
+            ALSA config for loopback device.
+          '';
+        };
+
+        dmixConfig = mkOption {
+          type = types.lines;
+          default = "";
+          example = ''
+            period_size 2048
+            periods 2
+          '';
+          description = ''
+            For music production software that still doesn't support JACK natively you
+            would like to put buffer/period adjustments here
+            to decrease dmix device latency.
+          '';
+        };
+
+        session = mkOption {
+          type = types.lines;
+          description = ''
+            Additional commands to run to setup loopback device.
+          '';
+        };
+      };
+
+    };
+
+  };
+
+  config = mkMerge [
+
+    (mkIf pcmPlugin {
+      sound.extraConfig = ''
+        pcm_type.jack {
+          libs.native = ${pkgs.alsaPlugins}/lib/alsa-lib/libasound_module_pcm_jack.so ;
+          ${lib.optionalString enable32BitAlsaPlugins
+          "libs.32Bit = ${pkgs.pkgsi686Linux.alsaPlugins}/lib/alsa-lib/libasound_module_pcm_jack.so ;"}
+        }
+        pcm.!default {
+          @func getenv
+          vars [ PCM ]
+          default "plug:jack"
+        }
+      '';
+    })
+
+    (mkIf loopback {
+      boot.kernelModules = [ "snd-aloop" ];
+      boot.kernelParams = [ "snd-aloop.index=${toString cfg.loopback.index}" ];
+      sound.extraConfig = cfg.loopback.config;
+    })
+
+    (mkIf cfg.jackd.enable {
+      services.jack.jackd.session = ''
+        ${lib.optionalString bridgeNeeded "${pkgs.a2jmidid}/bin/a2jmidid -e &"}
+      '';
+      # https://alsa.opensrc.org/Jack_and_Loopback_device_as_Alsa-to-Jack_bridge#id06
+      services.jack.loopback.config = ''
+        pcm.loophw00 {
+          type hw
+          card ${toString cfg.loopback.index}
+          device 0
+          subdevice 0
+        }
+        pcm.amix {
+          type dmix
+          ipc_key 219345
+          slave {
+            pcm loophw00
+            ${cfg.loopback.dmixConfig}
+          }
+        }
+        pcm.asoftvol {
+          type softvol
+          slave.pcm "amix"
+          control { name Master }
+        }
+        pcm.cloop {
+          type hw
+          card ${toString cfg.loopback.index}
+          device 1
+          subdevice 0
+          format S32_LE
+        }
+        pcm.loophw01 {
+          type hw
+          card ${toString cfg.loopback.index}
+          device 0
+          subdevice 1
+        }
+        pcm.ploop {
+          type hw
+          card ${toString cfg.loopback.index}
+          device 1
+          subdevice 1
+          format S32_LE
+        }
+        pcm.aduplex {
+          type asym
+          playback.pcm "asoftvol"
+          capture.pcm "loophw01"
+        }
+        pcm.!default {
+          type plug
+          slave.pcm aduplex
+        }
+      '';
+      services.jack.loopback.session = ''
+        alsa_in -j cloop -dcloop &
+        alsa_out -j ploop -dploop &
+        while [ "$(jack_lsp cloop)" == "" ] || [ "$(jack_lsp ploop)" == "" ]; do sleep 1; done
+        jack_connect cloop:capture_1 system:playback_1
+        jack_connect cloop:capture_2 system:playback_2
+        jack_connect system:capture_1 ploop:playback_1
+        jack_connect system:capture_2 ploop:playback_2
+      '';
+
+      assertions = [
+        {
+          assertion = !(cfg.alsa.enable && cfg.loopback.enable);
+          message = "For JACK both alsa and loopback options shouldn't be used at the same time.";
+        }
+      ];
+
+      users.users.jackaudio = {
+        group = "jackaudio";
+        extraGroups = [ "audio" ];
+        description = "JACK Audio system service user";
+      };
+      # http://jackaudio.org/faq/linux_rt_config.html
+      security.pam.loginLimits = [
+        { domain = "@jackaudio"; type = "-"; item = "rtprio"; value = "99"; }
+        { domain = "@jackaudio"; type = "-"; item = "memlock"; value = "unlimited"; }
+      ];
+      users.groups.jackaudio = {};
+
+      environment = {
+        systemPackages = [ cfg.jackd.package ];
+        etc."alsa/conf.d/50-jack.conf".source = "${pkgs.alsaPlugins}/etc/alsa/conf.d/50-jack.conf";
+        variables.JACK_PROMISCUOUS_SERVER = "jackaudio";
+      };
+
+      services.udev.extraRules = ''
+        ACTION=="add", SUBSYSTEM=="sound", ATTRS{id}!="Loopback", TAG+="systemd", ENV{SYSTEMD_WANTS}="jack.service"
+      '';
+
+      systemd.services.jack = {
+        description = "JACK Audio Connection Kit";
+        serviceConfig = {
+          User = "jackaudio";
+          ExecStart = "${cfg.jackd.package}/bin/jackd ${lib.escapeShellArgs cfg.jackd.extraOptions}";
+          LimitRTPRIO = 99;
+          LimitMEMLOCK = "infinity";
+        } // optionalAttrs umaskNeeded {
+          UMask = "007";
+        };
+        path = [ cfg.jackd.package ];
+        environment = {
+          JACK_PROMISCUOUS_SERVER = "jackaudio";
+          JACK_NO_AUDIO_RESERVATION = "1";
+        };
+        restartIfChanged = false;
+      };
+      systemd.services.jack-session = {
+        description = "JACK session";
+        script = ''
+          jack_wait -w
+          ${cfg.jackd.session}
+          ${lib.optionalString cfg.loopback.enable cfg.loopback.session}
+        '';
+        serviceConfig = {
+          RemainAfterExit = true;
+          User = "jackaudio";
+          StateDirectory = "jack";
+          LimitRTPRIO = 99;
+          LimitMEMLOCK = "infinity";
+        };
+        path = [ cfg.jackd.package ];
+        environment = {
+          JACK_PROMISCUOUS_SERVER = "jackaudio";
+          HOME = "/var/lib/jack";
+        };
+        wantedBy = [ "jack.service" ];
+        partOf = [ "jack.service" ];
+        after = [ "jack.service" ];
+        restartIfChanged = false;
+      };
+    })
+
+  ];
+
+  meta.maintainers = [ maintainers.gnidorah ];
+}
diff --git a/nixos/modules/services/audio/liquidsoap.nix b/nixos/modules/services/audio/liquidsoap.nix
index 66f84ef20762..3a047d10a631 100644
--- a/nixos/modules/services/audio/liquidsoap.nix
+++ b/nixos/modules/services/audio/liquidsoap.nix
@@ -14,15 +14,10 @@ let
         description = "${name} liquidsoap stream";
         wantedBy = [ "multi-user.target" ];
         path = [ pkgs.wget ];
-        preStart =
-          ''
-            mkdir -p /var/log/liquidsoap
-            chown liquidsoap -R /var/log/liquidsoap
-          '';
         serviceConfig = {
-          PermissionsStartOnly="true";
           ExecStart = "${pkgs.liquidsoap}/bin/liquidsoap ${stream}";
           User = "liquidsoap";
+          LogsDirectory = "liquidsoap";
         };
       };
     };
diff --git a/nixos/modules/services/audio/mpd.nix b/nixos/modules/services/audio/mpd.nix
index 5bfe2b6a22ad..0df8f9688d25 100644
--- a/nixos/modules/services/audio/mpd.nix
+++ b/nixos/modules/services/audio/mpd.nix
@@ -158,18 +158,18 @@ in {
       };
     };
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' - ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.playlistDirectory}' - ${cfg.user} ${cfg.group} - -"
+    ];
+
     systemd.services.mpd = {
       after = [ "network.target" "sound.target" ];
       description = "Music Player Daemon";
       wantedBy = optional (!cfg.startWhenNeeded) "multi-user.target";
 
-      preStart = ''
-        mkdir -p "${cfg.dataDir}" && chown -R ${cfg.user}:${cfg.group} "${cfg.dataDir}"
-        mkdir -p "${cfg.playlistDirectory}" && chown -R ${cfg.user}:${cfg.group} "${cfg.playlistDirectory}"
-      '';
       serviceConfig = {
         User = "${cfg.user}";
-        PermissionsStartOnly = true;
         ExecStart = "${pkgs.mpd}/bin/mpd --no-daemon ${mpdConf}";
         Type = "notify";
         LimitRTPRIO = 50;
diff --git a/nixos/modules/services/backup/automysqlbackup.nix b/nixos/modules/services/backup/automysqlbackup.nix
new file mode 100644
index 000000000000..b845f370fb70
--- /dev/null
+++ b/nixos/modules/services/backup/automysqlbackup.nix
@@ -0,0 +1,115 @@
+{ config, lib, pkgs, ... }:
+
+let
+
+  inherit (lib) concatMapStringsSep concatStringsSep isInt isList literalExample;
+  inherit (lib) mapAttrs mapAttrsToList mkDefault mkEnableOption mkIf mkOption optional types;
+
+  cfg = config.services.automysqlbackup;
+  pkg = pkgs.automysqlbackup;
+  user = "automysqlbackup";
+  group = "automysqlbackup";
+
+  toStr = val:
+    if isList val then "( ${concatMapStringsSep " " (val: "'${val}'") val} )"
+    else if isInt val then toString val
+    else if true == val then "'yes'"
+    else if false == val then "'no'"
+    else "'${toString val}'";
+
+  configFile = pkgs.writeText "automysqlbackup.conf" ''
+    #version=${pkg.version}
+    # DONT'T REMOVE THE PREVIOUS VERSION LINE!
+    #
+    ${concatStringsSep "\n" (mapAttrsToList (name: value: "CONFIG_${name}=${toStr value}") cfg.config)}
+  '';
+
+in
+{
+  # interface
+  options = {
+    services.automysqlbackup = {
+
+      enable = mkEnableOption "AutoMySQLBackup";
+
+      calendar = mkOption {
+        type = types.str;
+        default = "01:15:00";
+        description = ''
+          Configured when to run the backup service systemd unit (DayOfWeek Year-Month-Day Hour:Minute:Second).
+        '';
+      };
+
+      config = mkOption {
+        type = with types; attrsOf (either (either str (either int bool)) (listOf str));
+        default = {};
+        description = ''
+          automysqlbackup configuration. Refer to
+          <filename>''${pkgs.automysqlbackup}/etc/automysqlbackup.conf</filename>
+          for details on supported values.
+        '';
+        example = literalExample ''
+          {
+            db_names = [ "nextcloud" "matomo" ];
+            table_exclude = [ "nextcloud.oc_users" "nextcloud.oc_whats_new" ];
+            mailcontent = "log";
+            mail_address = "admin@example.org";
+          }
+        '';
+      };
+
+    };
+  };
+
+  # implementation
+  config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion = !config.services.mysqlBackup.enable;
+        message = "Please choose one of services.mysqlBackup or services.automysqlbackup.";
+      }
+    ];
+
+    services.automysqlbackup.config = mapAttrs (name: mkDefault) {
+      mysql_dump_username = user;
+      mysql_dump_host = "localhost";
+      backup_dir = "/var/backup/mysql";
+      db_exclude = [ "information_schema" "performance_schema" ];
+      mailcontent = "stdout";
+      mysql_dump_single_transaction = true;
+    };
+
+    systemd.timers.automysqlbackup = {
+      description = "automysqlbackup timer";
+      wantedBy = [ "timers.target" ];
+      timerConfig = {
+        OnCalendar = cfg.calendar;
+        AccuracySec = "5m";
+      };
+    };
+
+    systemd.services.automysqlbackup = {
+      description = "automysqlbackup service";
+      serviceConfig = {
+        User = user;
+        Group = group;
+        ExecStart = "${pkg}/bin/automysqlbackup ${configFile}";
+      };
+    };
+
+    environment.systemPackages = [ pkg ];
+
+    users.users.${user}.group = group;
+    users.groups.${group} = { };
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.config.backup_dir}' 0750 ${user} ${group} - -"
+    ];
+
+    services.mysql.ensureUsers = optional (config.services.mysql.enable && cfg.config.mysql_dump_host == "localhost") {
+      name = user;
+      ensurePermissions = { "*.*" = "SELECT, SHOW VIEW, TRIGGER, LOCK TABLES"; };
+    };
+
+  };
+}
diff --git a/nixos/modules/services/backup/mysql-backup.nix b/nixos/modules/services/backup/mysql-backup.nix
index f0c273ffebf1..ba6e154f6b3d 100644
--- a/nixos/modules/services/backup/mysql-backup.nix
+++ b/nixos/modules/services/backup/mysql-backup.nix
@@ -117,14 +117,12 @@ in
         enable = true;
         serviceConfig = {
           User = cfg.user;
-          PermissionsStartOnly = true;
         };
-        preStart = ''
-          mkdir -m 0700 -p ${cfg.location}
-          chown -R ${cfg.user} ${cfg.location}
-        '';
         script = backupScript;
       };
+      tmpfiles.rules = [
+        "d ${cfg.location} 0700 ${cfg.user} - - -"
+      ];
     };
   };
 
diff --git a/nixos/modules/services/backup/postgresql-backup.nix b/nixos/modules/services/backup/postgresql-backup.nix
index 11efa47ec5b2..17b410a97f3e 100644
--- a/nixos/modules/services/backup/postgresql-backup.nix
+++ b/nixos/modules/services/backup/postgresql-backup.nix
@@ -14,11 +14,6 @@ let
 
       requires = [ "postgresql.service" ];
 
-      preStart = ''
-        mkdir -m 0700 -p ${cfg.location}
-        chown postgres ${cfg.location}
-      '';
-
       script = ''
         umask 0077 # ensure backup is only readable by postgres user
 
@@ -32,7 +27,6 @@ let
 
       serviceConfig = {
         Type = "oneshot";
-        PermissionsStartOnly = "true";
         User = "postgres";
       };
 
@@ -107,6 +101,11 @@ in {
         message = "config.services.postgresqlBackup.backupAll cannot be used together with config.services.postgresqlBackup.databases";
       }];
     }
+    (mkIf cfg.enable {
+      systemd.tmpfiles.rules = [
+        "d '${cfg.location}' 0700 postgres - - -"
+      ];
+    })
     (mkIf (cfg.enable && cfg.backupAll) {
       systemd.services.postgresqlBackup =
         postgresqlBackupService "all" "${config.services.postgresql.package}/bin/pg_dumpall";
diff --git a/nixos/modules/services/backup/znapzend.nix b/nixos/modules/services/backup/znapzend.nix
index fc8a424190f7..9c7f84655727 100644
--- a/nixos/modules/services/backup/znapzend.nix
+++ b/nixos/modules/services/backup/znapzend.nix
@@ -7,28 +7,23 @@ let
 
   planDescription = ''
       The znapzend backup plan to use for the source.
-    </para>
-    <para>
+
       The plan specifies how often to backup and for how long to keep the
       backups. It consists of a series of retention periodes to interval
       associations:
-    </para>
-    <para>
+
       <literal>
         retA=>intA,retB=>intB,...
       </literal>
-    </para>
-    <para>
-    Both intervals and retention periods are expressed in standard units
-    of time or multiples of them. You can use both the full name or a
-    shortcut according to the following listing:
-    </para>
-    <para>
+
+      Both intervals and retention periods are expressed in standard units
+      of time or multiples of them. You can use both the full name or a
+      shortcut according to the following listing:
+
       <literal>
         second|sec|s, minute|min, hour|h, day|d, week|w, month|mon|m, year|y
       </literal>
-    </para>
-    <para>
+
       See <citerefentry><refentrytitle>znapzendzetup</refentrytitle><manvolnum>1</manvolnum></citerefentry> for more info.
   '';
   planExample = "1h=>10min,1d=>1h,1w=>1d,1m=>1w,1y=>1m";
@@ -139,12 +134,10 @@ let
           type = nullOr ints.u16;
           description = ''
               Port to use for <command>mbuffer</command>.
-            </para>
-            <para>
+
               If this is null, it will run <command>mbuffer</command> through
               ssh.
-            </para>
-            <para>
+
               If this is not null, it will run <command>mbuffer</command>
               directly through TCP, which is not encrypted but faster. In that
               case the given port needs to be open on the destination host.
@@ -255,7 +248,7 @@ let
   cfg = config.services.znapzend;
 
   onOff = b: if b then "on" else "off";
-  nullOff = b: if isNull b then "off" else toString b;
+  nullOff = b: if b == null then "off" else toString b;
   stripSlashes = replaceStrings [ "/" ] [ "." ];
 
   attrsToFile = config: concatStringsSep "\n" (builtins.attrValues (
@@ -263,7 +256,7 @@ let
 
   mkDestAttrs = dst: with dst;
     mapAttrs' (n: v: nameValuePair "dst_${label}${n}" v) ({
-      "" = optionalString (! isNull host) "${host}:" + dataset;
+      "" = optionalString (host != null) "${host}:" + dataset;
       _plan = plan;
     } // optionalAttrs (presend != null) {
       _precmd = presend;
@@ -382,8 +375,10 @@ in
             | xargs -I{} ${pkgs.znapzend}/bin/znapzendzetup delete "{}"
         '' + concatStringsSep "\n" (mapAttrsToList (dataset: config: ''
           echo Importing znapzend zetup ${config} for dataset ${dataset}
-          ${pkgs.znapzend}/bin/znapzendzetup import --write ${dataset} ${config}
-        '') files);
+          ${pkgs.znapzend}/bin/znapzendzetup import --write ${dataset} ${config} &
+        '') files) + ''
+          wait
+        '';
 
         serviceConfig = {
           ExecStart = let
diff --git a/nixos/modules/services/cluster/kubernetes/addon-manager.nix b/nixos/modules/services/cluster/kubernetes/addon-manager.nix
index 17f2dde31a71..ad7d17c9c283 100644
--- a/nixos/modules/services/cluster/kubernetes/addon-manager.nix
+++ b/nixos/modules/services/cluster/kubernetes/addon-manager.nix
@@ -62,19 +62,50 @@ in
       '';
     };
 
-    enable = mkEnableOption "Whether to enable Kubernetes addon manager.";
+    enable = mkEnableOption "Kubernetes addon manager";
+
+    kubeconfig = top.lib.mkKubeConfigOptions "Kubernetes addon manager";
+    bootstrapAddonsKubeconfig = top.lib.mkKubeConfigOptions "Kubernetes addon manager bootstrap";
   };
 
   ###### implementation
-  config = mkIf cfg.enable {
+  config = let
+
+    addonManagerPaths = filter (a: a != null) [
+      cfg.kubeconfig.caFile
+      cfg.kubeconfig.certFile
+      cfg.kubeconfig.keyFile
+    ];
+    bootstrapAddonsPaths = filter (a: a != null) [
+      cfg.bootstrapAddonsKubeconfig.caFile
+      cfg.bootstrapAddonsKubeconfig.certFile
+      cfg.bootstrapAddonsKubeconfig.keyFile
+    ];
+
+  in mkIf cfg.enable {
     environment.etc."kubernetes/addons".source = "${addons}/";
 
+    #TODO: Get rid of kube-addon-manager in the future for the following reasons
+    # - it is basically just a shell script wrapped around kubectl
+    # - it assumes that it is clusterAdmin or can gain clusterAdmin rights through serviceAccount
+    # - it is designed to be used with k8s system components only
+    # - it would be better with a more Nix-oriented way of managing addons
     systemd.services.kube-addon-manager = {
       description = "Kubernetes addon manager";
       wantedBy = [ "kubernetes.target" ];
-      after = [ "kube-apiserver.service" ];
-      environment.ADDON_PATH = "/etc/kubernetes/addons/";
-      path = [ pkgs.gawk ];
+      after = [ "kube-node-online.target" ];
+      before = [ "kubernetes.target" ];
+      environment = {
+        ADDON_PATH = "/etc/kubernetes/addons/";
+        KUBECONFIG = top.lib.mkKubeConfig "kube-addon-manager" cfg.kubeconfig;
+      };
+      path = with pkgs; [ gawk kubectl ];
+      preStart = ''
+        until kubectl -n kube-system get serviceaccounts/default 2>/dev/null; do
+          echo kubectl -n kube-system get serviceaccounts/default: exit status $?
+          sleep 2
+        done
+      '';
       serviceConfig = {
         Slice = "kubernetes.slice";
         ExecStart = "${top.package}/bin/kube-addons";
@@ -84,8 +115,52 @@ in
         Restart = "on-failure";
         RestartSec = 10;
       };
+      unitConfig.ConditionPathExists = addonManagerPaths;
     };
 
+    systemd.paths.kube-addon-manager = {
+      wantedBy = [ "kube-addon-manager.service" ];
+      pathConfig = {
+        PathExists = addonManagerPaths;
+        PathChanged = addonManagerPaths;
+      };
+    };
+
+    services.kubernetes.addonManager.kubeconfig.server = mkDefault top.apiserverAddress;
+
+    systemd.services.kube-addon-manager-bootstrap = mkIf (top.apiserver.enable && top.addonManager.bootstrapAddons != {}) {
+      wantedBy = [ "kube-control-plane-online.target" ];
+      after = [ "kube-apiserver.service" ];
+      before = [ "kube-control-plane-online.target" ];
+      path = [ pkgs.kubectl ];
+      environment = {
+        KUBECONFIG = top.lib.mkKubeConfig "kube-addon-manager-bootstrap" cfg.bootstrapAddonsKubeconfig;
+      };
+      preStart = with pkgs; let
+        files = mapAttrsToList (n: v: writeText "${n}.json" (builtins.toJSON v))
+          cfg.bootstrapAddons;
+      in ''
+        until kubectl auth can-i '*' '*' -q 2>/dev/null; do
+          echo kubectl auth can-i '*' '*': exit status $?
+          sleep 2
+        done
+
+        kubectl apply -f ${concatStringsSep " \\\n -f " files}
+      '';
+      script = "echo Ok";
+      unitConfig.ConditionPathExists = bootstrapAddonsPaths;
+    };
+
+    systemd.paths.kube-addon-manager-bootstrap = {
+      wantedBy = [ "kube-addon-manager-bootstrap.service" ];
+      pathConfig = {
+        PathExists = bootstrapAddonsPaths;
+        PathChanged = bootstrapAddonsPaths;
+      };
+    };
+
+    services.kubernetes.addonManager.bootstrapAddonsKubeconfig.server = mkDefault top.apiserverAddress;
+
     services.kubernetes.addonManager.bootstrapAddons = mkIf isRBACEnabled
     (let
       name = system:kube-addon-manager;
diff --git a/nixos/modules/services/cluster/kubernetes/addons/dashboard.nix b/nixos/modules/services/cluster/kubernetes/addons/dashboard.nix
index 454e7d35bc01..2295694ffc74 100644
--- a/nixos/modules/services/cluster/kubernetes/addons/dashboard.nix
+++ b/nixos/modules/services/cluster/kubernetes/addons/dashboard.nix
@@ -169,6 +169,23 @@ in {
         };
       };
 
+      kubernetes-dashboard-cm = {
+        apiVersion = "v1";
+        kind = "ConfigMap";
+        metadata = {
+          labels = {
+            k8s-app = "kubernetes-dashboard";
+            # Allows editing resource and makes sure it is created first.
+            "addonmanager.kubernetes.io/mode" = "EnsureExists";
+          };
+          name = "kubernetes-dashboard-settings";
+          namespace = "kube-system";
+        };
+      };
+    };
+
+    services.kubernetes.addonManager.bootstrapAddons = mkMerge [{
+
       kubernetes-dashboard-sa = {
         apiVersion = "v1";
         kind = "ServiceAccount";
@@ -210,20 +227,9 @@ in {
         };
         type = "Opaque";
       };
-      kubernetes-dashboard-cm = {
-        apiVersion = "v1";
-        kind = "ConfigMap";
-        metadata = {
-          labels = {
-            k8s-app = "kubernetes-dashboard";
-            # Allows editing resource and makes sure it is created first.
-            "addonmanager.kubernetes.io/mode" = "EnsureExists";
-          };
-          name = "kubernetes-dashboard-settings";
-          namespace = "kube-system";
-        };
-      };
-    } // (optionalAttrs cfg.rbac.enable
+    }
+
+    (optionalAttrs cfg.rbac.enable
       (let
         subjects = [{
           kind = "ServiceAccount";
@@ -323,6 +329,6 @@ in {
             inherit subjects;
           };
         })
-    ));
+    ))];
   };
 }
diff --git a/nixos/modules/services/cluster/kubernetes/addons/dns.nix b/nixos/modules/services/cluster/kubernetes/addons/dns.nix
index 4368159ea6e3..ee0ac632ecf0 100644
--- a/nixos/modules/services/cluster/kubernetes/addons/dns.nix
+++ b/nixos/modules/services/cluster/kubernetes/addons/dns.nix
@@ -3,7 +3,7 @@
 with lib;
 
 let
-  version = "1.3.1";
+  version = "1.5.0";
   cfg = config.services.kubernetes.addons.dns;
   ports = {
     dns = 10053;
@@ -55,9 +55,9 @@ in {
       type = types.attrs;
       default = {
         imageName = "coredns/coredns";
-        imageDigest = "sha256:02382353821b12c21b062c59184e227e001079bb13ebd01f9d3270ba0fcbf1e4";
+        imageDigest = "sha256:e83beb5e43f8513fa735e77ffc5859640baea30a882a11cc75c4c3244a737d3c";
         finalImageTag = version;
-        sha256 = "0vbylgyxv2jm2mnzk6f28jbsj305zsxmx3jr6ngjq461czcl5fi5";
+        sha256 = "15sbmhrxjxidj0j0cccn1qxpg6al175w43m6ngspl0mc132zqc9q";
       };
     };
   };
@@ -160,7 +160,7 @@ in {
               fallthrough in-addr.arpa ip6.arpa
             }
             prometheus :${toString ports.metrics}
-            proxy . /etc/resolv.conf
+            forward . /etc/resolv.conf
             cache 30
             loop
             reload
diff --git a/nixos/modules/services/cluster/kubernetes/apiserver.nix b/nixos/modules/services/cluster/kubernetes/apiserver.nix
index 455d02396040..f293dd79f42a 100644
--- a/nixos/modules/services/cluster/kubernetes/apiserver.nix
+++ b/nixos/modules/services/cluster/kubernetes/apiserver.nix
@@ -184,6 +184,24 @@ in
       type = bool;
     };
 
+    preferredAddressTypes = mkOption {
+      description = "List of the preferred NodeAddressTypes to use for kubelet connections.";
+      type = nullOr str;
+      default = null;
+    };
+
+    proxyClientCertFile = mkOption {
+      description = "Client certificate to use for connections to proxy.";
+      default = null;
+      type = nullOr path;
+    };
+
+    proxyClientKeyFile = mkOption {
+      description = "Key to use for connections to proxy.";
+      default = null;
+      type = nullOr path;
+    };
+
     runtimeConfig = mkOption {
       description = ''
         Api runtime configuration. See
@@ -272,11 +290,32 @@ in
   ###### implementation
   config = mkMerge [
 
-    (mkIf cfg.enable {
+    (let
+
+      apiserverPaths = filter (a: a != null) [
+        cfg.clientCaFile
+        cfg.etcd.caFile
+        cfg.etcd.certFile
+        cfg.etcd.keyFile
+        cfg.kubeletClientCaFile
+        cfg.kubeletClientCertFile
+        cfg.kubeletClientKeyFile
+        cfg.serviceAccountKeyFile
+        cfg.tlsCertFile
+        cfg.tlsKeyFile
+      ];
+      etcdPaths = filter (a: a != null) [
+        config.services.etcd.trustedCaFile
+        config.services.etcd.certFile
+        config.services.etcd.keyFile
+      ];
+
+    in mkIf cfg.enable {
         systemd.services.kube-apiserver = {
           description = "Kubernetes APIServer Service";
-          wantedBy = [ "kubernetes.target" ];
-          after = [ "network.target" ];
+          wantedBy = [ "kube-control-plane-online.target" ];
+          after = [ "certmgr.service" ];
+          before = [ "kube-control-plane-online.target" ];
           serviceConfig = {
             Slice = "kubernetes.slice";
             ExecStart = ''${top.package}/bin/kube-apiserver \
@@ -316,6 +355,12 @@ in
                 "--kubelet-client-certificate=${cfg.kubeletClientCertFile}"} \
               ${optionalString (cfg.kubeletClientKeyFile != null)
                 "--kubelet-client-key=${cfg.kubeletClientKeyFile}"} \
+              ${optionalString (cfg.preferredAddressTypes != null)
+                "--kubelet-preferred-address-types=${cfg.preferredAddressTypes}"} \
+              ${optionalString (cfg.proxyClientCertFile != null)
+                "--proxy-client-cert-file=${cfg.proxyClientCertFile}"} \
+              ${optionalString (cfg.proxyClientKeyFile != null)
+                "--proxy-client-key-file=${cfg.proxyClientKeyFile}"} \
               --insecure-bind-address=${cfg.insecureBindAddress} \
               --insecure-port=${toString cfg.insecurePort} \
               ${optionalString (cfg.runtimeConfig != "")
@@ -341,6 +386,15 @@ in
             Restart = "on-failure";
             RestartSec = 5;
           };
+          unitConfig.ConditionPathExists = apiserverPaths;
+        };
+
+        systemd.paths.kube-apiserver = mkIf top.apiserver.enable {
+          wantedBy = [ "kube-apiserver.service" ];
+          pathConfig = {
+            PathExists = apiserverPaths;
+            PathChanged = apiserverPaths;
+          };
         };
 
         services.etcd = {
@@ -354,6 +408,18 @@ in
           initialAdvertisePeerUrls = mkDefault ["https://${top.masterAddress}:2380"];
         };
 
+        systemd.services.etcd = {
+          unitConfig.ConditionPathExists = etcdPaths;
+        };
+
+        systemd.paths.etcd = {
+          wantedBy = [ "etcd.service" ];
+          pathConfig = {
+            PathExists = etcdPaths;
+            PathChanged = etcdPaths;
+          };
+        };
+
         services.kubernetes.addonManager.bootstrapAddons = mkIf isRBACEnabled {
 
           apiserver-kubelet-api-admin-crb = {
@@ -389,6 +455,11 @@ in
                   ] ++ cfg.extraSANs;
           action = "systemctl restart kube-apiserver.service";
         };
+        apiserverProxyClient = mkCert {
+          name = "kube-apiserver-proxy-client";
+          CN = "front-proxy-client";
+          action = "systemctl restart kube-apiserver.service";
+        };
         apiserverKubeletClient = mkCert {
           name = "kube-apiserver-kubelet-client";
           CN = "system:kube-apiserver";
diff --git a/nixos/modules/services/cluster/kubernetes/controller-manager.nix b/nixos/modules/services/cluster/kubernetes/controller-manager.nix
index 060fd9b78db6..b94e8bd86d4c 100644
--- a/nixos/modules/services/cluster/kubernetes/controller-manager.nix
+++ b/nixos/modules/services/cluster/kubernetes/controller-manager.nix
@@ -28,7 +28,7 @@ in
       type = str;
     };
 
-    enable = mkEnableOption "Kubernetes controller manager.";
+    enable = mkEnableOption "Kubernetes controller manager";
 
     extraOpts = mkOption {
       description = "Kubernetes controller manager extra command line options.";
@@ -104,11 +104,31 @@ in
   };
 
   ###### implementation
-  config = mkIf cfg.enable {
-    systemd.services.kube-controller-manager = {
+  config = let
+
+    controllerManagerPaths = filter (a: a != null) [
+      cfg.kubeconfig.caFile
+      cfg.kubeconfig.certFile
+      cfg.kubeconfig.keyFile
+      cfg.rootCaFile
+      cfg.serviceAccountKeyFile
+      cfg.tlsCertFile
+      cfg.tlsKeyFile
+    ];
+
+  in mkIf cfg.enable {
+    systemd.services.kube-controller-manager = rec {
       description = "Kubernetes Controller Manager Service";
-      wantedBy = [ "kubernetes.target" ];
+      wantedBy = [ "kube-control-plane-online.target" ];
       after = [ "kube-apiserver.service" ];
+      before = [ "kube-control-plane-online.target" ];
+      environment.KUBECONFIG = top.lib.mkKubeConfig "kube-controller-manager" cfg.kubeconfig;
+      preStart = ''
+        until kubectl auth can-i get /api -q 2>/dev/null; do
+          echo kubectl auth can-i get /api: exit status $?
+          sleep 2
+        done
+      '';
       serviceConfig = {
         RestartSec = "30s";
         Restart = "on-failure";
@@ -120,7 +140,7 @@ in
             "--cluster-cidr=${cfg.clusterCidr}"} \
           ${optionalString (cfg.featureGates != [])
             "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.featureGates}"} \
-          --kubeconfig=${top.lib.mkKubeConfig "kube-controller-manager" cfg.kubeconfig} \
+          --kubeconfig=${environment.KUBECONFIG} \
           --leader-elect=${boolToString cfg.leaderElect} \
           ${optionalString (cfg.rootCaFile!=null)
             "--root-ca-file=${cfg.rootCaFile}"} \
@@ -141,7 +161,16 @@ in
         User = "kubernetes";
         Group = "kubernetes";
       };
-      path = top.path;
+      path = top.path ++ [ pkgs.kubectl ];
+      unitConfig.ConditionPathExists = controllerManagerPaths;
+    };
+
+    systemd.paths.kube-controller-manager = {
+      wantedBy = [ "kube-controller-manager.service" ];
+      pathConfig = {
+        PathExists = controllerManagerPaths;
+        PathChanged = controllerManagerPaths;
+      };
     };
 
     services.kubernetes.pki.certs = with top.lib; {
diff --git a/nixos/modules/services/cluster/kubernetes/default.nix b/nixos/modules/services/cluster/kubernetes/default.nix
index 3e53d18f8bbf..5e46bfc4240f 100644
--- a/nixos/modules/services/cluster/kubernetes/default.nix
+++ b/nixos/modules/services/cluster/kubernetes/default.nix
@@ -263,6 +263,29 @@ in {
         wantedBy = [ "multi-user.target" ];
       };
 
+      systemd.targets.kube-control-plane-online = {
+        wantedBy = [ "kubernetes.target" ];
+        before = [ "kubernetes.target" ];
+      };
+
+      systemd.services.kube-control-plane-online = rec {
+        description = "Kubernetes control plane is online";
+        wantedBy = [ "kube-control-plane-online.target" ];
+        after = [ "kube-scheduler.service" "kube-controller-manager.service" ];
+        before = [ "kube-control-plane-online.target" ];
+        path = [ pkgs.curl ];
+        preStart = ''
+          until curl -Ssf ${cfg.apiserverAddress}/healthz do
+            echo curl -Ssf ${cfg.apiserverAddress}/healthz: exit status $?
+            sleep 3
+          done
+        '';
+        script = "echo Ok";
+        serviceConfig = {
+          TimeoutSec = "500";
+        };
+      };
+
       systemd.tmpfiles.rules = [
         "d /opt/cni/bin 0755 root root -"
         "d /run/kubernetes 0755 kubernetes kubernetes -"
@@ -286,6 +309,8 @@ in {
       services.kubernetes.apiserverAddress = mkDefault ("https://${if cfg.apiserver.advertiseAddress != null
                           then cfg.apiserver.advertiseAddress
                           else "${cfg.masterAddress}:${toString cfg.apiserver.securePort}"}");
+
+      services.kubernetes.kubeconfig.server = mkDefault cfg.apiserverAddress;
     })
   ];
 }
diff --git a/nixos/modules/services/cluster/kubernetes/flannel.nix b/nixos/modules/services/cluster/kubernetes/flannel.nix
index 93ee2fd65eeb..d9437427d6d1 100644
--- a/nixos/modules/services/cluster/kubernetes/flannel.nix
+++ b/nixos/modules/services/cluster/kubernetes/flannel.nix
@@ -23,17 +23,27 @@ in
 {
   ###### interface
   options.services.kubernetes.flannel = {
-    enable = mkEnableOption "enable flannel networking";
+    enable = mkEnableOption "flannel networking";
+    kubeconfig = top.lib.mkKubeConfigOptions "Kubernetes flannel";
   };
 
   ###### implementation
-  config = mkIf cfg.enable {
+  config = let
+
+    flannelPaths = filter (a: a != null) [
+      cfg.kubeconfig.caFile
+      cfg.kubeconfig.certFile
+      cfg.kubeconfig.keyFile
+    ];
+    kubeconfig = top.lib.mkKubeConfig "flannel" cfg.kubeconfig;
+
+  in mkIf cfg.enable {
     services.flannel = {
 
       enable = mkDefault true;
       network = mkDefault top.clusterCidr;
-      inherit storageBackend;
-      nodeName = config.services.kubernetes.kubelet.hostname;
+      inherit storageBackend kubeconfig;
+      nodeName = top.kubelet.hostname;
     };
 
     services.kubernetes.kubelet = {
@@ -48,24 +58,66 @@ in
       }];
     };
 
-    systemd.services."mk-docker-opts" = {
+    systemd.services.mk-docker-opts = {
       description = "Pre-Docker Actions";
+      wantedBy = [ "flannel.target" ];
+      before = [ "flannel.target" ];
       path = with pkgs; [ gawk gnugrep ];
       script = ''
         ${mkDockerOpts}/mk-docker-opts -d /run/flannel/docker
         systemctl restart docker
       '';
+      unitConfig.ConditionPathExists = [ "/run/flannel/subnet.env" ];
       serviceConfig.Type = "oneshot";
     };
 
-    systemd.paths."flannel-subnet-env" = {
-      wantedBy = [ "flannel.service" ];
+    systemd.paths.flannel-subnet-env = {
+      wantedBy = [ "mk-docker-opts.service" ];
       pathConfig = {
-        PathModified = "/run/flannel/subnet.env";
+        PathExists = [ "/run/flannel/subnet.env" ];
+        PathChanged = [ "/run/flannel/subnet.env" ];
         Unit = "mk-docker-opts.service";
       };
     };
 
+    systemd.targets.flannel = {
+      wantedBy = [ "kube-node-online.target" ];
+      before = [ "kube-node-online.target" ];
+    };
+
+    systemd.services.flannel = {
+      wantedBy = [ "flannel.target" ];
+      after = [ "kubelet.target" ];
+      before = [ "flannel.target" ];
+      path = with pkgs; [ iptables kubectl ];
+      environment.KUBECONFIG = kubeconfig;
+      preStart = let
+        args = [
+          "--selector=kubernetes.io/hostname=${top.kubelet.hostname}"
+          # flannel exits if node is not registered yet, before that there is no podCIDR
+          "--output=jsonpath={.items[0].spec.podCIDR}"
+          # if jsonpath cannot be resolved exit with status 1
+          "--allow-missing-template-keys=false"
+        ];
+      in ''
+        until kubectl get nodes ${concatStringsSep " " args} 2>/dev/null; do
+          echo Waiting for ${top.kubelet.hostname} to be RegisteredNode
+          sleep 1
+        done
+      '';
+      unitConfig.ConditionPathExists = flannelPaths;
+    };
+
+    systemd.paths.flannel = {
+      wantedBy = [ "flannel.service" ];
+      pathConfig = {
+        PathExists = flannelPaths;
+        PathChanged = flannelPaths;
+      };
+    };
+
+    services.kubernetes.flannel.kubeconfig.server = mkDefault top.apiserverAddress;
+
     systemd.services.docker = {
       environment.DOCKER_OPTS = "-b none";
       serviceConfig.EnvironmentFile = "-/run/flannel/docker";
@@ -92,7 +144,6 @@ in
 
     # give flannel som kubernetes rbac permissions if applicable
     services.kubernetes.addonManager.bootstrapAddons = mkIf ((storageBackend == "kubernetes") && (elem "RBAC" top.apiserver.authorizationMode)) {
-
       flannel-cr = {
         apiVersion = "rbac.authorization.k8s.io/v1beta1";
         kind = "ClusterRole";
@@ -128,7 +179,6 @@ in
           name = "flannel-client";
         }];
       };
-
     };
   };
 }
diff --git a/nixos/modules/services/cluster/kubernetes/kubelet.nix b/nixos/modules/services/cluster/kubernetes/kubelet.nix
index c94bb28bf7fb..ccc8a16e788a 100644
--- a/nixos/modules/services/cluster/kubernetes/kubelet.nix
+++ b/nixos/modules/services/cluster/kubernetes/kubelet.nix
@@ -7,9 +7,9 @@ let
   cfg = top.kubelet;
 
   cniConfig =
-    if cfg.cni.config != [] && !(isNull cfg.cni.configDir) then
+    if cfg.cni.config != [] && cfg.cni.configDir != null then
       throw "Verbatim CNI-config and CNI configDir cannot both be set."
-    else if !(isNull cfg.cni.configDir) then
+    else if cfg.cni.configDir != null then
       cfg.cni.configDir
     else
       (pkgs.buildEnv {
@@ -241,21 +241,28 @@ in
 
   ###### implementation
   config = mkMerge [
-    (mkIf cfg.enable {
+    (let
+
+      kubeletPaths = filter (a: a != null) [
+        cfg.kubeconfig.caFile
+        cfg.kubeconfig.certFile
+        cfg.kubeconfig.keyFile
+        cfg.clientCaFile
+        cfg.tlsCertFile
+        cfg.tlsKeyFile
+      ];
+
+    in mkIf cfg.enable {
       services.kubernetes.kubelet.seedDockerImages = [infraContainer];
 
       systemd.services.kubelet = {
         description = "Kubernetes Kubelet Service";
-        wantedBy = [ "kubernetes.target" ];
-        after = [ "network.target" "docker.service" "kube-apiserver.service" ];
+        wantedBy = [ "kubelet.target" ];
+        after = [ "kube-control-plane-online.target" ];
+        before = [ "kubelet.target" ];
         path = with pkgs; [ gitMinimal openssh docker utillinux iproute ethtool thin-provisioning-tools iptables socat ] ++ top.path;
         preStart = ''
-          ${concatMapStrings (img: ''
-            echo "Seeding docker image: ${img}"
-            docker load <${img}
-          '') cfg.seedDockerImages}
-
-          rm /opt/cni/bin/* || true
+          rm -f /opt/cni/bin/* || true
           ${concatMapStrings (package: ''
             echo "Linking cni package: ${package}"
             ln -fs ${package}/bin/* /opt/cni/bin
@@ -308,6 +315,56 @@ in
           '';
           WorkingDirectory = top.dataDir;
         };
+        unitConfig.ConditionPathExists = kubeletPaths;
+      };
+
+      systemd.paths.kubelet = {
+        wantedBy =  [ "kubelet.service" ];
+        pathConfig = {
+          PathExists = kubeletPaths;
+          PathChanged = kubeletPaths;
+        };
+      };
+
+      systemd.services.docker.before = [ "kubelet.service" ];
+
+      systemd.services.docker-seed-images = {
+        wantedBy = [ "docker.service" ];
+        after = [ "docker.service" ];
+        before = [ "kubelet.service" ];
+        path = with pkgs; [ docker ];
+        preStart = ''
+          ${concatMapStrings (img: ''
+            echo "Seeding docker image: ${img}"
+            docker load <${img}
+          '') cfg.seedDockerImages}
+        '';
+        script = "echo Ok";
+        serviceConfig.Type = "oneshot";
+        serviceConfig.RemainAfterExit = true;
+        serviceConfig.Slice = "kubernetes.slice";
+      };
+
+      systemd.services.kubelet-online = {
+        wantedBy = [ "kube-node-online.target" ];
+        after = [ "flannel.target" "kubelet.target" ];
+        before = [ "kube-node-online.target" ];
+        # it is complicated. flannel needs kubelet to run the pause container before
+        # it discusses the node CIDR with apiserver and afterwards configures and restarts
+        # dockerd. Until then prevent creating any pods because they have to be recreated anyway
+        # because the network of docker0 has been changed by flannel.
+        script = let
+          docker-env = "/run/flannel/docker";
+          flannel-date = "stat --print=%Y ${docker-env}";
+          docker-date = "systemctl show --property=ActiveEnterTimestamp --value docker";
+        in ''
+          until test -f ${docker-env} ; do sleep 1 ; done
+          while test `${flannel-date}` -gt `date +%s --date="$(${docker-date})"` ; do
+            sleep 1
+          done
+        '';
+        serviceConfig.Type = "oneshot";
+        serviceConfig.Slice = "kubernetes.slice";
       };
 
       # Allways include cni plugins
@@ -316,7 +373,7 @@ in
       boot.kernelModules = ["br_netfilter"];
 
       services.kubernetes.kubelet.hostname = with config.networking;
-        mkDefault (hostName + optionalString (!isNull domain) ".${domain}");
+        mkDefault (hostName + optionalString (domain != null) ".${domain}");
 
       services.kubernetes.pki.certs = with top.lib; {
         kubelet = mkCert {
@@ -354,5 +411,16 @@ in
       };
     })
 
+    {
+      systemd.targets.kubelet = {
+        wantedBy = [ "kube-node-online.target" ];
+        before = [ "kube-node-online.target" ];
+      };
+
+      systemd.targets.kube-node-online = {
+        wantedBy = [ "kubernetes.target" ];
+        before = [ "kubernetes.target" ];
+      };
+    }
   ];
 }
diff --git a/nixos/modules/services/cluster/kubernetes/pki.nix b/nixos/modules/services/cluster/kubernetes/pki.nix
index 38deca23a990..e68660e8bdd4 100644
--- a/nixos/modules/services/cluster/kubernetes/pki.nix
+++ b/nixos/modules/services/cluster/kubernetes/pki.nix
@@ -27,12 +27,11 @@ let
   certmgrAPITokenPath = "${top.secretsPath}/${cfsslAPITokenBaseName}";
   cfsslAPITokenLength = 32;
 
-  clusterAdminKubeconfig = with cfg.certs.clusterAdmin;
-    top.lib.mkKubeConfig "cluster-admin" {
-        server = top.apiserverAddress;
-        certFile = cert;
-        keyFile = key;
-    };
+  clusterAdminKubeconfig = with cfg.certs.clusterAdmin; {
+    server = top.apiserverAddress;
+    certFile = cert;
+    keyFile = key;
+  };
 
   remote = with config.services; "https://${kubernetes.masterAddress}:${toString cfssl.port}";
 in
@@ -40,7 +39,7 @@ in
   ###### interface
   options.services.kubernetes.pki = with lib.types; {
 
-    enable = mkEnableOption "Whether to enable easyCert issuer service.";
+    enable = mkEnableOption "easyCert issuer service";
 
     certs = mkOption {
       description = "List of certificate specs to feed to cert generator.";
@@ -119,6 +118,12 @@ in
     cfsslCertPathPrefix = "${config.services.cfssl.dataDir}/cfssl";
     cfsslCert = "${cfsslCertPathPrefix}.pem";
     cfsslKey = "${cfsslCertPathPrefix}-key.pem";
+    cfsslPort = toString config.services.cfssl.port;
+
+    certmgrPaths = [
+      top.caFile
+      certmgrAPITokenPath
+    ];
   in
   {
 
@@ -168,13 +173,40 @@ in
         chown cfssl "${cfsslAPITokenPath}" && chmod 400 "${cfsslAPITokenPath}"
       '')]);
 
+    systemd.targets.cfssl-online = {
+      wantedBy = [ "network-online.target" ];
+      after = [ "cfssl.service" "network-online.target" "cfssl-online.service" ];
+    };
+
+    systemd.services.cfssl-online = {
+      description = "Wait for ${remote} to be reachable.";
+      wantedBy = [ "cfssl-online.target" ];
+      before = [ "cfssl-online.target" ];
+      path = [ pkgs.curl ];
+      preStart = ''
+        until curl --fail-early -fskd '{}' ${remote}/api/v1/cfssl/info -o /dev/null; do
+          echo curl ${remote}/api/v1/cfssl/info: exit status $?
+          sleep 2
+        done
+      '';
+      script = "echo Ok";
+      serviceConfig = {
+        TimeoutSec = "300";
+      };
+    };
+
     systemd.services.kube-certmgr-bootstrap = {
       description = "Kubernetes certmgr bootstrapper";
-      wantedBy = [ "certmgr.service" ];
-      after = [ "cfssl.target" ];
+      wantedBy = [ "cfssl-online.target" ];
+      after = [ "cfssl-online.target" ];
+      before = [ "certmgr.service" ];
+      path = with pkgs; [ curl cfssl ];
       script = concatStringsSep "\n" [''
         set -e
 
+        mkdir -p $(dirname ${certmgrAPITokenPath})
+        mkdir -p $(dirname ${top.caFile})
+
         # If there's a cfssl (cert issuer) running locally, then don't rely on user to
         # manually paste it in place. Just symlink.
         # otherwise, create the target file, ready for users to insert the token
@@ -186,15 +218,18 @@ in
         fi
       ''
       (optionalString (cfg.pkiTrustOnBootstrap) ''
-        if [ ! -f "${top.caFile}" ] || [ $(cat "${top.caFile}" | wc -c) -lt 1 ]; then
-          ${pkgs.curl}/bin/curl --fail-early -f -kd '{}' ${remote}/api/v1/cfssl/info | \
-            ${pkgs.cfssl}/bin/cfssljson -stdout >${top.caFile}
+        if [ ! -s "${top.caFile}" ]; then
+          until test -s ${top.caFile}.json; do
+            sleep 2
+            curl --fail-early -fskd '{}' ${remote}/api/v1/cfssl/info -o ${top.caFile}.json
+          done
+          cfssljson -f ${top.caFile}.json -stdout >${top.caFile}
+          rm ${top.caFile}.json
         fi
       '')
       ];
       serviceConfig = {
-        RestartSec = "10s";
-        Restart = "on-failure";
+        TimeoutSec = "500";
       };
     };
 
@@ -230,35 +265,28 @@ in
           mapAttrs mkSpec cfg.certs;
       };
 
-      #TODO: Get rid of kube-addon-manager in the future for the following reasons
-      # - it is basically just a shell script wrapped around kubectl
-      # - it assumes that it is clusterAdmin or can gain clusterAdmin rights through serviceAccount
-      # - it is designed to be used with k8s system components only
-      # - it would be better with a more Nix-oriented way of managing addons
-      systemd.services.kube-addon-manager = mkIf top.addonManager.enable (mkMerge [{
-        environment.KUBECONFIG = with cfg.certs.addonManager;
-          top.lib.mkKubeConfig "addon-manager" {
-            server = top.apiserverAddress;
-            certFile = cert;
-            keyFile = key;
-          };
-        }
-
-        (optionalAttrs (top.addonManager.bootstrapAddons != {}) {
-          serviceConfig.PermissionsStartOnly = true;
-          preStart = with pkgs;
-          let
-            files = mapAttrsToList (n: v: writeText "${n}.json" (builtins.toJSON v))
-              top.addonManager.bootstrapAddons;
-          in
-          ''
-            export KUBECONFIG=${clusterAdminKubeconfig}
-            ${kubectl}/bin/kubectl apply -f ${concatStringsSep " \\\n -f " files}
-          '';
-        })]);
-
-      environment.etc.${cfg.etcClusterAdminKubeconfig}.source = mkIf (!isNull cfg.etcClusterAdminKubeconfig)
-        clusterAdminKubeconfig;
+      systemd.services.certmgr = {
+        wantedBy = [ "cfssl-online.target" ];
+        after = [ "cfssl-online.target" "kube-certmgr-bootstrap.service" ];
+        preStart = ''
+          while ! test -s ${certmgrAPITokenPath} ; do
+            sleep 1
+            echo Waiting for ${certmgrAPITokenPath}
+          done
+        '';
+        unitConfig.ConditionPathExists = certmgrPaths;
+      };
+
+      systemd.paths.certmgr = {
+        wantedBy = [ "certmgr.service" ];
+        pathConfig = {
+          PathExists = certmgrPaths;
+          PathChanged = certmgrPaths;
+        };
+      };
+
+      environment.etc.${cfg.etcClusterAdminKubeconfig}.source = mkIf (cfg.etcClusterAdminKubeconfig != null)
+        (top.lib.mkKubeConfig "cluster-admin" clusterAdminKubeconfig);
 
       environment.systemPackages = mkIf (top.kubelet.enable || top.proxy.enable) [
       (pkgs.writeScriptBin "nixos-kubernetes-node-join" ''
@@ -284,38 +312,22 @@ in
           exit 1
         fi
 
+        do_restart=$(test -s ${certmgrAPITokenPath} && echo -n y || echo -n n)
+
         echo $token > ${certmgrAPITokenPath}
         chmod 600 ${certmgrAPITokenPath}
 
-        echo "Restarting certmgr..." >&1
-        systemctl restart certmgr
-
-        echo "Waiting for certs to appear..." >&1
-
-        ${optionalString top.kubelet.enable ''
-          while [ ! -f ${cfg.certs.kubelet.cert} ]; do sleep 1; done
-          echo "Restarting kubelet..." >&1
-          systemctl restart kubelet
-        ''}
-
-        ${optionalString top.proxy.enable ''
-          while [ ! -f ${cfg.certs.kubeProxyClient.cert} ]; do sleep 1; done
-          echo "Restarting kube-proxy..." >&1
-          systemctl restart kube-proxy
-        ''}
-
-        ${optionalString top.flannel.enable ''
-          while [ ! -f ${cfg.certs.flannelClient.cert} ]; do sleep 1; done
-          echo "Restarting flannel..." >&1
-          systemctl restart flannel
-        ''}
+        if [ y = $do_restart ]; then
+          echo "Restarting certmgr..." >&1
+          systemctl restart certmgr
+        fi
 
-        echo "Node joined succesfully"
+        echo "Node joined succesfully" >&1
       '')];
 
       # isolate etcd on loopback at the master node
       # easyCerts doesn't support multimaster clusters anyway atm.
-      services.etcd = with cfg.certs.etcd; {
+      services.etcd = mkIf top.apiserver.enable (with cfg.certs.etcd; {
         listenClientUrls = ["https://127.0.0.1:2379"];
         listenPeerUrls = ["https://127.0.0.1:2380"];
         advertiseClientUrls = ["https://etcd.local:2379"];
@@ -324,19 +336,11 @@ in
         certFile = mkDefault cert;
         keyFile = mkDefault key;
         trustedCaFile = mkDefault caCert;
-      };
+      });
       networking.extraHosts = mkIf (config.services.etcd.enable) ''
         127.0.0.1 etcd.${top.addons.dns.clusterDomain} etcd.local
       '';
 
-      services.flannel = with cfg.certs.flannelClient; {
-        kubeconfig = top.lib.mkKubeConfig "flannel" {
-          server = top.apiserverAddress;
-          certFile = cert;
-          keyFile = key;
-        };
-      };
-
       services.kubernetes = {
 
         apiserver = mkIf top.apiserver.enable (with cfg.certs.apiServer; {
@@ -353,7 +357,16 @@ in
           kubeletClientCaFile = mkDefault caCert;
           kubeletClientCertFile = mkDefault cfg.certs.apiserverKubeletClient.cert;
           kubeletClientKeyFile = mkDefault cfg.certs.apiserverKubeletClient.key;
+          proxyClientCertFile = mkDefault cfg.certs.apiserverProxyClient.cert;
+          proxyClientKeyFile = mkDefault cfg.certs.apiserverProxyClient.key;
         });
+        addonManager = mkIf top.addonManager.enable {
+          kubeconfig = with cfg.certs.addonManager; {
+            certFile = mkDefault cert;
+            keyFile = mkDefault key;
+          };
+          bootstrapAddonsKubeconfig = clusterAdminKubeconfig;
+        };
         controllerManager = mkIf top.controllerManager.enable {
           serviceAccountKeyFile = mkDefault cfg.certs.serviceAccount.key;
           rootCaFile = cfg.certs.controllerManagerClient.caCert;
@@ -362,6 +375,12 @@ in
             keyFile = mkDefault key;
           };
         };
+        flannel = mkIf top.flannel.enable {
+          kubeconfig = with cfg.certs.flannelClient; {
+            certFile = cert;
+            keyFile = key;
+          };
+        };
         scheduler = mkIf top.scheduler.enable {
           kubeconfig = with cfg.certs.schedulerClient; {
             certFile = mkDefault cert;
diff --git a/nixos/modules/services/cluster/kubernetes/proxy.nix b/nixos/modules/services/cluster/kubernetes/proxy.nix
index 83cd3e231000..23f4d97b7030 100644
--- a/nixos/modules/services/cluster/kubernetes/proxy.nix
+++ b/nixos/modules/services/cluster/kubernetes/proxy.nix
@@ -17,7 +17,7 @@ in
       type = str;
     };
 
-    enable = mkEnableOption "Whether to enable Kubernetes proxy.";
+    enable = mkEnableOption "Kubernetes proxy";
 
     extraOpts = mkOption {
       description = "Kubernetes proxy extra command line options.";
@@ -45,12 +45,28 @@ in
   };
 
   ###### implementation
-  config = mkIf cfg.enable {
-    systemd.services.kube-proxy = {
+  config = let
+
+    proxyPaths = filter (a: a != null) [
+      cfg.kubeconfig.caFile
+      cfg.kubeconfig.certFile
+      cfg.kubeconfig.keyFile
+    ];
+
+  in mkIf cfg.enable {
+    systemd.services.kube-proxy = rec {
       description = "Kubernetes Proxy Service";
-      wantedBy = [ "kubernetes.target" ];
-      after = [ "kube-apiserver.service" ];
-      path = with pkgs; [ iptables conntrack_tools ];
+      wantedBy = [ "kube-node-online.target" ];
+      after = [ "kubelet-online.service" ];
+      before = [ "kube-node-online.target" ];
+      environment.KUBECONFIG = top.lib.mkKubeConfig "kube-proxy" cfg.kubeconfig;
+      path = with pkgs; [ iptables conntrack_tools kubectl ];
+      preStart = ''
+        until kubectl auth can-i get nodes/${top.kubelet.hostname} -q 2>/dev/null; do
+          echo kubectl auth can-i get nodes/${top.kubelet.hostname}: exit status $?
+          sleep 2
+        done
+      '';
       serviceConfig = {
         Slice = "kubernetes.slice";
         ExecStart = ''${top.package}/bin/kube-proxy \
@@ -59,7 +75,7 @@ in
             "--cluster-cidr=${top.clusterCidr}"} \
           ${optionalString (cfg.featureGates != [])
             "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.featureGates}"} \
-          --kubeconfig=${top.lib.mkKubeConfig "kube-proxy" cfg.kubeconfig} \
+          --kubeconfig=${environment.KUBECONFIG} \
           ${optionalString (cfg.verbosity != null) "--v=${toString cfg.verbosity}"} \
           ${cfg.extraOpts}
         '';
@@ -67,6 +83,15 @@ in
         Restart = "on-failure";
         RestartSec = 5;
       };
+      unitConfig.ConditionPathExists = proxyPaths;
+    };
+
+    systemd.paths.kube-proxy = {
+      wantedBy = [ "kube-proxy.service" ];
+      pathConfig = {
+        PathExists = proxyPaths;
+        PathChanged = proxyPaths;
+      };
     };
 
     services.kubernetes.pki.certs = {
diff --git a/nixos/modules/services/cluster/kubernetes/scheduler.nix b/nixos/modules/services/cluster/kubernetes/scheduler.nix
index 0305b9aefe59..a0e484542951 100644
--- a/nixos/modules/services/cluster/kubernetes/scheduler.nix
+++ b/nixos/modules/services/cluster/kubernetes/scheduler.nix
@@ -16,7 +16,7 @@ in
       type = str;
     };
 
-    enable = mkEnableOption "Whether to enable Kubernetes scheduler.";
+    enable = mkEnableOption "Kubernetes scheduler";
 
     extraOpts = mkOption {
       description = "Kubernetes scheduler extra command line options.";
@@ -56,18 +56,35 @@ in
   };
 
   ###### implementation
-  config = mkIf cfg.enable {
-    systemd.services.kube-scheduler = {
+  config =  let
+
+    schedulerPaths = filter (a: a != null) [
+      cfg.kubeconfig.caFile
+      cfg.kubeconfig.certFile
+      cfg.kubeconfig.keyFile
+    ];
+
+  in mkIf cfg.enable {
+    systemd.services.kube-scheduler = rec {
       description = "Kubernetes Scheduler Service";
-      wantedBy = [ "kubernetes.target" ];
+      wantedBy = [ "kube-control-plane-online.target" ];
       after = [ "kube-apiserver.service" ];
+      before = [ "kube-control-plane-online.target" ];
+      environment.KUBECONFIG = top.lib.mkKubeConfig "kube-scheduler" cfg.kubeconfig;
+      path = [ pkgs.kubectl ];
+      preStart = ''
+        until kubectl auth can-i get /api -q 2>/dev/null; do
+          echo kubectl auth can-i get /api: exit status $?
+          sleep 2
+        done
+      '';
       serviceConfig = {
         Slice = "kubernetes.slice";
         ExecStart = ''${top.package}/bin/kube-scheduler \
           --address=${cfg.address} \
           ${optionalString (cfg.featureGates != [])
             "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.featureGates}"} \
-          --kubeconfig=${top.lib.mkKubeConfig "kube-scheduler" cfg.kubeconfig} \
+          --kubeconfig=${environment.KUBECONFIG} \
           --leader-elect=${boolToString cfg.leaderElect} \
           --port=${toString cfg.port} \
           ${optionalString (cfg.verbosity != null) "--v=${toString cfg.verbosity}"} \
@@ -79,6 +96,15 @@ in
         Restart = "on-failure";
         RestartSec = 5;
       };
+      unitConfig.ConditionPathExists = schedulerPaths;
+    };
+
+    systemd.paths.kube-scheduler = {
+      wantedBy = [ "kube-scheduler.service" ];
+      pathConfig = {
+        PathExists = schedulerPaths;
+        PathChanged = schedulerPaths;
+      };
     };
 
     services.kubernetes.pki.certs = {
diff --git a/nixos/modules/services/continuous-integration/buildkite-agent.nix b/nixos/modules/services/continuous-integration/buildkite-agent.nix
index 2136778aff47..12cc3d2b1ccc 100644
--- a/nixos/modules/services/continuous-integration/buildkite-agent.nix
+++ b/nixos/modules/services/continuous-integration/buildkite-agent.nix
@@ -236,7 +236,7 @@ in
       };
 
     assertions = [
-      { assertion = cfg.hooksPath == hooksDir || all isNull (attrValues cfg.hooks);
+      { assertion = cfg.hooksPath == hooksDir || all (v: v == null) (attrValues cfg.hooks);
         message = ''
           Options `services.buildkite-agent.hooksPath' and
           `services.buildkite-agent.hooks.<name>' are mutually exclusive.
diff --git a/nixos/modules/services/continuous-integration/jenkins/default.nix b/nixos/modules/services/continuous-integration/jenkins/default.nix
index 1eca45fbd570..ec6a36413fe7 100644
--- a/nixos/modules/services/continuous-integration/jenkins/default.nix
+++ b/nixos/modules/services/continuous-integration/jenkins/default.nix
@@ -189,7 +189,7 @@ in {
 
       preStart =
         let replacePlugins =
-              if isNull cfg.plugins
+              if cfg.plugins == null
               then ""
               else
                 let pluginCmds = lib.attrsets.mapAttrsToList
diff --git a/nixos/modules/services/databases/cassandra.nix b/nixos/modules/services/databases/cassandra.nix
index d741ee48c48f..688938868020 100644
--- a/nixos/modules/services/databases/cassandra.nix
+++ b/nixos/modules/services/databases/cassandra.nix
@@ -22,11 +22,11 @@ let
              else {})
     );
   cassandraConfigWithAddresses = cassandraConfig //
-    ( if isNull cfg.listenAddress
+    ( if cfg.listenAddress == null
         then { listen_interface = cfg.listenInterface; }
         else { listen_address = cfg.listenAddress; }
     ) // (
-      if isNull cfg.rpcAddress
+      if cfg.rpcAddress == null
         then { rpc_interface = cfg.rpcInterface; }
         else { rpc_address = cfg.rpcAddress; }
     );
@@ -219,19 +219,13 @@ in {
   config = mkIf cfg.enable {
     assertions =
       [ { assertion =
-            ((isNull cfg.listenAddress)
-             || (isNull cfg.listenInterface)
-            ) && !((isNull cfg.listenAddress)
-                   && (isNull cfg.listenInterface)
-                  );
+          (cfg.listenAddress == null || cfg.listenInterface == null)
+          && !(cfg.listenAddress == null && cfg.listenInterface == null);
           message = "You have to set either listenAddress or listenInterface";
         }
         { assertion =
-            ((isNull cfg.rpcAddress)
-             || (isNull cfg.rpcInterface)
-            ) && !((isNull cfg.rpcAddress)
-                   && (isNull cfg.rpcInterface)
-                  );
+          (cfg.rpcAddress == null || cfg.rpcInterface == null)
+          && !(cfg.rpcAddress == null && cfg.rpcInterface == null);
           message = "You have to set either rpcAddress or rpcInterface";
         }
       ];
@@ -276,7 +270,7 @@ in {
           };
       };
     systemd.timers.cassandra-full-repair =
-      mkIf (!isNull cfg.fullRepairInterval) {
+      mkIf (cfg.fullRepairInterval != null) {
         description = "Schedule full repairs on Cassandra";
         wantedBy = [ "timers.target" ];
         timerConfig =
@@ -300,7 +294,7 @@ in {
           };
       };
     systemd.timers.cassandra-incremental-repair =
-      mkIf (!isNull cfg.incrementalRepairInterval) {
+      mkIf (cfg.incrementalRepairInterval != null) {
         description = "Schedule incremental repairs on Cassandra";
         wantedBy = [ "timers.target" ];
         timerConfig =
diff --git a/nixos/modules/services/databases/clickhouse.nix b/nixos/modules/services/databases/clickhouse.nix
index 21e0cee34151..dbabcae43ee5 100644
--- a/nixos/modules/services/databases/clickhouse.nix
+++ b/nixos/modules/services/databases/clickhouse.nix
@@ -1,8 +1,6 @@
 { config, lib, pkgs, ... }:
 let
   cfg = config.services.clickhouse;
-  confDir = "/etc/clickhouse-server";
-  stateDir = "/var/lib/clickhouse";
 in
 with lib;
 {
@@ -43,20 +41,13 @@ with lib;
 
       after = [ "network.target" ];
 
-      preStart = ''
-        mkdir -p ${stateDir}
-        chown clickhouse:clickhouse ${confDir} ${stateDir}
-      '';
-
-      script = ''
-        cd "${confDir}"
-        exec ${pkgs.clickhouse}/bin/clickhouse-server
-      '';
-
       serviceConfig = {
         User = "clickhouse";
         Group = "clickhouse";
-        PermissionsStartOnly = true;
+        ConfigurationDirectory = "clickhouse-server";
+        StateDirectory = "clickhouse";
+        LogsDirectory = "clickhouse";
+        ExecStart = "${pkgs.clickhouse}/bin/clickhouse-server --config-file=${pkgs.clickhouse}/etc/clickhouse-server/config.xml";
       };
     };
 
diff --git a/nixos/modules/services/databases/cockroachdb.nix b/nixos/modules/services/databases/cockroachdb.nix
index e977751b21ef..268fdcc819fd 100644
--- a/nixos/modules/services/databases/cockroachdb.nix
+++ b/nixos/modules/services/databases/cockroachdb.nix
@@ -7,7 +7,7 @@ let
   crdb = cfg.package;
 
   escape    = builtins.replaceStrings ["%"] ["%%"];
-  ifNotNull = v: s: optionalString (!isNull v) s;
+  ifNotNull = v: s: optionalString (v != null) s;
 
   startupCommand = lib.concatStringsSep " "
     [ # Basic startup
@@ -164,7 +164,7 @@ in
 
   config = mkIf config.services.cockroachdb.enable {
     assertions = [
-      { assertion = !cfg.insecure -> !(isNull cfg.certsDir);
+      { assertion = !cfg.insecure -> cfg.certsDir != null;
         message = "CockroachDB must have a set of SSL certificates (.certsDir), or run in Insecure Mode (.insecure = true)";
       }
     ];
diff --git a/nixos/modules/services/databases/couchdb.nix b/nixos/modules/services/databases/couchdb.nix
index 84d108d9c747..5ddf8ba4bfbd 100644
--- a/nixos/modules/services/databases/couchdb.nix
+++ b/nixos/modules/services/databases/couchdb.nix
@@ -158,27 +158,21 @@ in {
     services.couchdb.configFile = mkDefault
       (if useVersion2 then "/var/lib/couchdb/local.ini" else "/var/lib/couchdb/couchdb.ini");
 
+    systemd.tmpfiles.rules = [
+      "d '${dirOf cfg.uriFile}' - ${cfg.user} ${cfg.group} - -"
+      "d '${dirOf cfg.logFile}' - ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.databaseDir}' -  ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.viewIndexDir}' -  ${cfg.user} ${cfg.group} - -"
+    ];
+
     systemd.services.couchdb = {
       description = "CouchDB Server";
       wantedBy = [ "multi-user.target" ];
 
       preStart =
         ''
-        mkdir -p `dirname ${cfg.uriFile}`;
-        mkdir -p `dirname ${cfg.logFile}`;
-        mkdir -p ${cfg.databaseDir};
-        mkdir -p ${cfg.viewIndexDir};
         touch ${cfg.configFile}
         touch -a ${cfg.logFile}
-
-        if [ "$(id -u)" = 0 ]; then
-          chown ${cfg.user}:${cfg.group} `dirname ${cfg.uriFile}`;
-          (test -f ${cfg.uriFile} && chown ${cfg.user}:${cfg.group} ${cfg.uriFile}) || true
-          chown ${cfg.user}:${cfg.group} ${cfg.databaseDir}
-          chown ${cfg.user}:${cfg.group} ${cfg.viewIndexDir}
-          chown ${cfg.user}:${cfg.group} ${cfg.configFile}
-          chown ${cfg.user}:${cfg.group} ${cfg.logFile}
-        fi
         '';
 
       environment = mkIf useVersion2 {
@@ -191,7 +185,6 @@ in {
       };
 
       serviceConfig = {
-        PermissionsStartOnly = true;
         User = cfg.user;
         Group = cfg.group;
         ExecStart = executable;
diff --git a/nixos/modules/services/databases/foundationdb.nix b/nixos/modules/services/databases/foundationdb.nix
index ad24f9f4b0fd..490c5e9d005a 100644
--- a/nixos/modules/services/databases/foundationdb.nix
+++ b/nixos/modules/services/databases/foundationdb.nix
@@ -36,6 +36,10 @@ let
     memory         = ${cfg.memory}
     storage_memory = ${cfg.storageMemory}
 
+    ${optionalString (lib.versionAtLeast cfg.package.version "6.1") ''
+    trace_format   = ${cfg.traceFormat}
+    ''}
+
     ${optionalString (cfg.tls != null) ''
       tls_plugin           = ${pkg}/libexec/plugins/FDBLibTLS.so
       tls_certificate_file = ${cfg.tls.certificate}
@@ -317,9 +321,24 @@ in
       default     = "/run/foundationdb.pid";
       description = "Path to pidfile for fdbmonitor.";
     };
+
+    traceFormat = mkOption {
+      type = types.enum [ "xml" "json" ];
+      default = "xml";
+      description = "Trace logging format.";
+    };
   };
 
   config = mkIf cfg.enable {
+    assertions = [
+      { assertion = lib.versionOlder cfg.package.version "6.1" -> cfg.traceFormat == "xml";
+        message = ''
+          Versions of FoundationDB before 6.1 do not support configurable trace formats (only XML is supported).
+          This option has no effect for version '' + cfg.package.version + '', and enabling it is an error.
+        '';
+      }
+    ];
+
     environment.systemPackages = [ pkg ];
 
     users.users = optionalAttrs (cfg.user == "foundationdb") (singleton
@@ -382,7 +401,7 @@ in
           chown -R ${cfg.user}:${cfg.group} ${cfg.pidfile}
 
         for x in "${cfg.logDir}" "${cfg.dataDir}"; do
-          [ ! -d "$x" ] && mkdir -m 0700 -vp "$x";
+          [ ! -d "$x" ] && mkdir -m 0770 -vp "$x";
           chown -R ${cfg.user}:${cfg.group} "$x";
         done
 
@@ -404,7 +423,7 @@ in
 
       postStart = ''
         if [ -e "${cfg.dataDir}/.first_startup" ]; then
-          fdbcli --exec "configure new single memory"
+          fdbcli --exec "configure new single ssd"
           rm -f "${cfg.dataDir}/.first_startup";
         fi
       '';
diff --git a/nixos/modules/services/databases/influxdb.nix b/nixos/modules/services/databases/influxdb.nix
index 888bf13c3df8..6868050c8446 100644
--- a/nixos/modules/services/databases/influxdb.nix
+++ b/nixos/modules/services/databases/influxdb.nix
@@ -157,20 +157,19 @@ in
 
   config = mkIf config.services.influxdb.enable {
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' 0770 ${cfg.user} ${cfg.group} - -"
+    ];
+
     systemd.services.influxdb = {
       description = "InfluxDB Server";
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
       serviceConfig = {
         ExecStart = ''${cfg.package}/bin/influxd -config "${configFile}"'';
-        User = "${cfg.user}";
-        Group = "${cfg.group}";
-        PermissionsStartOnly = true;
+        User = cfg.user;
+        Group = cfg.group;
       };
-      preStart = ''
-        mkdir -m 0770 -p ${cfg.dataDir}
-        if [ "$(id -u)" = 0 ]; then chown -R ${cfg.user}:${cfg.group} ${cfg.dataDir}; fi
-      '';
       postStart =
         let
           scheme = if configOptions.http.https-enabled then "-k https" else "http";
diff --git a/nixos/modules/services/databases/memcached.nix b/nixos/modules/services/databases/memcached.nix
index 7af452e4dced..052ff1f308eb 100644
--- a/nixos/modules/services/databases/memcached.nix
+++ b/nixos/modules/services/databases/memcached.nix
@@ -78,11 +78,6 @@ in
       after = [ "network.target" ];
 
       serviceConfig = {
-        PermissionsStartOnly = true;
-        ExecStartPre = optionals cfg.enableUnixSocket [
-          "${pkgs.coreutils}/bin/install -d -o ${cfg.user} /run/memcached/"
-          "${pkgs.coreutils}/bin/chown -R ${cfg.user} /run/memcached/"
-        ];
         ExecStart =
         let
           networking = if cfg.enableUnixSocket
@@ -91,12 +86,13 @@ in
         in "${memcached}/bin/memcached ${networking} -m ${toString cfg.maxMemory} -c ${toString cfg.maxConnections} ${concatStringsSep " " cfg.extraOptions}";
 
         User = cfg.user;
+        RuntimeDirectory = "memcached";
       };
     };
   };
   imports = [
     (mkRemovedOptionModule ["services" "memcached" "socket"] ''
-      This option was replaced by a fixed unix socket path at /run/memcached/memcached.sock enabled using services.memached.enableUnixSocket.
+      This option was replaced by a fixed unix socket path at /run/memcached/memcached.sock enabled using services.memcached.enableUnixSocket.
     '')
   ];
 
diff --git a/nixos/modules/services/databases/mongodb.nix b/nixos/modules/services/databases/mongodb.nix
index 3fe4af2f2619..c458a1d648a0 100644
--- a/nixos/modules/services/databases/mongodb.nix
+++ b/nixos/modules/services/databases/mongodb.nix
@@ -8,12 +8,13 @@ let
 
   mongodb = cfg.package;
 
-  mongoCnf = pkgs.writeText "mongodb.conf"
+  mongoCnf = cfg: pkgs.writeText "mongodb.conf"
   ''
     net.bindIp: ${cfg.bind_ip}
     ${optionalString cfg.quiet "systemLog.quiet: true"}
     systemLog.destination: syslog
     storage.dbPath: ${cfg.dbpath}
+    ${optionalString cfg.enableAuth "security.authorization: enabled"}
     ${optionalString (cfg.replSetName != "") "replication.replSetName: ${cfg.replSetName}"}
     ${cfg.extraConfig}
   '';
@@ -59,6 +60,18 @@ in
         description = "quieter output";
       };
 
+      enableAuth = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Enable client authentication. Creates a default superuser with username root!";
+      };
+      
+      initialRootPassword = mkOption {
+        type = types.nullOr types.string;
+        default = null;
+        description = "Password for the root user if auth is enabled.";
+      };
+
       dbpath = mkOption {
         default = "/var/db/mongodb";
         description = "Location where MongoDB stores its files";
@@ -84,6 +97,14 @@ in
         '';
         description = "MongoDB extra configuration in YAML format";
       };
+
+      initialScript = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          A file containing MongoDB statements to execute on first startup.
+        '';
+      };
     };
 
   };
@@ -92,6 +113,11 @@ in
   ###### implementation
 
   config = mkIf config.services.mongodb.enable {
+    assertions = [
+      { assertion = !cfg.enableAuth || cfg.initialRootPassword != null;
+        message = "`enableAuth` requires `initialRootPassword` to be set.";
+      }
+    ];
 
     users.users.mongodb = mkIf (cfg.user == "mongodb")
       { name = "mongodb";
@@ -108,7 +134,7 @@ in
         after = [ "network.target" ];
 
         serviceConfig = {
-          ExecStart = "${mongodb}/bin/mongod --config ${mongoCnf} --fork --pidfilepath ${cfg.pidFile}";
+          ExecStart = "${mongodb}/bin/mongod --config ${mongoCnf cfg} --fork --pidfilepath ${cfg.pidFile}";
           User = cfg.user;
           PIDFile = cfg.pidFile;
           Type = "forking";
@@ -116,15 +142,50 @@ in
           PermissionsStartOnly = true;
         };
 
-        preStart = ''
+        preStart = let
+          cfg_ = cfg // { enableAuth = false; bind_ip = "127.0.0.1"; };
+        in ''
           rm ${cfg.dbpath}/mongod.lock || true
           if ! test -e ${cfg.dbpath}; then
               install -d -m0700 -o ${cfg.user} ${cfg.dbpath}
+              # See postStart!
+              touch ${cfg.dbpath}/.first_startup
           fi
           if ! test -e ${cfg.pidFile}; then
               install -D -o ${cfg.user} /dev/null ${cfg.pidFile}
+          fi '' + lib.optionalString cfg.enableAuth ''
+
+          if ! test -e "${cfg.dbpath}/.auth_setup_complete"; then
+            systemd-run --unit=mongodb-for-setup --uid=${cfg.user} ${mongodb}/bin/mongod --config ${mongoCnf cfg_}
+            # wait for mongodb
+            while ! ${mongodb}/bin/mongo --eval "db.version()" > /dev/null 2>&1; do sleep 0.1; done
+
+          ${mongodb}/bin/mongo <<EOF
+            use admin
+            db.createUser(
+              {
+                user: "root",
+                pwd: "${cfg.initialRootPassword}",
+                roles: [
+                  { role: "userAdminAnyDatabase", db: "admin" },
+                  { role: "dbAdminAnyDatabase", db: "admin" },
+                  { role: "readWriteAnyDatabase", db: "admin" }
+                ]
+              }
+            )
+          EOF
+            touch "${cfg.dbpath}/.auth_setup_complete"
+            systemctl stop mongodb-for-setup
           fi
         '';
+        postStart = ''
+            if test -e "${cfg.dbpath}/.first_startup"; then
+              ${optionalString (cfg.initialScript != null) ''
+                ${mongodb}/bin/mongo -u root -p ${cfg.initialRootPassword} admin "${cfg.initialScript}"
+              ''}
+              rm -f "${cfg.dbpath}/.first_startup"
+            fi
+        '';
       };
 
   };
diff --git a/nixos/modules/services/databases/mysql.nix b/nixos/modules/services/databases/mysql.nix
index 89291d4438ff..66d55b650a45 100644
--- a/nixos/modules/services/databases/mysql.nix
+++ b/nixos/modules/services/databases/mysql.nix
@@ -18,16 +18,12 @@ let
     in (pName mysql == pName pkgs.mysql57)
        && ((builtins.compareVersions mysql.version "5.7") >= 0);
 
-  pidFile = "${cfg.pidDir}/mysqld.pid";
-
-  mysqldAndInstallOptions =
-    "--user=${cfg.user} --datadir=${cfg.dataDir} --basedir=${mysql}";
   mysqldOptions =
-    "${mysqldAndInstallOptions} --pid-file=${pidFile}";
+    "--user=${cfg.user} --datadir=${cfg.dataDir} --basedir=${mysql}";
   # For MySQL 5.7+, --insecure creates the root user without password
   # (earlier versions and MariaDB do this by default).
   installOptions =
-    "${mysqldAndInstallOptions} ${lib.optionalString isMysqlAtLeast57 "--insecure"}";
+    "${mysqldOptions} ${lib.optionalString isMysqlAtLeast57 "--insecure"}";
 
 in
 
@@ -80,11 +76,6 @@ in
         description = "Location where MySQL stores its table files";
       };
 
-      pidDir = mkOption {
-        default = "/run/mysqld";
-        description = "Location of the file which stores the PID of the MySQL server";
-      };
-
       extraOptions = mkOption {
         type = types.lines;
         default = "";
@@ -133,7 +124,7 @@ in
       };
 
       initialScript = mkOption {
-        type = types.nullOr types.lines;
+        type = types.nullOr types.path;
         default = null;
         description = "A file containing SQL statements to be executed on the first startup. Can be used for granting certain permissions on the database";
       };
@@ -296,6 +287,10 @@ in
       ${cfg.extraOptions}
     '';
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' 0700 ${cfg.user} mysql -"
+    ];
+
     systemd.services.mysql = let
       hasNotify = (cfg.package == pkgs.mariadb);
     in {
@@ -313,68 +308,69 @@ in
           pkgs.nettools
         ];
 
-        preStart =
-          ''
-            if ! test -e ${cfg.dataDir}/mysql; then
-                mkdir -m 0700 -p ${cfg.dataDir}
-                chown -R ${cfg.user} ${cfg.dataDir}
-                ${mysql}/bin/mysql_install_db --defaults-file=/etc/my.cnf ${installOptions}
-                touch /tmp/mysql_init
-            fi
-
-            mkdir -m 0755 -p ${cfg.pidDir}
-            chown -R ${cfg.user} ${cfg.pidDir}
-          '';
+        preStart = ''
+          if ! test -e ${cfg.dataDir}/mysql; then
+            ${mysql}/bin/mysql_install_db --defaults-file=/etc/my.cnf ${installOptions}
+            touch /tmp/mysql_init
+          fi
+        '';
 
         serviceConfig = {
+          User = cfg.user;
+          Group = "mysql";
           Type = if hasNotify then "notify" else "simple";
           RuntimeDirectory = "mysqld";
+          RuntimeDirectoryMode = "0755";
           # The last two environment variables are used for starting Galera clusters
           ExecStart = "${mysql}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} $_WSREP_NEW_CLUSTER $_WSREP_START_POSITION";
         };
 
-        postStart = ''
-          ${lib.optionalString (!hasNotify) ''
-            # Wait until the MySQL server is available for use
-            count=0
-            while [ ! -e /run/mysqld/mysqld.sock ]
-            do
-                if [ $count -eq 30 ]
-                then
-                    echo "Tried 30 times, giving up..."
-                    exit 1
-                fi
-
-                echo "MySQL daemon not yet started. Waiting for 1 second..."
-                count=$((count++))
-                sleep 1
-            done
-          ''}
+        postStart =
+          let
+            cmdWatchForMysqlSocket = ''
+              # Wait until the MySQL server is available for use
+              count=0
+              while [ ! -e /run/mysqld/mysqld.sock ]
+              do
+                  if [ $count -eq 30 ]
+                  then
+                      echo "Tried 30 times, giving up..."
+                      exit 1
+                  fi
+
+                  echo "MySQL daemon not yet started. Waiting for 1 second..."
+                  count=$((count++))
+                  sleep 1
+              done
+            '';
+            cmdInitialDatabases = concatMapStrings (database: ''
+              # Create initial databases
+              if ! test -e "${cfg.dataDir}/${database.name}"; then
+                  echo "Creating initial database: ${database.name}"
+                  ( echo 'create database `${database.name}`;'
+
+                    ${optionalString (database.schema != null) ''
+                    echo 'use `${database.name}`;'
+
+                    # TODO: this silently falls through if database.schema does not exist,
+                    # we should catch this somehow and exit, but can't do it here because we're in a subshell.
+                    if [ -f "${database.schema}" ]
+                    then
+                        cat ${database.schema}
+                    elif [ -d "${database.schema}" ]
+                    then
+                        cat ${database.schema}/mysql-databases/*.sql
+                    fi
+                    ''}
+                  ) | ${mysql}/bin/mysql -u root -N
+              fi
+            '') cfg.initialDatabases;
+          in
 
+          lib.optionalString (!hasNotify) cmdWatchForMysqlSocket + ''
             if [ -f /tmp/mysql_init ]
             then
-                ${concatMapStrings (database:
-                  ''
-                    # Create initial databases
-                    if ! test -e "${cfg.dataDir}/${database.name}"; then
-                        echo "Creating initial database: ${database.name}"
-                        ( echo 'create database `${database.name}`;'
-
-                          ${optionalString (database ? "schema") ''
-                          echo 'use `${database.name}`;'
-
-                          if [ -f "${database.schema}" ]
-                          then
-                              cat ${database.schema}
-                          elif [ -d "${database.schema}" ]
-                          then
-                              cat ${database.schema}/mysql-databases/*.sql
-                          fi
-                          ''}
-                        ) | ${mysql}/bin/mysql -u root -N
-                    fi
-                  '') cfg.initialDatabases}
-
+                ${cmdInitialDatabases}
                 ${optionalString (cfg.replication.role == "master")
                   ''
                     # Set up the replication master
@@ -399,7 +395,9 @@ in
                 ${optionalString (cfg.initialScript != null)
                   ''
                     # Execute initial script
-                    cat ${cfg.initialScript} | ${mysql}/bin/mysql -u root -N
+                    # using toString to avoid copying the file to nix store if given as path instead of string,
+                    # as it might contain credentials
+                    cat ${toString cfg.initialScript} | ${mysql}/bin/mysql -u root -N
                   ''}
 
                 ${optionalString (cfg.rootPassword != null)
diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix
index c101e7375af9..c2f458c03794 100644
--- a/nixos/modules/services/databases/openldap.nix
+++ b/nixos/modules/services/databases/openldap.nix
@@ -18,7 +18,11 @@ let
     database ${cfg.database}
     suffix ${cfg.suffix}
     rootdn ${cfg.rootdn}
-    rootpw ${cfg.rootpw}
+    ${if (cfg.rootpw != null) then ''
+      rootpw ${cfg.rootpw}
+    '' else ''
+      include ${cfg.rootpwFile}
+    ''}
     directory ${cfg.dataDir}
     ${cfg.extraDatabaseConfig}
   '');
@@ -106,10 +110,23 @@ in
       };
 
       rootpw = mkOption {
-        type = types.str;
+        type = types.nullOr types.str;
+        default = null;
         description = ''
           Password for the root user.
           This setting will be ignored if configDir is set.
+          Using this option will store the root password in plain text in the
+          world-readable nix store. To avoid this the <literal>rootpwFile</literal> can be used.
+        '';
+      };
+
+      rootpwFile = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Password file for the root user.
+          The file should contain the string <literal>rootpw</literal> followed by the password.
+          e.g.: <literal>rootpw mysecurepassword</literal>
         '';
       };
 
@@ -140,9 +157,9 @@ in
             include ${pkgs.openldap.out}/etc/schema/inetorgperson.schema
             include ${pkgs.openldap.out}/etc/schema/nis.schema
 
-            database bdb 
-            suffix dc=example,dc=org 
-            rootdn cn=admin,dc=example,dc=org 
+            database bdb
+            suffix dc=example,dc=org
+            rootdn cn=admin,dc=example,dc=org
             # NOTE: change after first start
             rootpw secret
             directory /var/db/openldap
@@ -218,6 +235,12 @@ in
   ###### implementation
 
   config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = cfg.rootpwFile != null || cfg.rootpw != null;
+        message = "Either services.openldap.rootpw or services.openldap.rootpwFile must be set";
+      }
+    ];
 
     environment.systemPackages = [ openldap ];
 
diff --git a/nixos/modules/services/databases/pgmanage.nix b/nixos/modules/services/databases/pgmanage.nix
index 1a34c7f5ecee..1050c2dd481a 100644
--- a/nixos/modules/services/databases/pgmanage.nix
+++ b/nixos/modules/services/databases/pgmanage.nix
@@ -16,7 +16,7 @@ let
 
       super_only = ${builtins.toJSON cfg.superOnly}
 
-      ${optionalString (!isNull cfg.loginGroup) "login_group = ${cfg.loginGroup}"}
+      ${optionalString (cfg.loginGroup != null) "login_group = ${cfg.loginGroup}"}
 
       login_timeout = ${toString cfg.loginTimeout}
 
@@ -24,7 +24,7 @@ let
 
       sql_root = ${cfg.sqlRoot}
 
-      ${optionalString (!isNull cfg.tls) ''
+      ${optionalString (cfg.tls != null) ''
       tls_cert = ${cfg.tls.cert}
       tls_key = ${cfg.tls.key}
       ''}
diff --git a/nixos/modules/services/databases/postgresql.nix b/nixos/modules/services/databases/postgresql.nix
index 87b236dd5fd1..5661edbee2db 100644
--- a/nixos/modules/services/databases/postgresql.nix
+++ b/nixos/modules/services/databases/postgresql.nix
@@ -105,6 +105,80 @@ in
         '';
       };
 
+      ensureDatabases = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          Ensures that the specified databases exist.
+          This option will never delete existing databases, especially not when the value of this
+          option is changed. This means that databases created once through this option or
+          otherwise have to be removed manually.
+        '';
+        example = [
+          "gitea"
+          "nextcloud"
+        ];
+      };
+
+      ensureUsers = mkOption {
+        type = types.listOf (types.submodule {
+          options = {
+            name = mkOption {
+              type = types.str;
+              description = ''
+                Name of the user to ensure.
+              '';
+            };
+            ensurePermissions = mkOption {
+              type = types.attrsOf types.str;
+              default = {};
+              description = ''
+                Permissions to ensure for the user, specified as an attribute set.
+                The attribute names specify the database and tables to grant the permissions for.
+                The attribute values specify the permissions to grant. You may specify one or
+                multiple comma-separated SQL privileges here.
+
+                For more information on how to specify the target
+                and on which privileges exist, see the
+                <link xlink:href="https://www.postgresql.org/docs/current/sql-grant.html">GRANT syntax</link>.
+                The attributes are used as <code>GRANT ''${attrName} ON ''${attrValue}</code>.
+              '';
+              example = literalExample ''
+                {
+                  "DATABASE nextcloud" = "ALL PRIVILEGES";
+                  "ALL TABLES IN SCHEMA public" = "ALL PRIVILEGES";
+                }
+              '';
+            };
+          };
+        });
+        default = [];
+        description = ''
+          Ensures that the specified users exist and have at least the ensured permissions.
+          The PostgreSQL users will be identified using peer authentication. This authenticates the Unix user with the
+          same name only, and that without the need for a password.
+          This option will never delete existing users or remove permissions, especially not when the value of this
+          option is changed. This means that users created and permissions assigned once through this option or
+          otherwise have to be removed manually.
+        '';
+        example = literalExample ''
+          [
+            {
+              name = "nextcloud";
+              ensurePermissions = {
+                "DATABASE nextcloud" = "ALL PRIVILEGES";
+              };
+            }
+            {
+              name = "superuser";
+              ensurePermissions = {
+                "ALL TABLES IN SCHEMA public" = "ALL PRIVILEGES";
+              };
+            }
+          ]
+        '';
+      };
+
       enableTCPIP = mkOption {
         type = types.bool;
         default = false;
@@ -256,17 +330,30 @@ in
         # Wait for PostgreSQL to be ready to accept connections.
         postStart =
           ''
-            while ! ${pkgs.sudo}/bin/sudo -u ${cfg.superUser} psql --port=${toString cfg.port} -d postgres -c "" 2> /dev/null; do
+            PSQL="${pkgs.sudo}/bin/sudo -u ${cfg.superUser} psql --port=${toString cfg.port}"
+
+            while ! $PSQL -d postgres -c "" 2> /dev/null; do
                 if ! kill -0 "$MAINPID"; then exit 1; fi
                 sleep 0.1
             done
 
             if test -e "${cfg.dataDir}/.first_startup"; then
               ${optionalString (cfg.initialScript != null) ''
-                ${pkgs.sudo}/bin/sudo -u ${cfg.superUser} psql -f "${cfg.initialScript}" --port=${toString cfg.port} -d postgres
+                $PSQL -f "${cfg.initialScript}" -d postgres
               ''}
               rm -f "${cfg.dataDir}/.first_startup"
             fi
+          '' + optionalString (cfg.ensureDatabases != []) ''
+            ${concatMapStrings (database: ''
+              $PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = '${database}'" | grep -q 1 || $PSQL -tAc "CREATE DATABASE ${database}"
+            '') cfg.ensureDatabases}
+          '' + ''
+            ${concatMapStrings (user: ''
+              $PSQL -tAc "SELECT 1 FROM pg_roles WHERE rolname='${user.name}'" | grep -q 1 || $PSQL -tAc "CREATE USER ${user.name}"
+              ${concatStringsSep "\n" (mapAttrsToList (database: permission: ''
+                $PSQL -tAc "GRANT ${permission} ON ${database} TO ${user.name}"
+              '') user.ensurePermissions)}
+            '') cfg.ensureUsers}
           '';
 
         unitConfig.RequiresMountsFor = "${cfg.dataDir}";
diff --git a/nixos/modules/services/databases/stanchion.nix b/nixos/modules/services/databases/stanchion.nix
index 9fe49f51edd2..97e55bc70c47 100644
--- a/nixos/modules/services/databases/stanchion.nix
+++ b/nixos/modules/services/databases/stanchion.nix
@@ -98,7 +98,7 @@ in
         type = types.path;
         default = "/var/log/stanchion";
         description = ''
-          Log directory for Stanchino.
+          Log directory for Stanchion.
         '';
       };
 
@@ -152,6 +152,11 @@ in
 
     users.groups.stanchion.gid = config.ids.gids.stanchion;
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.logDir}' - stanchion stanchion --"
+      "d '${cfg.dataDir}' 0700 stanchion stanchion --"
+    ];
+
     systemd.services.stanchion = {
       description = "Stanchion Server";
 
@@ -168,25 +173,12 @@ in
       environment.STANCHION_LOG_DIR = "${cfg.logDir}";
       environment.STANCHION_ETC_DIR = "/etc/stanchion";
 
-      preStart = ''
-        if ! test -e ${cfg.logDir}; then
-          mkdir -m 0755 -p ${cfg.logDir}
-          chown -R stanchion:stanchion ${cfg.logDir}
-        fi
-
-        if ! test -e ${cfg.dataDir}; then
-          mkdir -m 0700 -p ${cfg.dataDir}
-          chown -R stanchion:stanchion ${cfg.dataDir}
-        fi
-      '';
-
       serviceConfig = {
         ExecStart = "${cfg.package}/bin/stanchion console";
         ExecStop = "${cfg.package}/bin/stanchion stop";
         StandardInput = "tty";
         User = "stanchion";
         Group = "stanchion";
-        PermissionsStartOnly = true;
         # Give Stanchion a decent amount of time to clean up.
         TimeoutStopSec = 120;
         LimitNOFILE = 65536;
diff --git a/nixos/modules/services/desktops/deepin/dde-daemon.nix b/nixos/modules/services/desktops/deepin/dde-daemon.nix
deleted file mode 100644
index 057da4e2d7f2..000000000000
--- a/nixos/modules/services/desktops/deepin/dde-daemon.nix
+++ /dev/null
@@ -1,41 +0,0 @@
-# dde-daemon
-
-{ config, pkgs, lib, ... }:
-
-{
-
-  ###### interface
-
-  options = {
-
-    services.deepin.dde-daemon = {
-
-      enable = lib.mkEnableOption
-        "A daemon for handling Deepin Desktop Environment session settings";
-
-    };
-
-  };
-
-
-  ###### implementation
-
-  config = lib.mkIf config.services.deepin.dde-daemon.enable {
-
-    environment.systemPackages = [ pkgs.deepin.dde-daemon ];
-
-    services.dbus.packages = [ pkgs.deepin.dde-daemon ];
-
-    systemd.packages = [ pkgs.deepin.dde-daemon ];
-
-    users.groups.dde-daemon = { };
-
-    users.users.dde-daemon = {
-      description = "Deepin daemon user";
-      group = "dde-daemon";
-      isSystemUser = true;
-    };
-
-  };
-
-}
diff --git a/nixos/modules/services/desktops/deepin/deepin-menu.nix b/nixos/modules/services/desktops/deepin/deepin-menu.nix
deleted file mode 100644
index 23fe5a741c42..000000000000
--- a/nixos/modules/services/desktops/deepin/deepin-menu.nix
+++ /dev/null
@@ -1,29 +0,0 @@
-# deepin-menu
-
-{ config, pkgs, lib, ... }:
-
-{
-
-  ###### interface
-
-  options = {
-
-    services.deepin.deepin-menu = {
-
-      enable = lib.mkEnableOption
-        "DBus service for unified menus in Deepin Desktop Environment";
-
-    };
-
-  };
-
-
-  ###### implementation
-
-  config = lib.mkIf config.services.deepin.deepin-menu.enable {
-
-    services.dbus.packages = [ pkgs.deepin.deepin-menu ];
-
-  };
-
-}
diff --git a/nixos/modules/services/desktops/deepin/deepin.nix b/nixos/modules/services/desktops/deepin/deepin.nix
new file mode 100644
index 000000000000..d9d5d539e422
--- /dev/null
+++ b/nixos/modules/services/desktops/deepin/deepin.nix
@@ -0,0 +1,121 @@
+# deepin
+
+{ config, pkgs, lib, ... }:
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.deepin.core.enable = lib.mkEnableOption "
+      Basic dbus and systemd services, groups and users needed by the
+      Deepin Desktop Environment.
+    ";
+
+    services.deepin.deepin-menu.enable = lib.mkEnableOption "
+      DBus service for unified menus in Deepin Desktop Environment.
+    ";
+
+    services.deepin.deepin-turbo.enable = lib.mkEnableOption "
+      Turbo service for the Deepin Desktop Environment. It is a daemon
+      that helps to launch applications faster.
+    ";
+
+  };
+
+
+  ###### implementation
+
+  config = lib.mkMerge [
+
+    (lib.mkIf config.services.deepin.core.enable {
+      environment.systemPackages = [
+        pkgs.deepin.dde-api
+        pkgs.deepin.dde-calendar
+        pkgs.deepin.dde-daemon
+        pkgs.deepin.dde-dock
+        pkgs.deepin.dde-file-manager
+        pkgs.deepin.dde-session-ui
+        pkgs.deepin.deepin-anything
+        pkgs.deepin.deepin-image-viewer
+        pkgs.deepin.deepin-screenshot
+      ];
+
+      services.dbus.packages = [
+        pkgs.deepin.dde-api
+        pkgs.deepin.dde-calendar
+        pkgs.deepin.dde-daemon
+        pkgs.deepin.dde-dock
+        pkgs.deepin.dde-file-manager
+        pkgs.deepin.dde-session-ui
+        pkgs.deepin.deepin-anything
+        pkgs.deepin.deepin-image-viewer
+        pkgs.deepin.deepin-screenshot
+      ];
+
+      systemd.packages = [
+        pkgs.deepin.dde-api
+        pkgs.deepin.dde-daemon
+        pkgs.deepin.dde-file-manager
+        pkgs.deepin.deepin-anything
+      ];
+
+      boot.extraModulePackages = [ config.boot.kernelPackages.deepin-anything ];
+
+      boot.kernelModules = [ "vfs_monitor" ];
+
+      users.groups.deepin-sound-player = { };
+
+      users.users.deepin-sound-player = {
+        description = "Deepin sound player";
+        group = "deepin-sound-player";
+        isSystemUser = true;
+      };
+
+      users.groups.deepin-daemon = { };
+
+      users.users.deepin-daemon = {
+        description = "Deepin daemon user";
+        group = "deepin-daemon";
+        isSystemUser = true;
+      };
+
+      users.groups.deepin_anything_server = { };
+
+      users.users.deepin_anything_server = {
+        description = "Deepin Anything Server";
+        group = "deepin_anything_server";
+        isSystemUser = true;
+      };
+
+      security.pam.services.deepin-auth-keyboard.text = ''
+        # original at ${pkgs.deepin.dde-daemon}/etc/pam.d/deepin-auth-keyboard
+        auth	[success=2 default=ignore]	pam_lsass.so
+        auth	[success=1 default=ignore]	pam_unix.so nullok_secure try_first_pass
+        auth	requisite	pam_deny.so
+        auth	required	pam_permit.so
+      '';
+
+      environment.etc = {
+        "polkit-1/localauthority/10-vendor.d/com.deepin.api.device.pkla".source = "${pkgs.deepin.dde-api}/etc/polkit-1/localauthority/10-vendor.d/com.deepin.api.device.pkla";
+        "polkit-1/localauthority/10-vendor.d/com.deepin.daemon.Accounts.pkla".source = "${pkgs.deepin.dde-daemon}/etc/polkit-1/localauthority/10-vendor.d/com.deepin.daemon.Accounts.pkla";
+        "polkit-1/localauthority/10-vendor.d/com.deepin.daemon.Grub2.pkla".source = "${pkgs.deepin.dde-daemon}/etc/polkit-1/localauthority/10-vendor.d/com.deepin.daemon.Grub2.pkla";
+      };
+
+      services.deepin.deepin-menu.enable = true;
+      services.deepin.deepin-turbo.enable = true;
+    })
+
+    (lib.mkIf config.services.deepin.deepin-menu.enable {
+      services.dbus.packages = [ pkgs.deepin.deepin-menu ];
+    })
+
+    (lib.mkIf config.services.deepin.deepin-turbo.enable {
+      environment.systemPackages = [ pkgs.deepin.deepin-turbo ];
+      systemd.packages = [ pkgs.deepin.deepin-turbo ];
+    })
+
+  ];
+
+}
diff --git a/nixos/modules/services/desktops/geoclue2.nix b/nixos/modules/services/desktops/geoclue2.nix
index 840aa5294ed0..040fe157d52d 100644
--- a/nixos/modules/services/desktops/geoclue2.nix
+++ b/nixos/modules/services/desktops/geoclue2.nix
@@ -7,6 +7,56 @@ with lib;
 let
   # the demo agent isn't built by default, but we need it here
   package = pkgs.geoclue2.override { withDemoAgent = config.services.geoclue2.enableDemoAgent; };
+
+  cfg = config.services.geoclue2;
+
+  defaultWhitelist = [ "gnome-shell" "io.elementary.desktop.agent-geoclue2" ];
+
+  appConfigModule = types.submodule ({ name, ... }: {
+    options = {
+      desktopID = mkOption {
+        type = types.str;
+        description = "Desktop ID of the application.";
+      };
+
+      isAllowed = mkOption {
+        type = types.bool;
+        default = null;
+        description = ''
+          Whether the application will be allowed access to location information.
+        '';
+      };
+
+      isSystem = mkOption {
+        type = types.bool;
+        default = null;
+        description = ''
+          Whether the application is a system component or not.
+        '';
+      };
+
+      users = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          List of UIDs of all users for which this application is allowed location
+          info access, Defaults to an empty string to allow it for all users.
+        '';
+      };
+    };
+
+    config.desktopID = mkDefault name;
+  });
+
+  appConfigToINICompatible = _: { desktopID, isAllowed, isSystem, users, ... }: {
+    name = desktopID;
+    value = {
+      allowed = isAllowed;
+      system = isSystem;
+      users = concatStringsSep ";" users;
+    };
+  };
+
 in
 {
 
@@ -35,23 +85,130 @@ in
         '';
       };
 
+      enableNmea = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to fetch location from NMEA sources on local network.
+        '';
+      };
+
+      enable3G = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable 3G source.
+        '';
+      };
+
+      enableCDMA = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable CDMA source.
+        '';
+      };
+
+      enableModemGPS = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable Modem-GPS source.
+        '';
+      };
+
+      enableWifi = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable WiFi source.
+        '';
+      };
+
+      geoProviderUrl = mkOption {
+        type = types.str;
+        default = "https://location.services.mozilla.com/v1/geolocate?key=geoclue";
+        example = "https://www.googleapis.com/geolocation/v1/geolocate?key=YOUR_KEY";
+        description = ''
+          The url to the wifi GeoLocation Service.
+        '';
+      };
+
+      submitData = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to submit data to a GeoLocation Service.
+        '';
+      };
+
+      submissionUrl = mkOption {
+        type = types.str;
+        default = "https://location.services.mozilla.com/v1/submit?key=geoclue";
+        description = ''
+          The url to submit data to a GeoLocation Service.
+        '';
+      };
+
+      submissionNick = mkOption {
+        type = types.str;
+        default = "geoclue";
+        description = ''
+          A nickname to submit network data with.
+          Must be 2-32 characters long.
+        '';
+      };
+
+      appConfig = mkOption {
+        type = types.loaOf appConfigModule;
+        default = {};
+        example = literalExample ''
+          "com.github.app" = {
+            isAllowed = true;
+            isSystem = true;
+            users = [ "300" ];
+          };
+        '';
+        description = ''
+          Specify extra settings per application.
+        '';
+      };
+
     };
 
   };
 
 
   ###### implementation
-  config = mkIf config.services.geoclue2.enable {
+  config = mkIf cfg.enable {
 
     environment.systemPackages = [ package ];
 
     services.dbus.packages = [ package ];
 
     systemd.packages = [ package ];
-  
+
+    users.users.geoclue = {
+      isSystemUser = true;
+      home = "/var/lib/geoclue";
+      group = "geoclue";
+      description = "Geoinformation service";
+    };
+
+    users.groups.geoclue = {};
+
+    systemd.tmpfiles.rules = [
+      "d /var/lib/geoclue 0755 geoclue geoclue"
+    ];
+
+    # restart geoclue service when the configuration changes
+    systemd.services."geoclue".restartTriggers = [
+      config.environment.etc."geoclue/geoclue.conf".source
+    ];
+
     # this needs to run as a user service, since it's associated with the
     # user who is making the requests
-    systemd.user.services = mkIf config.services.geoclue2.enableDemoAgent { 
+    systemd.user.services = mkIf cfg.enableDemoAgent {
       "geoclue-agent" = {
         description = "Geoclue agent";
         script = "${package}/libexec/geoclue-2.0/demos/agent";
@@ -62,7 +219,41 @@ in
       };
     };
 
-    environment.etc."geoclue/geoclue.conf".source = "${package}/etc/geoclue/geoclue.conf";
-  };
+    services.geoclue2.appConfig."epiphany" = {
+      isAllowed = true;
+      isSystem = false;
+    };
 
+    services.geoclue2.appConfig."firefox" = {
+      isAllowed = true;
+      isSystem = false;
+    };
+
+    environment.etc."geoclue/geoclue.conf".text =
+      generators.toINI {} ({
+        agent = {
+          whitelist = concatStringsSep ";"
+            (optional cfg.enableDemoAgent "geoclue-demo-agent" ++ defaultWhitelist);
+        };
+        network-nmea = {
+          enable = cfg.enableNmea;
+        };
+        "3g" = {
+          enable = cfg.enable3G;
+        };
+        cdma = {
+          enable = cfg.enableCDMA;
+        };
+        modem-gps = {
+          enable = cfg.enableModemGPS;
+        };
+        wifi = {
+          enable = cfg.enableWifi;
+          url = cfg.geoProviderUrl;
+          submit-data = boolToString cfg.submitData;
+          submission-url = cfg.submissionUrl;
+          submission-nick = cfg.submissionNick;
+        };
+      } // mapAttrs' appConfigToINICompatible cfg.appConfig);
+  };
 }
diff --git a/nixos/modules/services/desktops/gnome3/gnome-keyring.nix b/nixos/modules/services/desktops/gnome3/gnome-keyring.nix
index 4c350d8bb1c6..db60445ef773 100644
--- a/nixos/modules/services/desktops/gnome3/gnome-keyring.nix
+++ b/nixos/modules/services/desktops/gnome3/gnome-keyring.nix
@@ -37,6 +37,11 @@ with lib;
 
     security.pam.services.login.enableGnomeKeyring = true;
 
+    security.wrappers.gnome-keyring-daemon = {
+      source = "${pkgs.gnome3.gnome-keyring}/bin/gnome-keyring-daemon";
+      capabilities = "cap_ipc_lock=ep";
+    };
+
   };
 
 }
diff --git a/nixos/modules/services/desktops/gnome3/gnome-settings-daemon.nix b/nixos/modules/services/desktops/gnome3/gnome-settings-daemon.nix
index dbf0f4e9b118..7f7adcf26acf 100644
--- a/nixos/modules/services/desktops/gnome3/gnome-settings-daemon.nix
+++ b/nixos/modules/services/desktops/gnome3/gnome-settings-daemon.nix
@@ -18,7 +18,7 @@ in
 
     services.gnome3.gnome-settings-daemon = {
 
-      enable = mkEnableOption "GNOME Settings Daemon.";
+      enable = mkEnableOption "GNOME Settings Daemon";
 
       # There are many forks of gnome-settings-daemon
       package = mkOption {
diff --git a/nixos/modules/services/games/factorio.nix b/nixos/modules/services/games/factorio.nix
index 3f6bf9de8931..d04673a6c8b8 100644
--- a/nixos/modules/services/games/factorio.nix
+++ b/nixos/modules/services/games/factorio.nix
@@ -6,7 +6,7 @@ let
   cfg = config.services.factorio;
   factorio = pkgs.factorio-headless;
   name = "Factorio";
-  stateDir = cfg.stateDir;
+  stateDir = "/var/lib/${cfg.stateDirName}";
   mkSavePath = name: "${stateDir}/saves/${name}.zip";
   configFile = pkgs.writeText "factorio.conf" ''
     use-system-read-write-data-directories=true
@@ -80,11 +80,11 @@ in
           customizations.
         '';
       };
-      stateDir = mkOption {
-        type = types.path;
-        default = "/var/lib/factorio";
+      stateDirName = mkOption {
+        type = types.string;
+        default = "factorio";
         description = ''
-          The server's data directory.
+          Name of the directory under /var/lib holding the server's data.
 
           The configuration and map will be stored here.
         '';
@@ -176,20 +176,6 @@ in
   };
 
   config = mkIf cfg.enable {
-    users = {
-      users.factorio = {
-        uid             = config.ids.uids.factorio;
-        description     = "Factorio server user";
-        group           = "factorio";
-        home            = stateDir;
-        createHome      = true;
-      };
-
-      groups.factorio = {
-        gid = config.ids.gids.factorio;
-      };
-    };
-
     systemd.services.factorio = {
       description   = "Factorio headless server";
       wantedBy      = [ "multi-user.target" ];
@@ -205,12 +191,10 @@ in
       ];
 
       serviceConfig = {
-        User = "factorio";
-        Group = "factorio";
         Restart = "always";
         KillSignal = "SIGINT";
-        WorkingDirectory = stateDir;
-        PrivateTmp = true;
+        DynamicUser = true;
+        StateDirectory = cfg.stateDirName;
         UMask = "0007";
         ExecStart = toString [
           "${factorio}/bin/factorio"
@@ -220,6 +204,20 @@ in
           "--server-settings=${serverSettingsFile}"
           (optionalString (cfg.mods != []) "--mod-directory=${modDir}")
         ];
+
+        # Sandboxing
+        NoNewPrivileges = true;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        ProtectControlGroups = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" ];
+        RestrictRealtime = true;
+        RestrictNamespaces = true;
+        MemoryDenyWriteExecute = true;
       };
     };
 
diff --git a/nixos/modules/services/games/minecraft-server.nix b/nixos/modules/services/games/minecraft-server.nix
index 7d26d1501650..39a68f4b5536 100644
--- a/nixos/modules/services/games/minecraft-server.nix
+++ b/nixos/modules/services/games/minecraft-server.nix
@@ -215,8 +215,8 @@ in {
     networking.firewall = mkIf cfg.openFirewall (if cfg.declarative then {
       allowedUDPPorts = [ serverPort ];
       allowedTCPPorts = [ serverPort ]
-        ++ optional (! isNull queryPort) queryPort
-        ++ optional (! isNull rconPort) rconPort;
+        ++ optional (queryPort != null) queryPort
+        ++ optional (rconPort != null) rconPort;
     } else {
       allowedUDPPorts = [ defaultServerPort ];
       allowedTCPPorts = [ defaultServerPort ];
diff --git a/nixos/modules/services/hardware/bluetooth.nix b/nixos/modules/services/hardware/bluetooth.nix
index 2a8dfe4a66c9..c5f9d1f9b725 100644
--- a/nixos/modules/services/hardware/bluetooth.nix
+++ b/nixos/modules/services/hardware/bluetooth.nix
@@ -13,7 +13,7 @@ in {
   options = {
 
     hardware.bluetooth = {
-      enable = mkEnableOption "support for Bluetooth.";
+      enable = mkEnableOption "support for Bluetooth";
 
       powerOnBoot = mkOption {
         type    = types.bool;
diff --git a/nixos/modules/services/hardware/throttled.nix b/nixos/modules/services/hardware/throttled.nix
new file mode 100644
index 000000000000..cd5b01450e44
--- /dev/null
+++ b/nixos/modules/services/hardware/throttled.nix
@@ -0,0 +1,21 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.throttled;
+in {
+  options = {
+    services.throttled = {
+      enable = mkEnableOption "fix for Intel CPU throttling";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.packages = [ pkgs.throttled ];
+    # The upstream package has this in Install, but that's not enough, see the NixOS manual
+    systemd.services."lenovo_fix".wantedBy = [ "multi-user.target" ];
+
+    environment.etc."lenovo_fix.conf".source = "${pkgs.throttled}/etc/lenovo_fix.conf";
+  };
+}
diff --git a/nixos/modules/services/hardware/vdr.nix b/nixos/modules/services/hardware/vdr.nix
index 4822506a899b..6e246f70f515 100644
--- a/nixos/modules/services/hardware/vdr.nix
+++ b/nixos/modules/services/hardware/vdr.nix
@@ -12,7 +12,7 @@ in {
   options = {
 
     services.vdr = {
-      enable = mkEnableOption "enable VDR. Please put config into ${libDir}.";
+      enable = mkEnableOption "VDR. Please put config into ${libDir}";
 
       package = mkOption {
         type = types.package;
@@ -34,7 +34,7 @@ in {
         description = "Additional command line arguments to pass to VDR.";
       };
 
-      enableLirc = mkEnableOption "enable LIRC";
+      enableLirc = mkEnableOption "LIRC";
     };
   };
 
diff --git a/nixos/modules/services/logging/journalbeat.nix b/nixos/modules/services/logging/journalbeat.nix
index 8186a3b02c37..89f53b1b2454 100644
--- a/nixos/modules/services/logging/journalbeat.nix
+++ b/nixos/modules/services/logging/journalbeat.nix
@@ -5,11 +5,13 @@ with lib;
 let
   cfg = config.services.journalbeat;
 
+  lt6 = builtins.compareVersions cfg.package.version "6" < 0;
+
   journalbeatYml = pkgs.writeText "journalbeat.yml" ''
     name: ${cfg.name}
     tags: ${builtins.toJSON cfg.tags}
 
-    journalbeat.cursor_state_file: ${cfg.stateDir}/cursor-state
+    ${optionalString lt6 "journalbeat.cursor_state_file: /var/lib/${cfg.stateDir}/cursor-state"}
 
     ${cfg.extraConfig}
   '';
@@ -22,6 +24,16 @@ in
 
       enable = mkEnableOption "journalbeat";
 
+      package = mkOption {
+        type = types.package;
+        default = pkgs.journalbeat;
+        defaultText = "pkgs.journalbeat";
+        example = literalExample "pkgs.journalbeat7";
+        description = ''
+          The journalbeat package to use
+        '';
+      };
+
       name = mkOption {
         type = types.str;
         default = "journalbeat";
@@ -36,13 +48,17 @@ in
 
       stateDir = mkOption {
         type = types.str;
-        default = "/var/lib/journalbeat";
-        description = "The state directory. Journalbeat's own logs and other data are stored here.";
+        default = "journalbeat";
+        description = ''
+          Directory below <literal>/var/lib/</literal> to store journalbeat's
+          own logs and other data. This directory will be created automatically
+          using systemd's StateDirectory mechanism.
+        '';
       };
 
       extraConfig = mkOption {
         type = types.lines;
-        default = ''
+        default = optionalString lt6 ''
           journalbeat:
             seek_position: cursor
             cursor_seek_fallback: tail
@@ -61,7 +77,16 @@ in
 
   config = mkIf cfg.enable {
 
-    systemd.services.journalbeat = with pkgs; {
+    assertions = [
+      {
+        assertion = !hasPrefix "/" cfg.stateDir;
+        message =
+          "The option services.journalbeat.stateDir shouldn't be an absolute directory." +
+          " It should be a directory relative to /var/lib/.";
+      }
+    ];
+
+    systemd.services.journalbeat = {
       description = "Journalbeat log shipper";
       wantedBy = [ "multi-user.target" ];
       preStart = ''
@@ -69,7 +94,13 @@ in
         mkdir -p ${cfg.stateDir}/logs
       '';
       serviceConfig = {
-        ExecStart = "${pkgs.journalbeat}/bin/journalbeat -c ${journalbeatYml} -path.data ${cfg.stateDir}/data -path.logs ${cfg.stateDir}/logs";
+        StateDirectory = cfg.stateDir;
+        ExecStart = ''
+          ${cfg.package}/bin/journalbeat \
+            -c ${journalbeatYml} \
+            -path.data /var/lib/${cfg.stateDir}/data \
+            -path.logs /var/lib/${cfg.stateDir}/logs'';
+        Restart = "always";
       };
     };
   };
diff --git a/nixos/modules/services/logging/journalwatch.nix b/nixos/modules/services/logging/journalwatch.nix
index d0824df38ae3..576c646c0f58 100644
--- a/nixos/modules/services/logging/journalwatch.nix
+++ b/nixos/modules/services/logging/journalwatch.nix
@@ -4,6 +4,8 @@ with lib;
 let
   cfg = config.services.journalwatch;
   user = "journalwatch";
+  # for journal access
+  group = "systemd-journal";
   dataDir = "/var/lib/${user}";
 
   journalwatchConfig = pkgs.writeText "config" (''
@@ -31,6 +33,17 @@ let
 
   '') filterBlocks);
 
+  # can't use joinSymlinks directly, because when we point $XDG_CONFIG_HOME
+  # to the /nix/store path, we still need the subdirectory "journalwatch" inside that
+  # to match journalwatch's expectations
+  journalwatchConfigDir = pkgs.runCommand "journalwatch-config"
+    { preferLocalBuild = true; allowSubstitutes = false; }
+    ''
+      mkdir -p $out/journalwatch
+      ln -sf ${journalwatchConfig} $out/journalwatch/config
+      ln -sf ${journalwatchPatterns} $out/journalwatch/patterns
+    '';
+
 
 in {
   options = {
@@ -199,33 +212,38 @@ in {
 
     users.users.${user} = {
       isSystemUser = true;
-      createHome = true;
       home = dataDir;
-      # for journal access
-      group = "systemd-journal";
+      group = group;
     };
 
+    systemd.tmpfiles.rules = [
+      # present since NixOS 19.09: remove old stateful symlink join directory,
+      # which has been replaced with the journalwatchConfigDir store path
+      "R ${dataDir}/config"
+    ];
+
     systemd.services.journalwatch = {
+
       environment = {
+        # journalwatch stores the last processed timpestamp here
+        # the share subdirectory is historic now that config home lives in /nix/store,
+        # but moving this in a backwards-compatible way is much more work than what's justified
+        # for cleaning that up.
         XDG_DATA_HOME = "${dataDir}/share";
-        XDG_CONFIG_HOME = "${dataDir}/config";
+        XDG_CONFIG_HOME = journalwatchConfigDir;
       };
       serviceConfig = {
         User = user;
+        Group = group;
         Type = "oneshot";
-        PermissionsStartOnly = true;
+        # requires a relative directory name to create beneath /var/lib
+        StateDirectory = user;
+        StateDirectoryMode = 0750;
         ExecStart = "${pkgs.python3Packages.journalwatch}/bin/journalwatch mail";
         # lowest CPU and IO priority, but both still in best-effort class to prevent starvation
         Nice=19;
         IOSchedulingPriority=7;
       };
-      preStart = ''
-        chown -R ${user}:systemd-journal ${dataDir}
-        chmod -R u+rwX,go-w ${dataDir}
-        mkdir -p ${dataDir}/config/journalwatch
-        ln -sf ${journalwatchConfig} ${dataDir}/config/journalwatch/config
-        ln -sf ${journalwatchPatterns} ${dataDir}/config/journalwatch/patterns
-      '';
     };
 
     systemd.timers.journalwatch = {
diff --git a/nixos/modules/services/logging/logcheck.nix b/nixos/modules/services/logging/logcheck.nix
index 9c64160e92bc..f139190a1709 100644
--- a/nixos/modules/services/logging/logcheck.nix
+++ b/nixos/modules/services/logging/logcheck.nix
@@ -227,7 +227,7 @@ in
     '';
 
     services.cron.systemCronJobs =
-        let withTime = name: {timeArgs, ...}: ! (builtins.isNull timeArgs);
+        let withTime = name: {timeArgs, ...}: timeArgs != null;
             mkCron = name: {user, cmdline, timeArgs, ...}: ''
               ${timeArgs} ${user} ${cmdline}
             '';
diff --git a/nixos/modules/services/mail/dovecot.nix b/nixos/modules/services/mail/dovecot.nix
index 30ad7d82fb80..139011dca23a 100644
--- a/nixos/modules/services/mail/dovecot.nix
+++ b/nixos/modules/services/mail/dovecot.nix
@@ -16,13 +16,13 @@ let
       sendmail_path = /run/wrappers/bin/sendmail
     ''
 
-    (if isNull cfg.sslServerCert then ''
+    (if cfg.sslServerCert == null then ''
       ssl = no
       disable_plaintext_auth = no
     '' else ''
       ssl_cert = <${cfg.sslServerCert}
       ssl_key = <${cfg.sslServerKey}
-      ${optionalString (!(isNull cfg.sslCACert)) ("ssl_ca = <" + cfg.sslCACert)}
+      ${optionalString (cfg.sslCACert != null) ("ssl_ca = <" + cfg.sslCACert)}
       ssl_dh = <${config.security.dhparams.params.dovecot2.path}
       disable_plaintext_auth = yes
     '')
@@ -298,7 +298,7 @@ in
   config = mkIf cfg.enable {
     security.pam.services.dovecot2 = mkIf cfg.enablePAM {};
 
-    security.dhparams = mkIf (! isNull cfg.sslServerCert) {
+    security.dhparams = mkIf (cfg.sslServerCert != null) {
       enable = true;
       params.dovecot2 = {};
     };
@@ -384,14 +384,14 @@ in
       { assertion = intersectLists cfg.protocols [ "pop3" "imap" ] != [];
         message = "dovecot needs at least one of the IMAP or POP3 listeners enabled";
       }
-      { assertion = isNull cfg.sslServerCert == isNull cfg.sslServerKey
-          && (!(isNull cfg.sslCACert) -> !(isNull cfg.sslServerCert || isNull cfg.sslServerKey));
+      { assertion = (cfg.sslServerCert == null) == (cfg.sslServerKey == null)
+          && (cfg.sslCACert != null -> !(cfg.sslServerCert == null || cfg.sslServerKey == null));
         message = "dovecot needs both sslServerCert and sslServerKey defined for working crypto";
       }
       { assertion = cfg.showPAMFailure -> cfg.enablePAM;
         message = "dovecot is configured with showPAMFailure while enablePAM is disabled";
       }
-      { assertion = (cfg.sieveScripts != {}) -> ((cfg.mailUser != null) && (cfg.mailGroup != null));
+      { assertion = cfg.sieveScripts != {} -> (cfg.mailUser != null && cfg.mailGroup != null);
         message = "dovecot requires mailUser and mailGroup to be set when sieveScripts is set";
       }
     ];
diff --git a/nixos/modules/services/mail/mailcatcher.nix b/nixos/modules/services/mail/mailcatcher.nix
index 2c6aadadce9d..fa8d41e918d3 100644
--- a/nixos/modules/services/mail/mailcatcher.nix
+++ b/nixos/modules/services/mail/mailcatcher.nix
@@ -11,7 +11,7 @@ in
   options = {
 
     services.mailcatcher = {
-      enable = mkEnableOption "Enable MailCatcher.";
+      enable = mkEnableOption "MailCatcher";
 
       http.ip = mkOption {
         type = types.str;
diff --git a/nixos/modules/services/mail/nullmailer.nix b/nixos/modules/services/mail/nullmailer.nix
index 418c02af4b7f..9997d287013e 100644
--- a/nixos/modules/services/mail/nullmailer.nix
+++ b/nixos/modules/services/mail/nullmailer.nix
@@ -212,6 +212,10 @@ with lib;
       };
     };
 
+    systemd.tmpfiles.rules = [
+      "d /var/spool/nullmailer - ${cfg.user} - - -"
+    ];
+
     systemd.services.nullmailer = {
       description = "nullmailer";
       wantedBy = [ "multi-user.target" ];
@@ -220,13 +224,11 @@ with lib;
       preStart = ''
         mkdir -p /var/spool/nullmailer/{queue,tmp}
         rm -f /var/spool/nullmailer/trigger && mkfifo -m 660 /var/spool/nullmailer/trigger
-        chown ${cfg.user} /var/spool/nullmailer/*
       '';
 
       serviceConfig = {
         User = cfg.user;
         Group = cfg.group;
-        PermissionsStartOnly=true;
         ExecStart = "${pkgs.nullmailer}/bin/nullmailer-send";
         Restart = "always";
       };
diff --git a/nixos/modules/services/mail/offlineimap.nix b/nixos/modules/services/mail/offlineimap.nix
index 4b24bd8d0813..294e3806f94a 100644
--- a/nixos/modules/services/mail/offlineimap.nix
+++ b/nixos/modules/services/mail/offlineimap.nix
@@ -7,7 +7,7 @@ let
 in {
 
   options.services.offlineimap = {
-    enable = mkEnableOption "Offlineimap, a software to dispose your mailbox(es) as a local Maildir(s).";
+    enable = mkEnableOption "OfflineIMAP, a software to dispose your mailbox(es) as a local Maildir(s)";
 
     install = mkOption {
       type = types.bool;
diff --git a/nixos/modules/services/mail/rss2email.nix b/nixos/modules/services/mail/rss2email.nix
index 5f3b2877008f..a123736005ab 100644
--- a/nixos/modules/services/mail/rss2email.nix
+++ b/nixos/modules/services/mail/rss2email.nix
@@ -94,6 +94,10 @@ in {
 
     services.rss2email.config.to = cfg.to;
 
+    systemd.tmpfiles.rules = [
+      "d /var/rss2email 0700 rss2email rss2email - -"
+    ];
+
     systemd.services.rss2email = let
       conf = pkgs.writeText "rss2email.cfg" (lib.generators.toINI {} ({
           DEFAULT = cfg.config;
@@ -105,22 +109,16 @@ in {
     in
     {
       preStart = ''
-        mkdir -p /var/rss2email
-        chmod 700 /var/rss2email
-
         cp ${conf} /var/rss2email/conf.cfg
         if [ ! -f /var/rss2email/db.json ]; then
           echo '{"version":2,"feeds":[]}' > /var/rss2email/db.json
         fi
-
-        chown -R rss2email:rss2email /var/rss2email
       '';
       path = [ pkgs.system-sendmail ];
       serviceConfig = {
         ExecStart =
           "${pkgs.rss2email}/bin/r2e -c /var/rss2email/conf.cfg -d /var/rss2email/db.json run";
         User = "rss2email";
-        PermissionsStartOnly = "true";
       };
     };
 
diff --git a/nixos/modules/services/misc/beanstalkd.nix b/nixos/modules/services/misc/beanstalkd.nix
index 8a3e0ab1949a..06e881406b52 100644
--- a/nixos/modules/services/misc/beanstalkd.nix
+++ b/nixos/modules/services/misc/beanstalkd.nix
@@ -12,7 +12,7 @@ in
 
   options = {
     services.beanstalkd = {
-      enable = mkEnableOption "Enable the Beanstalk work queue.";
+      enable = mkEnableOption "the Beanstalk work queue";
 
       listen = {
         port = mkOption {
diff --git a/nixos/modules/services/misc/bepasty.nix b/nixos/modules/services/misc/bepasty.nix
index 006feca42b32..87d360681445 100644
--- a/nixos/modules/services/misc/bepasty.nix
+++ b/nixos/modules/services/misc/bepasty.nix
@@ -143,7 +143,7 @@ in
           serviceConfig = {
             Type = "simple";
             PrivateTmp = true;
-            ExecStartPre = assert !isNull server.secretKeyFile; pkgs.writeScript "bepasty-server.${name}-init" ''
+            ExecStartPre = assert server.secretKeyFile != null; pkgs.writeScript "bepasty-server.${name}-init" ''
               #!/bin/sh
               mkdir -p "${server.workDir}"
               mkdir -p "${server.dataDir}"
diff --git a/nixos/modules/services/misc/emby.nix b/nixos/modules/services/misc/emby.nix
deleted file mode 100644
index 0ad4a3f7376f..000000000000
--- a/nixos/modules/services/misc/emby.nix
+++ /dev/null
@@ -1,76 +0,0 @@
-{ config, pkgs, lib, ... }:
-
-with lib;
-
-let
-  cfg = config.services.emby;
-in
-{
-  options = {
-    services.emby = {
-      enable = mkEnableOption "Emby Media Server";
-
-      user = mkOption {
-        type = types.str;
-        default = "emby";
-        description = "User account under which Emby runs.";
-      };
-
-      group = mkOption {
-        type = types.str;
-        default = "emby";
-        description = "Group under which emby runs.";
-      };
-
-      dataDir = mkOption {
-        type = types.path;
-        default = "/var/lib/emby/ProgramData-Server";
-        description = "Location where Emby stores its data.";
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    systemd.services.emby = {
-      description = "Emby Media Server";
-      after = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
-      preStart = ''
-        if [ -d ${cfg.dataDir} ]
-        then
-            for plugin in ${cfg.dataDir}/plugins/*
-            do
-                echo "Correcting permissions of plugin: $plugin"
-                chmod u+w $plugin
-            done
-        else
-            echo "Creating initial Emby data directory in ${cfg.dataDir}"
-            mkdir -p ${cfg.dataDir}
-            chown -R ${cfg.user}:${cfg.group} ${cfg.dataDir}
-        fi
-      '';
-
-      serviceConfig = {
-        Type = "simple";
-        User = cfg.user;
-        Group = cfg.group;
-        PermissionsStartOnly = "true";
-        ExecStart = "${pkgs.emby}/bin/emby -programdata ${cfg.dataDir}";
-        Restart = "on-failure";
-      };
-    };
-
-    users.users = mkIf (cfg.user == "emby") {
-      emby = {
-        group = cfg.group;
-        uid = config.ids.uids.emby;
-      };
-    };
-
-    users.groups = mkIf (cfg.group == "emby") {
-      emby = {
-        gid = config.ids.gids.emby;
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/misc/errbot.nix b/nixos/modules/services/misc/errbot.nix
index ac6ba2181de2..256adce2f02e 100644
--- a/nixos/modules/services/misc/errbot.nix
+++ b/nixos/modules/services/misc/errbot.nix
@@ -81,7 +81,7 @@ in {
 
     systemd.services = mapAttrs' (name: instanceCfg: nameValuePair "errbot-${name}" (
     let
-      dataDir = if !isNull instanceCfg.dataDir then instanceCfg.dataDir else
+      dataDir = if instanceCfg.dataDir != null then instanceCfg.dataDir else
         "/var/lib/errbot/${name}";
     in {
       after = [ "network-online.target" ];
diff --git a/nixos/modules/services/misc/etcd.nix b/nixos/modules/services/misc/etcd.nix
index 2d1893dae64b..e4d5322f9b5f 100644
--- a/nixos/modules/services/misc/etcd.nix
+++ b/nixos/modules/services/misc/etcd.nix
@@ -142,6 +142,10 @@ in {
   };
 
   config = mkIf cfg.enable {
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' 0700 etcd - - -"
+    ];
+
     systemd.services.etcd = {
       description = "etcd key-value store";
       wantedBy = [ "multi-user.target" ];
@@ -176,14 +180,8 @@ in {
         Type = "notify";
         ExecStart = "${pkgs.etcd.bin}/bin/etcd";
         User = "etcd";
-        PermissionsStartOnly = true;
         LimitNOFILE = 40000;
       };
-
-      preStart = ''
-        mkdir -m 0700 -p ${cfg.dataDir}
-        if [ "$(id -u)" = 0 ]; then chown etcd ${cfg.dataDir}; fi
-      '';
     };
 
     environment.systemPackages = [ pkgs.etcdctl ];
diff --git a/nixos/modules/services/misc/ethminer.nix b/nixos/modules/services/misc/ethminer.nix
new file mode 100644
index 000000000000..2958cf214473
--- /dev/null
+++ b/nixos/modules/services/misc/ethminer.nix
@@ -0,0 +1,115 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.ethminer;
+  poolUrl = escapeShellArg "stratum1+tcp://${cfg.wallet}@${cfg.pool}:${toString cfg.stratumPort}/${cfg.rig}/${cfg.registerMail}";
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.ethminer = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Enable ethminer ether mining.";
+      };
+
+      recheckInterval = mkOption {
+        type = types.int;
+        default = 2000;
+        description = "Interval in milliseconds between farm rechecks.";
+      };
+
+      toolkit = mkOption {
+        type = types.enum [ "cuda" "opencl" ];
+        default = "cuda";
+        description = "Cuda or opencl toolkit.";
+      };
+
+      apiPort = mkOption {
+        type = types.int;
+        default = -3333;
+        description = "Ethminer api port. minus sign puts api in read-only mode.";
+      };
+
+      wallet = mkOption {
+        type = types.str;
+        example = "0x0123456789abcdef0123456789abcdef01234567";
+        description = "Ethereum wallet address.";
+      };
+
+      pool = mkOption {
+        type = types.str;
+        example = "eth-us-east1.nanopool.org";
+        description = "Mining pool address.";
+      };
+
+      stratumPort = mkOption {
+        type = types.port;
+        default = 9999;
+        description = "Stratum protocol tcp port.";
+      };
+
+      rig = mkOption {
+        type = types.str;
+        default = "mining-rig-name";
+        description = "Mining rig name.";
+      };
+
+      registerMail = mkOption {
+        type = types.str;
+        example = "email%40example.org";
+        description = "Url encoded email address to register with pool.";
+      };
+
+      maxPower = mkOption {
+        type = types.int;
+        default = 115;
+        description = "Miner max watt usage.";
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.services.ethminer = {
+      path = [ pkgs.cudatoolkit ];
+      description = "ethminer ethereum mining service";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        DynamicUser = true;
+        ExecStartPost = optional (cfg.toolkit == "cuda") "+${getBin config.boot.kernelPackages.nvidia_x11}/bin/nvidia-smi -pl ${toString cfg.maxPower}";
+      };
+
+      environment = {
+        LD_LIBRARY_PATH = "${config.boot.kernelPackages.nvidia_x11}/lib";
+      };
+
+      script = ''
+        ${pkgs.ethminer}/bin/.ethminer-wrapped \
+          --farm-recheck ${toString cfg.recheckInterval} \
+          --report-hashrate \
+          --${cfg.toolkit} \
+          --api-port ${toString cfg.apiPort} \
+          --pool ${poolUrl}
+      '';
+
+    };
+
+  };
+
+}
diff --git a/nixos/modules/services/misc/gitea.nix b/nixos/modules/services/misc/gitea.nix
index be4d38719785..7daa2dd0d4cc 100644
--- a/nixos/modules/services/misc/gitea.nix
+++ b/nixos/modules/services/misc/gitea.nix
@@ -8,6 +8,7 @@ let
   pg = config.services.postgresql;
   useMysql = cfg.database.type == "mysql";
   usePostgresql = cfg.database.type == "postgres";
+  useSqlite = cfg.database.type == "sqlite3";
   configFile = pkgs.writeText "app.ini" ''
     APP_NAME = ${cfg.appName}
     RUN_USER = ${cfg.user}
@@ -15,11 +16,15 @@ let
 
     [database]
     DB_TYPE = ${cfg.database.type}
-    HOST = ${if cfg.database.socket != null then cfg.database.socket else cfg.database.host + ":" + toString cfg.database.port}
-    NAME = ${cfg.database.name}
-    USER = ${cfg.database.user}
-    PASSWD = #dbpass#
-    PATH = ${cfg.database.path}
+    ${optionalString (usePostgresql || useMysql) ''
+      HOST = ${if cfg.database.socket != null then cfg.database.socket else cfg.database.host + ":" + toString cfg.database.port}
+      NAME = ${cfg.database.name}
+      USER = ${cfg.database.user}
+      PASSWD = #dbpass#
+    ''}
+    ${optionalString useSqlite ''
+      PATH = ${cfg.database.path}
+    ''}
     ${optionalString usePostgresql ''
       SSL_MODE = disable
     ''}
@@ -154,7 +159,8 @@ in
 
         socket = mkOption {
           type = types.nullOr types.path;
-          default = null;
+          default = if (cfg.database.createDatabase && usePostgresql) then "/run/postgresql" else if (cfg.database.createDatabase && useMysql) then "/run/mysqld/mysqld.sock" else null;
+          defaultText = "null";
           example = "/run/mysqld/mysqld.sock";
           description = "Path to the unix socket file to use for authentication.";
         };
@@ -168,10 +174,7 @@ in
         createDatabase = mkOption {
           type = types.bool;
           default = true;
-          description = ''
-            Whether to create a local postgresql database automatically.
-            This only applies if database type "postgres" is selected.
-          '';
+          description = "Whether to create a local database automatically.";
         };
       };
 
@@ -272,7 +275,46 @@ in
   };
 
   config = mkIf cfg.enable {
-    services.postgresql.enable = mkIf usePostgresql (mkDefault true);
+    assertions = [
+      { assertion = cfg.database.createDatabase -> cfg.database.user == cfg.user;
+        message = "services.gitea.database.user must match services.gitea.user if the database is to be automatically provisioned";
+      }
+    ];
+
+    services.postgresql = optionalAttrs (usePostgresql && cfg.database.createDatabase) {
+      enable = mkDefault true;
+
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.database.user;
+          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
+
+    services.mysql = optionalAttrs (useMysql && cfg.database.createDatabase) {
+      enable = mkDefault true;
+      package = mkDefault pkgs.mariadb;
+
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.database.user;
+          ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.stateDir}' - ${cfg.user} gitea - -"
+      "d '${cfg.stateDir}/conf' - ${cfg.user} gitea - -"
+      "d '${cfg.stateDir}/custom/conf' - ${cfg.user} gitea - -"
+      "d '${cfg.repositoryRoot}' - ${cfg.user} gitea - -"
+      "Z '${cfg.stateDir}' - ${cfg.user} gitea - -"
+
+      # If we have a folder or symlink with gitea locales, remove it
+      # And symlink the current gitea locales in place
+      "L+ '${cfg.stateDir}/conf/locale' - - - - ${gitea.out}/locale"
+    ];
 
     systemd.services.gitea = {
       description = "gitea";
@@ -284,12 +326,8 @@ in
         runConfig = "${cfg.stateDir}/custom/conf/app.ini";
         secretKey = "${cfg.stateDir}/custom/conf/secret_key";
       in ''
-        # Make sure that the stateDir exists, as well as the conf dir in there
-        mkdir -p ${cfg.stateDir}/conf
-
         # copy custom configuration and generate a random secret key if needed
         ${optionalString (cfg.useWizard == false) ''
-          mkdir -p ${cfg.stateDir}/custom/conf
           cp -f ${configFile} ${runConfig}
 
           if [ ! -e ${secretKey} ]; then
@@ -304,7 +342,6 @@ in
           chmod 640 ${runConfig} ${secretKey}
         ''}
 
-        mkdir -p ${cfg.repositoryRoot}
         # update all hooks' binary paths
         HOOKS=$(find ${cfg.repositoryRoot} -mindepth 4 -maxdepth 6 -type f -wholename "*git/hooks/*")
         if [ "$HOOKS" ]
@@ -314,43 +351,19 @@ in
           sed -ri 's,/nix/store/[a-z0-9.-]+/bin/bash,${pkgs.bash}/bin/bash,g' $HOOKS
           sed -ri 's,/nix/store/[a-z0-9.-]+/bin/perl,${pkgs.perl}/bin/perl,g' $HOOKS
         fi
-        # If we have a folder or symlink with gitea locales, remove it
-        if [ -e ${cfg.stateDir}/conf/locale ]
-        then
-          rm -r ${cfg.stateDir}/conf/locale
-        fi
-        # And symlink the current gitea locales in place
-        ln -s ${gitea.out}/locale ${cfg.stateDir}/conf/locale
+
         # update command option in authorized_keys
         if [ -r ${cfg.stateDir}/.ssh/authorized_keys ]
         then
           sed -ri 's,/nix/store/[a-z0-9.-]+/bin/gitea,${gitea.bin}/bin/gitea,g' ${cfg.stateDir}/.ssh/authorized_keys
         fi
-      '' + optionalString (usePostgresql && cfg.database.createDatabase) ''
-        if ! test -e "${cfg.stateDir}/db-created"; then
-          echo "CREATE ROLE ${cfg.database.user}
-                  WITH ENCRYPTED PASSWORD '$(head -n1 ${cfg.database.passwordFile})'
-                  NOCREATEDB NOCREATEROLE LOGIN"   |
-            ${pkgs.sudo}/bin/sudo -u ${pg.superUser} ${pg.package}/bin/psql
-          ${pkgs.sudo}/bin/sudo -u ${pg.superUser} \
-            ${pg.package}/bin/createdb             \
-            --owner=${cfg.database.user}           \
-            --encoding=UTF8                        \
-            --lc-collate=C                         \
-            --lc-ctype=C                           \
-            --template=template0                   \
-            ${cfg.database.name}
-          touch "${cfg.stateDir}/db-created"
-        fi
-      '' + ''
-        chown ${cfg.user} -R ${cfg.stateDir}
       '';
 
       serviceConfig = {
         Type = "simple";
         User = cfg.user;
+        Group = "gitea";
         WorkingDirectory = cfg.stateDir;
-        PermissionsStartOnly = true;
         ExecStart = "${gitea.bin}/bin/gitea web";
         Restart = "always";
       };
@@ -362,15 +375,17 @@ in
       };
     };
 
-    users = mkIf (cfg.user == "gitea") {
-      users.gitea = {
+    users.users = mkIf (cfg.user == "gitea") {
+      gitea = {
         description = "Gitea Service";
         home = cfg.stateDir;
-        createHome = true;
         useDefaultShell = true;
+        group = "gitea";
       };
     };
 
+    users.groups.gitea = {};
+
     warnings = optional (cfg.database.password != "")
       ''config.services.gitea.database.password will be stored as plaintext
         in the Nix store. Use database.passwordFile instead.'';
diff --git a/nixos/modules/services/misc/home-assistant.nix b/nixos/modules/services/misc/home-assistant.nix
index 7f8d31bcf0b8..f1b351246740 100644
--- a/nixos/modules/services/misc/home-assistant.nix
+++ b/nixos/modules/services/misc/home-assistant.nix
@@ -21,32 +21,23 @@ let
 
   availableComponents = cfg.package.availableComponents;
 
-  # Given component "parentConfig.platform", returns whether config.parentConfig
-  # is a list containing a set with set.platform == "platform".
+  usedPlatforms = config:
+    if isAttrs config then
+      optional (config ? platform) config.platform
+      ++ concatMap usedPlatforms (attrValues config)
+    else if isList config then
+      concatMap usedPlatforms config
+    else [ ];
+
+  # Given a component "platform", looks up whether it is used in the config
+  # as `platform = "platform";`.
   #
-  # For example, the component sensor.luftdaten is used as follows:
+  # For example, the component mqtt.sensor is used as follows:
   # config.sensor = [ {
-  #   platform = "luftdaten";
+  #   platform = "mqtt";
   #   ...
   # } ];
-  #
-  # Beginning with 0.87 Home Assistant is migrating their components to the
-  # scheme "platform.subComponent", e.g. "hue.light" instead of "light.hue".
-  # See https://developers.home-assistant.io/blog/2019/02/19/the-great-migration.html.
-  # Hence, we also check whether we find an entry in the config when interpreting
-  # the first part of the path as the component.
-  useComponentPlatform = component:
-    let
-      path = splitString "." component;
-      # old: platform is the last part of path
-      parentConfig = attrByPath (init path) null cfg.config;
-      platform = last path;
-      # new: platform is the first part of the path
-      parentConfig' = attrByPath (tail path) null cfg.config;
-      platform' = head path;
-    in
-      (isList parentConfig && any (item: item.platform or null == platform) parentConfig)
-      || (isList parentConfig' && any (item: item.platform or null == platform') parentConfig');
+  useComponentPlatform = component: elem component (usedPlatforms cfg.config);
 
   # Returns whether component is used in config
   useComponent = component:
diff --git a/nixos/modules/services/misc/jackett.nix b/nixos/modules/services/misc/jackett.nix
index b18ce2b1f81a..a07f20e5c24b 100644
--- a/nixos/modules/services/misc/jackett.nix
+++ b/nixos/modules/services/misc/jackett.nix
@@ -38,24 +38,19 @@ in
   };
 
   config = mkIf cfg.enable {
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' 0700 ${cfg.user} ${cfg.group} - -"
+    ];
+
     systemd.services.jackett = {
       description = "Jackett";
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
-      preStart = ''
-        test -d ${cfg.dataDir} || {
-          echo "Creating jackett data directory in ${cfg.dataDir}"
-          mkdir -p ${cfg.dataDir}
-        }
-        chown -R ${cfg.user}:${cfg.group} ${cfg.dataDir}
-        chmod 0700 ${cfg.dataDir}
-      '';
 
       serviceConfig = {
         Type = "simple";
         User = cfg.user;
         Group = cfg.group;
-        PermissionsStartOnly = "true";
         ExecStart = "${pkgs.jackett}/bin/Jackett --NoUpdates --DataFolder '${cfg.dataDir}'";
         Restart = "on-failure";
       };
diff --git a/nixos/modules/services/misc/jellyfin.nix b/nixos/modules/services/misc/jellyfin.nix
new file mode 100644
index 000000000000..55559206568d
--- /dev/null
+++ b/nixos/modules/services/misc/jellyfin.nix
@@ -0,0 +1,54 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.jellyfin;
+in
+{
+  options = {
+    services.jellyfin = {
+      enable = mkEnableOption "Jellyfin Media Server";
+
+      user = mkOption {
+        type = types.str;
+        default = "jellyfin";
+        description = "User account under which Jellyfin runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "jellyfin";
+        description = "Group under which jellyfin runs.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.jellyfin = {
+      description = "Jellyfin Media Server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = rec {
+        User = cfg.user;
+        Group = cfg.group;
+        StateDirectory = "jellyfin";
+        CacheDirectory = "jellyfin";
+        ExecStart = "${pkgs.jellyfin}/bin/jellyfin --datadir '/var/lib/${StateDirectory}' --cachedir '/var/cache/${CacheDirectory}'";
+        Restart = "on-failure";
+      };
+    };
+
+    users.users = mkIf (cfg.user == "jellyfin") {
+      jellyfin.group = cfg.group;
+    };
+
+    users.groups = mkIf (cfg.group == "jellyfin") {
+      jellyfin = {};
+    };
+
+  };
+
+  meta.maintainers = with lib.maintainers; [ minijackson ];
+}
diff --git a/nixos/modules/services/misc/lidarr.nix b/nixos/modules/services/misc/lidarr.nix
index 627f22334fe8..f466402abfc7 100644
--- a/nixos/modules/services/misc/lidarr.nix
+++ b/nixos/modules/services/misc/lidarr.nix
@@ -17,20 +17,15 @@ in
       description = "Lidarr";
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
-      preStart = ''
-        [ ! -d /var/lib/lidarr ] && mkdir -p /var/lib/lidarr
-        chown -R lidarr:lidarr /var/lib/lidarr
-      '';
 
       serviceConfig = {
         Type = "simple";
         User = "lidarr";
         Group = "lidarr";
-        PermissionsStartOnly = "true";
         ExecStart = "${pkgs.lidarr}/bin/Lidarr";
         Restart = "on-failure";
 
-        StateDirectory = "/var/lib/lidarr/";
+        StateDirectory = "lidarr";
         StateDirectoryMode = "0770";
       };
     };
diff --git a/nixos/modules/services/misc/matrix-synapse.nix b/nixos/modules/services/misc/matrix-synapse.nix
index 5e465926b832..00c8e7408030 100644
--- a/nixos/modules/services/misc/matrix-synapse.nix
+++ b/nixos/modules/services/misc/matrix-synapse.nix
@@ -554,7 +554,10 @@ in {
       };
       trusted_third_party_id_servers = mkOption {
         type = types.listOf types.str;
-        default = ["matrix.org"];
+        default = [
+          "matrix.org"
+          "vector.im"
+        ];
         description = ''
           The list of identity servers trusted to verify third party identifiers by this server.
         '';
diff --git a/nixos/modules/services/misc/mesos-master.nix b/nixos/modules/services/misc/mesos-master.nix
index 0523c6549ed6..572a9847e46c 100644
--- a/nixos/modules/services/misc/mesos-master.nix
+++ b/nixos/modules/services/misc/mesos-master.nix
@@ -95,6 +95,9 @@ in {
 
 
   config = mkIf cfg.enable {
+    systemd.tmpfiles.rules = [
+      "d '${cfg.workDir}' 0700 - - - -"
+    ];
     systemd.services.mesos-master = {
       description = "Mesos Master";
       wantedBy = [ "multi-user.target" ];
@@ -114,11 +117,7 @@ in {
             ${toString cfg.extraCmdLineOptions}
         '';
         Restart = "on-failure";
-        PermissionsStartOnly = true;
       };
-      preStart = ''
-        mkdir -m 0700 -p ${cfg.workDir}
-      '';
     };
   };
 
diff --git a/nixos/modules/services/misc/mesos-slave.nix b/nixos/modules/services/misc/mesos-slave.nix
index 468c7f36ecc5..170065d0065e 100644
--- a/nixos/modules/services/misc/mesos-slave.nix
+++ b/nixos/modules/services/misc/mesos-slave.nix
@@ -184,6 +184,9 @@ in {
   };
 
   config = mkIf cfg.enable {
+    systemd.tmpfiles.rules = [
+      "d '${cfg.workDir}' 0701 - - - -"
+    ];
     systemd.services.mesos-slave = {
       description = "Mesos Slave";
       wantedBy = [ "multi-user.target" ];
@@ -210,11 +213,7 @@ in {
             --executor_environment_variables=${lib.escapeShellArg (builtins.toJSON cfg.executorEnvironmentVariables)} \
             ${toString cfg.extraCmdLineOptions}
         '';
-        PermissionsStartOnly = true;
       };
-      preStart = ''
-        mkdir -m 0701 -p ${cfg.workDir}
-      '';
     };
   };
 
diff --git a/nixos/modules/services/misc/nix-daemon.nix b/nixos/modules/services/misc/nix-daemon.nix
index 665215822af8..8db3c44246f3 100644
--- a/nixos/modules/services/misc/nix-daemon.nix
+++ b/nixos/modules/services/misc/nix-daemon.nix
@@ -60,6 +60,7 @@ let
         ${optionalString (isNix20 && !cfg.distributedBuilds) ''
           builders =
         ''}
+        system-features = ${toString cfg.systemFeatures}
         $extraOptions
         END
       '' + optionalString cfg.checkConfig (
@@ -360,6 +361,14 @@ in
         '';
       };
 
+      systemFeatures = mkOption {
+        type = types.listOf types.str;
+        example = [ "kvm" "big-parallel" "gccarch-skylake" ];
+        description = ''
+          The supported features of a machine
+        '';
+      };
+
       checkConfig = mkOption {
         type = types.bool;
         default = true;
@@ -478,6 +487,21 @@ in
           /nix/var/nix/gcroots/tmp
       '';
 
+    nix.systemFeatures = mkDefault (
+      [ "nixos-test" "benchmark" "big-parallel" "kvm" ] ++
+      optionals (pkgs.stdenv.isx86_64 && pkgs.hostPlatform.platform ? gcc.arch) (
+        # a x86_64 builder can run code for `platform.gcc.arch` and minor architectures:
+        [ "gccarch-${pkgs.hostPlatform.platform.gcc.arch}" ] ++ {
+          "sandybridge"    = [ "gccarch-westmere" ];
+          "ivybridge"      = [ "gccarch-westmere" "gccarch-sandybridge" ];
+          "haswell"        = [ "gccarch-westmere" "gccarch-sandybridge" "gccarch-ivybridge" ];
+          "broadwell"      = [ "gccarch-westmere" "gccarch-sandybridge" "gccarch-ivybridge" "gccarch-haswell" ];
+          "skylake"        = [ "gccarch-westmere" "gccarch-sandybridge" "gccarch-ivybridge" "gccarch-haswell" "gccarch-broadwell" ];
+          "skylake-avx512" = [ "gccarch-westmere" "gccarch-sandybridge" "gccarch-ivybridge" "gccarch-haswell" "gccarch-broadwell" "gccarch-skylake" ];
+        }.${pkgs.hostPlatform.platform.gcc.arch} or []
+      )
+    );
+
   };
 
 }
diff --git a/nixos/modules/services/misc/nzbget.nix b/nixos/modules/services/misc/nzbget.nix
index 6ab98751c57b..eb7b4c05d82d 100644
--- a/nixos/modules/services/misc/nzbget.nix
+++ b/nixos/modules/services/misc/nzbget.nix
@@ -4,32 +4,34 @@ with lib;
 
 let
   cfg = config.services.nzbget;
-  dataDir = builtins.dirOf cfg.configFile;
-in {
-  options = {
-    services.nzbget = {
-      enable = mkEnableOption "NZBGet";
+  pkg = pkgs.nzbget;
+  stateDir = "/var/lib/nzbget";
+  configFile = "${stateDir}/nzbget.conf";
+  configOpts = concatStringsSep " " (mapAttrsToList (name: value: "-o ${name}=${value}") nixosOpts);
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.nzbget;
-        defaultText = "pkgs.nzbget";
-        description = "The NZBGet package to use";
-      };
+  nixosOpts = {
+    # allows nzbget to run as a "simple" service
+    OutputMode = "loggable";
+    # use journald for logging
+    WriteLog = "none";
+    ErrorTarget = "screen";
+    WarningTarget = "screen";
+    InfoTarget = "screen";
+    DetailTarget = "screen";
+    # required paths
+    ConfigTemplate = "${pkg}/share/nzbget/nzbget.conf";
+    WebDir = "${pkg}/share/nzbget/webui";
+    # nixos handles package updates
+    UpdateCheck = "none";
+  };
 
-      dataDir = mkOption {
-        type = types.str;
-        default = "/var/lib/nzbget";
-        description = "The directory where NZBGet stores its configuration files.";
-      };
+in
+{
+  # interface
 
-      openFirewall = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Open ports in the firewall for the NZBGet web interface
-        '';
-      };
+  options = {
+    services.nzbget = {
+      enable = mkEnableOption "NZBGet";
 
       user = mkOption {
         type = types.str;
@@ -42,15 +44,11 @@ in {
         default = "nzbget";
         description = "Group under which NZBGet runs";
       };
-
-      configFile = mkOption {
-        type = types.str;
-        default = "/var/lib/nzbget/nzbget.conf";
-        description = "Path for NZBGet's config file. (If this doesn't exist, the default config template is copied here.)";
-      };
     };
   };
 
+  # implementation
+
   config = mkIf cfg.enable {
     systemd.services.nzbget = {
       description = "NZBGet Daemon";
@@ -61,50 +59,26 @@ in {
         p7zip
       ];
       preStart = ''
-        cfgtemplate=${cfg.package}/share/nzbget/nzbget.conf
-        if [ ! -f ${cfg.configFile} ]; then
-          echo "${cfg.configFile} not found. Copying default config $cfgtemplate to ${cfg.configFile}"
-          install -m 0700 $cfgtemplate ${cfg.configFile}
-          echo "Setting temporary \$MAINDIR variable in default config required in order to allow nzbget to complete initial start"
-          echo "Remember to change this to a proper value once NZBGet startup has been completed"
-          sed -i -e 's/MainDir=.*/MainDir=\/tmp/g' ${cfg.configFile}
+        if [ ! -f ${configFile} ]; then
+          ${pkgs.coreutils}/bin/install -m 0700 ${pkg}/share/nzbget/nzbget.conf ${configFile}
         fi
       '';
 
-      script = ''
-        args="--daemon --configfile ${cfg.configFile}"
-        # The script in preStart (above) copies nzbget's config template to datadir on first run, containing paths that point to the nzbget derivation installed at the time.
-        # These paths break when nzbget is upgraded & the original derivation is garbage collected. If such broken paths are found in the config file, override them to point to
-        # the currently installed nzbget derivation.
-        cfgfallback () {
-          local hit=`grep -Po "(?<=^$1=).*+" "${cfg.configFile}" | sed 's/[ \t]*$//'` # Strip trailing whitespace
-          ( test $hit && test -e $hit ) || {
-            echo "In ${cfg.configFile}, valid $1 not found; falling back to $1=$2"
-            args+=" -o $1=$2"
-          }
-        }
-        cfgfallback ConfigTemplate ${cfg.package}/share/nzbget/nzbget.conf
-        cfgfallback WebDir ${cfg.package}/share/nzbget/webui
-        ${cfg.package}/bin/nzbget $args
-      '';
-
       serviceConfig = {
-        StateDirectory = dataDir;
-        StateDirectoryMode = "0700";
-        Type = "forking";
+        StateDirectory = "nzbget";
+        StateDirectoryMode = "0750";
         User = cfg.user;
         Group = cfg.group;
-        PermissionsStartOnly = "true";
+        UMask = "0002";
         Restart = "on-failure";
+        ExecStart = "${pkg}/bin/nzbget --server --configfile ${stateDir}/nzbget.conf ${configOpts}";
+        ExecStop = "${pkg}/bin/nzbget --quit";
       };
     };
 
-    networking.firewall = mkIf cfg.openFirewall {
-      allowedTCPPorts = [ 8989 ];
-    };
-
     users.users = mkIf (cfg.user == "nzbget") {
       nzbget = {
+        home = stateDir;
         group = cfg.group;
         uid = config.ids.uids.nzbget;
       };
diff --git a/nixos/modules/services/misc/octoprint.nix b/nixos/modules/services/misc/octoprint.nix
index baa7c3ade52e..52ae25201c35 100644
--- a/nixos/modules/services/misc/octoprint.nix
+++ b/nixos/modules/services/misc/octoprint.nix
@@ -7,7 +7,7 @@ let
   cfg = config.services.octoprint;
 
   baseConfig = {
-    plugins.cura.cura_engine = "${pkgs.curaengine_stable}/bin/CuraEngine";
+    plugins.curalegacy.cura_engine = "${pkgs.curaengine_stable}/bin/CuraEngine";
     server.host = cfg.host;
     server.port = cfg.port;
     webcam.ffmpeg = "${pkgs.ffmpeg.bin}/bin/ffmpeg";
diff --git a/nixos/modules/services/misc/packagekit.nix b/nixos/modules/services/misc/packagekit.nix
index bce21e8acff3..325c4e84e0d8 100644
--- a/nixos/modules/services/misc/packagekit.nix
+++ b/nixos/modules/services/misc/packagekit.nix
@@ -7,18 +7,19 @@ let
   cfg = config.services.packagekit;
 
   packagekitConf = ''
-[Daemon]
-KeepCache=false
-    '';
+    [Daemon]
+    DefaultBackend=${cfg.backend}
+    KeepCache=false
+  '';
 
   vendorConf = ''
-[PackagesNotFound]
-DefaultUrl=https://github.com/NixOS/nixpkgs
-CodecUrl=https://github.com/NixOS/nixpkgs
-HardwareUrl=https://github.com/NixOS/nixpkgs
-FontUrl=https://github.com/NixOS/nixpkgs
-MimeUrl=https://github.com/NixOS/nixpkgs
-      '';
+    [PackagesNotFound]
+    DefaultUrl=https://github.com/NixOS/nixpkgs
+    CodecUrl=https://github.com/NixOS/nixpkgs
+    HardwareUrl=https://github.com/NixOS/nixpkgs
+    FontUrl=https://github.com/NixOS/nixpkgs
+    MimeUrl=https://github.com/NixOS/nixpkgs
+  '';
 
 in
 
@@ -33,26 +34,32 @@ in
           installing software. Software utilizing PackageKit can install
           software regardless of the package manager.
         '';
-    };
 
+      # TODO: integrate with PolicyKit if the nix backend matures to the point
+      # where it will require elevated permissions
+      backend = mkOption {
+        type = types.enum [ "test_nop" ];
+        default = "test_nop";
+        description = ''
+          PackageKit supports multiple different backends and <literal>auto</literal> which
+          should do the right thing.
+          </para>
+          <para>
+          On NixOS however, we do not have a backend compatible with nix 2.0
+          (refer to <link xlink:href="https://github.com/NixOS/nix/issues/233">this issue</link> so we have to force
+          it to <literal>test_nop</literal> for now.
+        '';
+      };
+    };
   };
 
   config = mkIf cfg.enable {
 
-    services.dbus.packages = [ pkgs.packagekit ];
+    services.dbus.packages = with pkgs; [ packagekit ];
 
-    systemd.services.packagekit = {
-      description = "PackageKit Daemon";
-      wantedBy = [ "multi-user.target" ];
-      serviceConfig.ExecStart = "${pkgs.packagekit}/libexec/packagekitd";
-      serviceConfig.User = "root";
-      serviceConfig.BusName = "org.freedesktop.PackageKit";
-      serviceConfig.Type = "dbus";
-    };
+    systemd.packages = with pkgs; [ packagekit ];
 
     environment.etc."PackageKit/PackageKit.conf".text = packagekitConf;
     environment.etc."PackageKit/Vendor.conf".text = vendorConf;
-
   };
-
 }
diff --git a/nixos/modules/services/misc/paperless.nix b/nixos/modules/services/misc/paperless.nix
new file mode 100644
index 000000000000..4e6cd80e2425
--- /dev/null
+++ b/nixos/modules/services/misc/paperless.nix
@@ -0,0 +1,185 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+let
+  cfg = config.services.paperless;
+
+  defaultUser = "paperless";
+
+  manage = cfg.package.withConfig {
+    config = {
+      PAPERLESS_CONSUMPTION_DIR = cfg.consumptionDir;
+      PAPERLESS_INLINE_DOC = "true";
+      PAPERLESS_DISABLE_LOGIN = "true";
+    } // cfg.extraConfig;
+    inherit (cfg) dataDir ocrLanguages;
+    paperlessPkg = cfg.package;
+  };
+in
+{
+  options.services.paperless = {
+    enable = mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = ''
+        Enable Paperless.
+
+        When started, the Paperless database is automatically created if it doesn't
+        exist and updated if the Paperless package has changed.
+        Both tasks are achieved by running a Django migration.
+      '';
+    };
+
+    dataDir = mkOption {
+      type = types.str;
+      default = "/var/lib/paperless";
+      description = "Directory to store the Paperless data.";
+    };
+
+    consumptionDir = mkOption {
+      type = types.str;
+      default = "${cfg.dataDir}/consume";
+      defaultText = "\${dataDir}/consume";
+      description = "Directory from which new documents are imported.";
+    };
+
+    consumptionDirIsPublic = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Whether all users can write to the consumption dir.";
+    };
+
+    ocrLanguages = mkOption {
+      type = with types; nullOr (listOf string);
+      default = null;
+      description = ''
+        Languages available for OCR via Tesseract, specified as
+        <literal>ISO 639-2/T</literal> language codes.
+        If unset, defaults to all available languages.
+      '';
+      example = [ "eng" "spa" "jpn" ];
+    };
+
+    address = mkOption {
+      type = types.str;
+      default = "localhost";
+      description = "Server listening address.";
+    };
+
+    port = mkOption {
+      type = types.int;
+      default = 28981;
+      description = "Server port to listen on.";
+    };
+
+    extraConfig = mkOption {
+      type = types.attrs;
+      default = {};
+      description = ''
+        Extra paperless config options.
+
+        The config values are evaluated as double-quoted Bash string literals.
+
+        See <literal>paperless-src/paperless.conf.example</literal> for available options.
+
+        To enable user authentication, set <literal>PAPERLESS_DISABLE_LOGIN = "false"</literal>
+        and run the shell command <literal>$dataDir/paperless-manage createsuperuser</literal>.
+
+        To define secret options without storing them in /nix/store, use the following pattern:
+        <literal>PAPERLESS_PASSPHRASE = "$(&lt; /etc/my_passphrase_file)"</literal>
+      '';
+      example = literalExample ''
+        {
+          PAPERLESS_OCR_LANGUAGE = "deu";
+        }
+      '';
+    };
+
+    user = mkOption {
+      type = types.str;
+      default = defaultUser;
+      description = "User under which Paperless runs.";
+    };
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.paperless;
+      defaultText = "pkgs.paperless";
+      description = "The Paperless package to use.";
+    };
+
+    manage = mkOption {
+      type = types.package;
+      readOnly = true;
+      default = manage;
+      description = ''
+        A script to manage the Paperless instance.
+        It wraps Django's manage.py and is also available at
+        <literal>$dataDir/manage-paperless</literal>
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' - ${cfg.user} ${cfg.user} - -"
+    ] ++ (optional cfg.consumptionDirIsPublic
+      "d '${cfg.consumptionDir}' 777 ${cfg.user} ${cfg.user} - -"
+      # If the consumption dir is not created here, it's automatically created by
+      # 'manage' with the default permissions.
+    );
+
+    systemd.services.paperless-consumer = {
+      description = "Paperless document consumer";
+      serviceConfig = {
+        User = cfg.user;
+        ExecStart = "${manage} document_consumer";
+        Restart = "always";
+      };
+      after = [ "systemd-tmpfiles-setup.service" ];
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        if [[ $(readlink ${cfg.dataDir}/paperless-manage) != ${manage} ]]; then
+          ln -sf ${manage} ${cfg.dataDir}/paperless-manage
+        fi
+
+        ${manage.setupEnv}
+        # Auto-migrate on first run or if the package has changed
+        versionFile="$PAPERLESS_DBDIR/src-version"
+        if [[ $(cat "$versionFile" 2>/dev/null) != ${cfg.package} ]]; then
+          python $paperlessSrc/manage.py migrate
+          echo ${cfg.package} > "$versionFile"
+        fi
+      '';
+    };
+
+    systemd.services.paperless-server = {
+      description = "Paperless document server";
+      serviceConfig = {
+        User = cfg.user;
+        ExecStart = "${manage} runserver --noreload ${cfg.address}:${toString cfg.port}";
+        Restart = "always";
+      };
+      # Bind to `paperless-consumer` so that the server never runs
+      # during migrations
+      bindsTo = [ "paperless-consumer.service" ];
+      after = [ "paperless-consumer.service" ];
+      wantedBy = [ "multi-user.target" ];
+    };
+
+    users = optionalAttrs (cfg.user == defaultUser) {
+      users = [{
+        name = defaultUser;
+        group = defaultUser;
+        uid = config.ids.uids.paperless;
+        home = cfg.dataDir;
+      }];
+
+      groups = [{
+        name = defaultUser;
+        gid = config.ids.gids.paperless;
+      }];
+    };
+  };
+}
diff --git a/nixos/modules/services/misc/plex.nix b/nixos/modules/services/misc/plex.nix
index fce9b29011f1..7efadf1b9bb1 100644
--- a/nixos/modules/services/misc/plex.nix
+++ b/nixos/modules/services/misc/plex.nix
@@ -10,35 +10,38 @@ in
     services.plex = {
       enable = mkEnableOption "Plex Media Server";
 
-      # FIXME: In order for this config option to work, symlinks in the Plex
-      # package in the Nix store have to be changed to point to this directory.
       dataDir = mkOption {
         type = types.str;
         default = "/var/lib/plex";
-        description = "The directory where Plex stores its data files.";
+        description = ''
+          The directory where Plex stores its data files.
+        '';
       };
 
       openFirewall = mkOption {
         type = types.bool;
         default = false;
         description = ''
-          Open ports in the firewall for the media server
+          Open ports in the firewall for the media server.
         '';
       };
 
       user = mkOption {
         type = types.str;
         default = "plex";
-        description = "User account under which Plex runs.";
+        description = ''
+          User account under which Plex runs.
+        '';
       };
 
       group = mkOption {
         type = types.str;
         default = "plex";
-        description = "Group under which Plex runs.";
+        description = ''
+          Group under which Plex runs.
+        '';
       };
 
-
       managePlugins = mkOption {
         type = types.bool;
         default = true;
@@ -80,73 +83,48 @@ in
       description = "Plex Media Server";
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
-      preStart = ''
-        test -d "${cfg.dataDir}/Plex Media Server" || {
-          echo "Creating initial Plex data directory in \"${cfg.dataDir}\"."
-          mkdir -p "${cfg.dataDir}/Plex Media Server"
-          chown -R ${cfg.user}:${cfg.group} "${cfg.dataDir}"
-        }
-
-        # Copy the database skeleton files to /var/lib/plex/.skeleton
-        # See the the Nix expression for Plex's package for more information on
-        # why this is done.
-        install --owner ${cfg.user} --group ${cfg.group} -d "${cfg.dataDir}/.skeleton"
-        for db in "com.plexapp.plugins.library.db"; do
-            if [ ! -e  "${cfg.dataDir}/.skeleton/$db" ]; then
-              cp "${cfg.package}/usr/lib/plexmediaserver/Resources/base_$db" "${cfg.dataDir}/.skeleton/$db"
-            fi
-            chmod u+w "${cfg.dataDir}/.skeleton/$db"
-            chown ${cfg.user}:${cfg.group} "${cfg.dataDir}/.skeleton/$db"
-        done
-
-        # If managePlugins is enabled, setup symlinks for plugins.
-        ${optionalString cfg.managePlugins ''
-          echo "Preparing plugin directory."
-          PLUGINDIR="${cfg.dataDir}/Plex Media Server/Plug-ins"
-          test -d "$PLUGINDIR" || {
-            mkdir -p "$PLUGINDIR";
-            chown ${cfg.user}:${cfg.group} "$PLUGINDIR";
-          }
-
-          echo "Removing old symlinks."
-          # First, remove all of the symlinks in the directory.
-          for f in `ls "$PLUGINDIR/"`; do
-            if [[ -L "$PLUGINDIR/$f" ]]; then
-              echo "Removing plugin symlink $PLUGINDIR/$f."
-              rm "$PLUGINDIR/$f"
-            fi
-          done
-
-          echo "Symlinking plugins."
-          for path in ${toString cfg.extraPlugins}; do
-            dest="$PLUGINDIR/$(basename $path)"
-            if [[ ! -d "$path" ]]; then
-              echo "Error symlinking plugin from $path: no such directory."
-            elif [[ -d "$dest" || -L "$dest" ]]; then
-              echo "Error symlinking plugin from $path to $dest: file or directory already exists."
-            else
-              echo "Symlinking plugin at $path..."
-              ln -s "$path" "$dest"
-            fi
-          done
-        ''}
-     '';
+
       serviceConfig = {
         Type = "simple";
         User = cfg.user;
         Group = cfg.group;
-        PermissionsStartOnly = "true";
-        ExecStart = "\"${cfg.package}/usr/lib/plexmediaserver/Plex Media Server\"";
+
+        # Run the pre-start script with full permissions (the "!" prefix) so it
+        # can create the data directory if necessary.
+        ExecStartPre = let
+          preStartScript = pkgs.writeScript "plex-run-prestart" ''
+            #!${pkgs.bash}/bin/bash
+
+            # Create data directory if it doesn't exist
+            if ! test -d "$PLEX_DATADIR"; then
+              echo "Creating initial Plex data directory in: $PLEX_DATADIR"
+              install -d -m 0755 -o "${cfg.user}" -g "${cfg.group}" "$PLEX_DATADIR"
+            fi
+         '';
+        in
+          "!${preStartScript}";
+
+        ExecStart = "${cfg.package}/bin/plexmediaserver";
         KillSignal = "SIGQUIT";
         Restart = "on-failure";
       };
+
       environment = {
-        PLEX_MEDIA_SERVER_APPLICATION_SUPPORT_DIR=cfg.dataDir;
-        PLEX_MEDIA_SERVER_HOME="${cfg.package}/usr/lib/plexmediaserver";
+        # Configuration for our FHS userenv script
+        PLEX_DATADIR=cfg.dataDir;
+        PLEX_PLUGINS=concatMapStringsSep ":" builtins.toString cfg.extraPlugins;
+
+        # The following variables should be set by the FHS userenv script:
+        #   PLEX_MEDIA_SERVER_APPLICATION_SUPPORT_DIR
+        #   PLEX_MEDIA_SERVER_HOME
+
+        # Allow access to GPU acceleration; the Plex LD_LIBRARY_PATH is added
+        # by the FHS userenv script.
+        LD_LIBRARY_PATH="/run/opengl-driver/lib";
+
         PLEX_MEDIA_SERVER_MAX_PLUGIN_PROCS="6";
         PLEX_MEDIA_SERVER_TMPDIR="/tmp";
         PLEX_MEDIA_SERVER_USE_SYSLOG="true";
-        LD_LIBRARY_PATH="/run/opengl-driver/lib:${cfg.package}/usr/lib/plexmediaserver/lib";
         LC_ALL="en_US.UTF-8";
         LANG="en_US.UTF-8";
       };
diff --git a/nixos/modules/services/misc/radarr.nix b/nixos/modules/services/misc/radarr.nix
index 9ab26d848325..74444e24043f 100644
--- a/nixos/modules/services/misc/radarr.nix
+++ b/nixos/modules/services/misc/radarr.nix
@@ -38,24 +38,19 @@ in
   };
 
   config = mkIf cfg.enable {
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' 0700 ${cfg.user} ${cfg.group} - -"
+    ];
+
     systemd.services.radarr = {
       description = "Radarr";
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
-      preStart = ''
-        test -d ${cfg.dataDir} || {
-          echo "Creating radarr data directory in ${cfg.dataDir}"
-          mkdir -p ${cfg.dataDir}
-        }
-        chown -R ${cfg.user}:${cfg.group} ${cfg.dataDir}
-        chmod 0700 ${cfg.dataDir}
-      '';
 
       serviceConfig = {
         Type = "simple";
         User = cfg.user;
         Group = cfg.group;
-        PermissionsStartOnly = "true";
         ExecStart = "${pkgs.radarr}/bin/Radarr -nobrowser -data='${cfg.dataDir}'";
         Restart = "on-failure";
       };
diff --git a/nixos/modules/services/misc/sonarr.nix b/nixos/modules/services/misc/sonarr.nix
index a99445a268d7..77c7f0582d0b 100644
--- a/nixos/modules/services/misc/sonarr.nix
+++ b/nixos/modules/services/misc/sonarr.nix
@@ -39,24 +39,19 @@ in
   };
 
   config = mkIf cfg.enable {
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' 0700 ${cfg.user} ${cfg.group} - -"
+    ];
+
     systemd.services.sonarr = {
       description = "Sonarr";
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
-      preStart = ''
-        test -d ${cfg.dataDir} || {
-          echo "Creating sonarr data directory in ${cfg.dataDir}"
-          mkdir -p ${cfg.dataDir}
-        }
-        chown -R ${cfg.user}:${cfg.group} ${cfg.dataDir}
-        chmod 0700 ${cfg.dataDir}
-      '';
 
       serviceConfig = {
         Type = "simple";
         User = cfg.user;
         Group = cfg.group;
-        PermissionsStartOnly = "true";
         ExecStart = "${pkgs.sonarr}/bin/NzbDrone -nobrowser -data='${cfg.dataDir}'";
         Restart = "on-failure";
       };
diff --git a/nixos/modules/services/misc/sssd.nix b/nixos/modules/services/misc/sssd.nix
index fe472a6c68e5..6b64045dde88 100644
--- a/nixos/modules/services/misc/sssd.nix
+++ b/nixos/modules/services/misc/sssd.nix
@@ -6,7 +6,7 @@ let
 in {
   options = {
     services.sssd = {
-      enable = mkEnableOption "the System Security Services Daemon.";
+      enable = mkEnableOption "the System Security Services Daemon";
 
       config = mkOption {
         type = types.lines;
diff --git a/nixos/modules/services/misc/taskserver/default.nix b/nixos/modules/services/misc/taskserver/default.nix
index 483bc99ad946..07dbee69db0c 100644
--- a/nixos/modules/services/misc/taskserver/default.nix
+++ b/nixos/modules/services/misc/taskserver/default.nix
@@ -48,7 +48,7 @@ let
     type = types.nullOr types.int;
     default = null;
     example = 365;
-    apply = val: if isNull val then -1 else val;
+    apply = val: if val == null then -1 else val;
     description = mkAutoDesc ''
       The expiration time of ${desc} in days or <literal>null</literal> for no
       expiration time.
@@ -82,7 +82,7 @@ let
          then attrByPath newPath (notFound newPath) cfg.pki.manual
          else findPkiDefinitions newPath val;
     in flatten (mapAttrsToList mkSublist attrs);
-  in all isNull (findPkiDefinitions [] manualPkiOptions);
+  in all (x: x == null) (findPkiDefinitions [] manualPkiOptions);
 
   orgOptions = { ... }: {
     options.users = mkOption {
diff --git a/nixos/modules/services/misc/zoneminder.nix b/nixos/modules/services/misc/zoneminder.nix
index 2bd2f3c7cc08..9c555e8031c4 100644
--- a/nixos/modules/services/misc/zoneminder.nix
+++ b/nixos/modules/services/misc/zoneminder.nix
@@ -17,7 +17,7 @@ let
   defaultDir = "/var/lib/${user}";
   home = if useCustomDir then cfg.storageDir else defaultDir;
 
-  useCustomDir = !(builtins.isNull cfg.storageDir);
+  useCustomDir = cfg.storageDir != null;
 
   socket = "/run/phpfpm/${dirName}.sock";
 
@@ -229,6 +229,8 @@ in {
               location / {
                 try_files $uri $uri/ /index.php?$args =404;
 
+                rewrite ^/skins/.*/css/fonts/(.*)$ /fonts/$1 permanent;
+
                 location ~ /api/(css|img|ico) {
                   rewrite ^/api(.+)$ /api/app/webroot/$1 break;
                   try_files $uri $uri/ =404;
diff --git a/nixos/modules/services/misc/zookeeper.nix b/nixos/modules/services/misc/zookeeper.nix
index cb7cc97d5a5c..50c84e3c6b80 100644
--- a/nixos/modules/services/misc/zookeeper.nix
+++ b/nixos/modules/services/misc/zookeeper.nix
@@ -119,6 +119,10 @@ in {
   config = mkIf cfg.enable {
     environment.systemPackages = [cfg.package];
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' 0700 zookeeper - - -"
+    ];
+
     systemd.services.zookeeper = {
       description = "Zookeeper Daemon";
       wantedBy = [ "multi-user.target" ];
@@ -135,11 +139,8 @@ in {
             ${configDir}/zoo.cfg
         '';
         User = "zookeeper";
-        PermissionsStartOnly = true;
       };
       preStart = ''
-        mkdir -m 0700 -p ${cfg.dataDir}
-        if [ "$(id -u)" = 0 ]; then chown zookeeper ${cfg.dataDir}; fi
         echo "${toString cfg.id}" > ${cfg.dataDir}/myid
       '';
     };
diff --git a/nixos/modules/services/monitoring/collectd.nix b/nixos/modules/services/monitoring/collectd.nix
index 45e3312c0f44..6a4c678eb21f 100644
--- a/nixos/modules/services/monitoring/collectd.nix
+++ b/nixos/modules/services/monitoring/collectd.nix
@@ -79,6 +79,10 @@ in {
   };
 
   config = mkIf cfg.enable {
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' - ${cfg.user} - - -"
+    ];
+
     systemd.services.collectd = {
       description = "Collectd Monitoring Agent";
       after = [ "network.target" ];
@@ -87,16 +91,9 @@ in {
       serviceConfig = {
         ExecStart = "${cfg.package}/sbin/collectd -C ${conf} -f";
         User = cfg.user;
-        PermissionsStartOnly = true;
         Restart = "on-failure";
         RestartSec = 3;
       };
-
-      preStart = ''
-        mkdir -p "${cfg.dataDir}"
-        chmod 755 "${cfg.dataDir}"
-        chown -R ${cfg.user} "${cfg.dataDir}"
-      '';
     };
 
     users.users = optional (cfg.user == "collectd") {
diff --git a/nixos/modules/services/monitoring/grafana-reporter.nix b/nixos/modules/services/monitoring/grafana-reporter.nix
index 149026d20188..827cf6322cfd 100644
--- a/nixos/modules/services/monitoring/grafana-reporter.nix
+++ b/nixos/modules/services/monitoring/grafana-reporter.nix
@@ -52,7 +52,7 @@ in {
       wantedBy = ["multi-user.target"];
       after = ["network.target"];
       serviceConfig = let
-        args = lib.concatSepString " " [
+        args = lib.concatStringsSep " " [
           "-proto ${cfg.grafana.protocol}://"
           "-ip ${cfg.grafana.addr}:${toString cfg.grafana.port}"
           "-port :${toString cfg.port}"
diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix
index 85879cfe0b33..5d3f2e6ac28f 100644
--- a/nixos/modules/services/monitoring/grafana.nix
+++ b/nixos/modules/services/monitoring/grafana.nix
@@ -177,7 +177,7 @@ let
       folder = mkOption {
         type = types.str;
         default = "";
-        description = "Add dashboards to the speciied folder";
+        description = "Add dashboards to the specified folder";
       };
       type = mkOption {
         type = types.str;
diff --git a/nixos/modules/services/monitoring/graphite.nix b/nixos/modules/services/monitoring/graphite.nix
index f59bc56962b1..d6473220c140 100644
--- a/nixos/modules/services/monitoring/graphite.nix
+++ b/nixos/modules/services/monitoring/graphite.nix
@@ -19,13 +19,13 @@ let
 
   graphiteLocalSettings = pkgs.writeText "graphite_local_settings.py" (
     "STATIC_ROOT = '${staticDir}'\n" +
-    optionalString (! isNull config.time.timeZone) "TIME_ZONE = '${config.time.timeZone}'\n"
+    optionalString (config.time.timeZone != null) "TIME_ZONE = '${config.time.timeZone}'\n"
     + cfg.web.extraConfig
   );
 
   graphiteApiConfig = pkgs.writeText "graphite-api.yaml" ''
     search_index: ${dataDir}/index
-    ${optionalString (!isNull config.time.timeZone) ''time_zone: ${config.time.timeZone}''}
+    ${optionalString (config.time.timeZone != null) ''time_zone: ${config.time.timeZone}''}
     ${optionalString (cfg.api.finders != []) ''finders:''}
     ${concatMapStringsSep "\n" (f: "  - " + f.moduleName) cfg.api.finders}
     ${optionalString (cfg.api.functions != []) ''functions:''}
@@ -123,7 +123,7 @@ in {
           graphite carbon.
 
           For more information visit
-          <link xlink:href="http://graphite-api.readthedocs.org/en/latest/"/>
+          <link xlink:href="https://graphite-api.readthedocs.org/en/latest/"/>
         '';
         default = false;
         type = types.bool;
diff --git a/nixos/modules/services/monitoring/munin.nix b/nixos/modules/services/monitoring/munin.nix
index f67986327245..ffe223fedbe1 100644
--- a/nixos/modules/services/monitoring/munin.nix
+++ b/nixos/modules/services/monitoring/munin.nix
@@ -8,7 +8,7 @@
 # spawn-fcgi -s /run/munin/fastcgi-graph.sock -U www-data   -u munin -g munin /usr/lib/munin/cgi/munin-cgi-graph
 # spawn-fcgi -s /run/munin/fastcgi-html.sock  -U www-data   -u munin -g munin /usr/lib/munin/cgi/munin-cgi-html
 # https://paste.sh/vofcctHP#-KbDSXVeWoifYncZmLfZzgum
-# nginx http://munin.readthedocs.org/en/latest/example/webserver/nginx.html
+# nginx https://munin.readthedocs.org/en/latest/example/webserver/nginx.html
 
 
 with lib;
diff --git a/nixos/modules/services/monitoring/prometheus/default.nix b/nixos/modules/services/monitoring/prometheus/default.nix
index 0d73551dc071..d8384e0d35b3 100644
--- a/nixos/modules/services/monitoring/prometheus/default.nix
+++ b/nixos/modules/services/monitoring/prometheus/default.nix
@@ -22,9 +22,6 @@ let
   workingDir  = stateDirBase + stateDir;
   workingDir2 = stateDirBase + cfg2.stateDir;
 
-  # Get a submodule without any embedded metadata:
-  _filter = x: filterAttrs (k: v: k != "_module") x;
-
   # a wrapper that verifies that the configuration is valid
   promtoolCheck = what: name: file: pkgs.runCommand "${name}-${what}-checked"
     { buildInputs = [ cfg.package ]; } ''
@@ -50,11 +47,11 @@ let
 
   # This becomes the main config file for Prometheus 1
   promConfig = {
-    global = cfg.globalConfig;
+    global = filterValidPrometheus cfg.globalConfig;
     rule_files = map (promtoolCheck "check-rules" "rules") (cfg.ruleFiles ++ [
       (pkgs.writeText "prometheus.rules" (concatStringsSep "\n" cfg.rules))
     ]);
-    scrape_configs = cfg.scrapeConfigs;
+    scrape_configs = filterValidPrometheus cfg.scrapeConfigs;
   };
 
   generatedPrometheusYml = writePrettyJSON "prometheus.yml" promConfig;
@@ -77,11 +74,11 @@ let
 
   # This becomes the main config file for Prometheus 2
   promConfig2 = {
-    global = cfg2.globalConfig;
+    global = filterValidPrometheus cfg2.globalConfig;
     rule_files = map (prom2toolCheck "check rules" "rules") (cfg2.ruleFiles ++ [
       (pkgs.writeText "prometheus.rules" (concatStringsSep "\n" cfg2.rules))
     ]);
-    scrape_configs = cfg2.scrapeConfigs;
+    scrape_configs = filterValidPrometheus cfg2.scrapeConfigs;
     alerting = optionalAttrs (cfg2.alertmanagerURL != []) {
       alertmanagers = [{
         static_configs = [{
@@ -108,41 +105,52 @@ let
   ] ++
   optional (cfg2.webExternalUrl != null) "--web.external-url=${cfg2.webExternalUrl}";
 
+  filterValidPrometheus = filterAttrsListRecursive (n: v: !(n == "_module" || v == null));
+  filterAttrsListRecursive = pred: x:
+    if isAttrs x then
+      listToAttrs (
+        concatMap (name:
+          let v = x.${name}; in
+          if pred name v then [
+            (nameValuePair name (filterAttrsListRecursive pred v))
+          ] else []
+        ) (attrNames x)
+      )
+    else if isList x then
+      map (filterAttrsListRecursive pred) x
+    else x;
+
+  mkDefOpt = type : defaultStr : description : mkOpt type (description + ''
+
+    Defaults to <literal>${defaultStr}</literal> in prometheus
+    when set to <literal>null</literal>.
+  '');
+
+  mkOpt = type : description : mkOption {
+    type = types.nullOr type;
+    default = null;
+    inherit description;
+  };
+
   promTypes.globalConfig = types.submodule {
     options = {
-      scrape_interval = mkOption {
-        type = types.str;
-        default = "1m";
-        description = ''
-          How frequently to scrape targets by default.
-        '';
-      };
-
-      scrape_timeout = mkOption {
-        type = types.str;
-        default = "10s";
-        description = ''
-          How long until a scrape request times out.
-        '';
-      };
-
-      evaluation_interval = mkOption {
-        type = types.str;
-        default = "1m";
-        description = ''
-          How frequently to evaluate rules by default.
-        '';
-      };
-
-      external_labels = mkOption {
-        type = types.attrsOf types.str;
-        description = ''
-          The labels to add to any time series or alerts when
-          communicating with external systems (federation, remote
-          storage, Alertmanager).
-        '';
-        default = {};
-      };
+      scrape_interval = mkDefOpt types.str "1m" ''
+        How frequently to scrape targets by default.
+      '';
+
+      scrape_timeout = mkDefOpt types.str "10s" ''
+        How long until a scrape request times out.
+      '';
+
+      evaluation_interval = mkDefOpt types.str "1m" ''
+        How frequently to evaluate rules by default.
+      '';
+
+      external_labels = mkOpt (types.attrsOf types.str) ''
+        The labels to add to any time series or alerts when
+        communicating with external systems (federation, remote
+        storage, Alertmanager).
+      '';
     };
   };
 
@@ -154,137 +162,127 @@ let
           The job name assigned to scraped metrics by default.
         '';
       };
-      scrape_interval = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = ''
-          How frequently to scrape targets from this job. Defaults to the
-          globally configured default.
-        '';
-      };
-      scrape_timeout = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = ''
-          Per-target timeout when scraping this job. Defaults to the
-          globally configured default.
-        '';
-      };
-      metrics_path = mkOption {
-        type = types.str;
-        default = "/metrics";
-        description = ''
-          The HTTP resource path on which to fetch metrics from targets.
-        '';
-      };
-      honor_labels = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Controls how Prometheus handles conflicts between labels
-          that are already present in scraped data and labels that
-          Prometheus would attach server-side ("job" and "instance"
-          labels, manually configured target labels, and labels
-          generated by service discovery implementations).
-
-          If honor_labels is set to "true", label conflicts are
-          resolved by keeping label values from the scraped data and
-          ignoring the conflicting server-side labels.
-
-          If honor_labels is set to "false", label conflicts are
-          resolved by renaming conflicting labels in the scraped data
-          to "exported_&lt;original-label&gt;" (for example
-          "exported_instance", "exported_job") and then attaching
-          server-side labels. This is useful for use cases such as
-          federation, where all labels specified in the target should
-          be preserved.
-        '';
-      };
-      scheme = mkOption {
-        type = types.enum ["http" "https"];
-        default = "http";
-        description = ''
-          The URL scheme with which to fetch metrics from targets.
-        '';
-      };
-      params = mkOption {
-        type = types.attrsOf (types.listOf types.str);
-        default = {};
-        description = ''
-          Optional HTTP URL parameters.
-        '';
-      };
-      basic_auth = mkOption {
-        type = types.nullOr (types.submodule {
-          options = {
-            username = mkOption {
-              type = types.str;
-              description = ''
-                HTTP username
-              '';
-            };
-            password = mkOption {
-              type = types.str;
-              description = ''
-                HTTP password
-              '';
-            };
+      scrape_interval = mkOpt types.str ''
+        How frequently to scrape targets from this job. Defaults to the
+        globally configured default.
+      '';
+
+      scrape_timeout = mkOpt types.str ''
+        Per-target timeout when scraping this job. Defaults to the
+        globally configured default.
+      '';
+
+      metrics_path = mkDefOpt types.str "/metrics" ''
+        The HTTP resource path on which to fetch metrics from targets.
+      '';
+
+      honor_labels = mkDefOpt types.bool "false" ''
+        Controls how Prometheus handles conflicts between labels
+        that are already present in scraped data and labels that
+        Prometheus would attach server-side ("job" and "instance"
+        labels, manually configured target labels, and labels
+        generated by service discovery implementations).
+
+        If honor_labels is set to "true", label conflicts are
+        resolved by keeping label values from the scraped data and
+        ignoring the conflicting server-side labels.
+
+        If honor_labels is set to "false", label conflicts are
+        resolved by renaming conflicting labels in the scraped data
+        to "exported_&lt;original-label&gt;" (for example
+        "exported_instance", "exported_job") and then attaching
+        server-side labels. This is useful for use cases such as
+        federation, where all labels specified in the target should
+        be preserved.
+      '';
+
+      honor_timestamps = mkDefOpt types.bool "true" ''
+        honor_timestamps controls whether Prometheus respects the timestamps present
+        in scraped data.
+
+        If honor_timestamps is set to <literal>true</literal>, the timestamps of the metrics exposed
+        by the target will be used.
+
+        If honor_timestamps is set to <literal>false</literal>, the timestamps of the metrics exposed
+        by the target will be ignored.
+      '';
+
+      scheme = mkDefOpt (types.enum ["http" "https"]) "http" ''
+        The URL scheme with which to fetch metrics from targets.
+      '';
+
+      params = mkOpt (types.attrsOf (types.listOf types.str)) ''
+        Optional HTTP URL parameters.
+      '';
+
+      basic_auth = mkOpt (types.submodule {
+        options = {
+          username = mkOption {
+            type = types.str;
+            description = ''
+              HTTP username
+            '';
           };
-        });
-        default = null;
-        apply = x: mapNullable _filter x;
-        description = ''
-          Optional http login credentials for metrics scraping.
-        '';
-      };
-      tls_config = mkOption {
-        type = types.nullOr promTypes.tls_config;
-        default = null;
-        apply = x: mapNullable _filter x;
-        description = ''
-          Configures the scrape request's TLS settings.
-        '';
-      };
-      dns_sd_configs = mkOption {
-        type = types.listOf promTypes.dns_sd_config;
-        default = [];
-        apply = x: map _filter x;
-        description = ''
-          List of DNS service discovery configurations.
-        '';
-      };
-      consul_sd_configs = mkOption {
-        type = types.listOf promTypes.consul_sd_config;
-        default = [];
-        apply = x: map _filter x;
-        description = ''
-          List of Consul service discovery configurations.
-        '';
-      };
-      file_sd_configs = mkOption {
-        type = types.listOf promTypes.file_sd_config;
-        default = [];
-        apply = x: map _filter x;
-        description = ''
-          List of file service discovery configurations.
-        '';
-      };
-      static_configs = mkOption {
-        type = types.listOf promTypes.static_config;
-        default = [];
-        apply = x: map _filter x;
-        description = ''
-          List of labeled target groups for this job.
-        '';
-      };
-      relabel_configs = mkOption {
-        type = types.listOf promTypes.relabel_config;
-        default = [];
-        apply = x: map _filter x;
-        description = ''
-          List of relabel configurations.
-        '';
-      };
+          password = mkOption {
+            type = types.str;
+            description = ''
+              HTTP password
+            '';
+          };
+        };
+      }) ''
+        Optional http login credentials for metrics scraping.
+      '';
+
+      bearer_token = mkOpt types.str ''
+        Sets the `Authorization` header on every scrape request with
+        the configured bearer token. It is mutually exclusive with
+        <option>bearer_token_file</option>.
+      '';
+
+      bearer_token_file = mkOpt types.str ''
+        Sets the `Authorization` header on every scrape request with
+        the bearer token read from the configured file. It is mutually
+        exclusive with <option>bearer_token</option>.
+      '';
+
+      tls_config = mkOpt promTypes.tls_config ''
+        Configures the scrape request's TLS settings.
+      '';
+
+      proxy_url = mkOpt types.str ''
+        Optional proxy URL.
+      '';
+
+      ec2_sd_configs = mkOpt (types.listOf promTypes.ec2_sd_config) ''
+        List of EC2 service discovery configurations.
+      '';
+
+      dns_sd_configs = mkOpt (types.listOf promTypes.dns_sd_config) ''
+        List of DNS service discovery configurations.
+      '';
+
+      consul_sd_configs = mkOpt (types.listOf promTypes.consul_sd_config) ''
+        List of Consul service discovery configurations.
+      '';
+
+      file_sd_configs = mkOpt (types.listOf promTypes.file_sd_config) ''
+        List of file service discovery configurations.
+      '';
+
+      static_configs = mkOpt (types.listOf promTypes.static_config) ''
+        List of labeled target groups for this job.
+      '';
+
+      relabel_configs = mkOpt (types.listOf promTypes.relabel_config) ''
+        List of relabel configurations.
+      '';
+
+      sample_limit = mkDefOpt types.int "0" ''
+        Per-scrape limit on number of scraped samples that will be accepted.
+        If more than this number of samples are present after metric relabelling
+        the entire scrape will be treated as failed. 0 means no limit.
+      '';
     };
   };
 
@@ -306,64 +304,137 @@ let
     };
   };
 
-  promTypes.dns_sd_config = types.submodule {
+  promTypes.ec2_sd_config = types.submodule {
     options = {
-      names = mkOption {
-        type = types.listOf types.str;
-        description = ''
-          A list of DNS SRV record names to be queried.
-        '';
-      };
-      refresh_interval = mkOption {
+      region = mkOption {
         type = types.str;
-        default = "30s";
         description = ''
-          The time after which the provided names are refreshed.
+          The AWS Region.
         '';
       };
+      endpoint = mkOpt types.str ''
+        Custom endpoint to be used.
+      '';
+
+      access_key = mkOpt types.str ''
+        The AWS API key id. If blank, the environment variable
+        <literal>AWS_ACCESS_KEY_ID</literal> is used.
+      '';
+
+      secret_key = mkOpt types.str ''
+        The AWS API key secret. If blank, the environment variable
+         <literal>AWS_SECRET_ACCESS_KEY</literal> is used.
+      '';
+
+      profile = mkOpt  types.str ''
+        Named AWS profile used to connect to the API.
+      '';
+
+      role_arn = mkOpt types.str ''
+        AWS Role ARN, an alternative to using AWS API keys.
+      '';
+
+      refresh_interval = mkDefOpt types.str "60s" ''
+        Refresh interval to re-read the instance list.
+      '';
+
+      port = mkDefOpt types.int "80" ''
+        The port to scrape metrics from. If using the public IP
+        address, this must instead be specified in the relabeling
+        rule.
+      '';
+
+      filters = mkOpt (types.listOf promTypes.filter) ''
+        Filters can be used optionally to filter the instance list by other criteria.
+      '';
     };
   };
 
-  promTypes.consul_sd_config = types.submodule {
+  promTypes.filter = types.submodule {
     options = {
-      server = mkOption {
+      name = mkOption {
         type = types.str;
-        description = "Consul server to query.";
-      };
-      token = mkOption {
-        type = types.nullOr types.str;
-        description = "Consul token";
-      };
-      datacenter = mkOption {
-        type = types.nullOr types.str;
-        description = "Consul datacenter";
-      };
-      scheme = mkOption {
-        type = types.nullOr types.str;
-        description = "Consul scheme";
-      };
-      username = mkOption {
-        type = types.nullOr types.str;
-        description = "Consul username";
-      };
-      password = mkOption {
-        type = types.nullOr types.str;
-        description = "Consul password";
+        description = ''
+          See <link xlink:href="https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeInstances.html">this list</link>
+          for the available filters.
+        '';
       };
 
-      services = mkOption {
+      value = mkOption {
         type = types.listOf types.str;
+        default = [];
         description = ''
-          A list of services for which targets are retrieved.
+          Value of the filter.
         '';
       };
-      tag_separator = mkOption {
-        type = types.str;
-        default = ",";
+    };
+  };
+
+  promTypes.dns_sd_config = types.submodule {
+    options = {
+      names = mkOption {
+        type = types.listOf types.str;
         description = ''
-          The string by which Consul tags are joined into the tag label.
+          A list of DNS SRV record names to be queried.
         '';
       };
+
+      refresh_interval = mkDefOpt types.str "30s" ''
+        The time after which the provided names are refreshed.
+      '';
+    };
+  };
+
+  promTypes.consul_sd_config = types.submodule {
+    options = {
+      server = mkDefOpt types.str "localhost:8500" ''
+        Consul server to query.
+      '';
+
+      token = mkOpt types.str "Consul token";
+
+      datacenter = mkOpt types.str "Consul datacenter";
+
+      scheme = mkDefOpt types.str "http" "Consul scheme";
+
+      username = mkOpt types.str "Consul username";
+
+      password = mkOpt types.str "Consul password";
+
+      tls_config = mkOpt promTypes.tls_config ''
+        Configures the Consul request's TLS settings.
+      '';
+
+      services = mkOpt (types.listOf types.str) ''
+        A list of services for which targets are retrieved.
+      '';
+
+      tags = mkOpt (types.listOf types.str) ''
+        An optional list of tags used to filter nodes for a given
+        service. Services must contain all tags in the list.
+      '';
+
+      node_meta = mkOpt (types.attrsOf types.str) ''
+        Node metadata used to filter nodes for a given service.
+      '';
+
+      tag_separator = mkDefOpt types.str "," ''
+        The string by which Consul tags are joined into the tag label.
+      '';
+
+      allow_stale = mkOpt types.bool ''
+        Allow stale Consul results
+        (see <link xlink:href="https://www.consul.io/api/index.html#consistency-modes"/>).
+
+        Will reduce load on Consul.
+      '';
+
+      refresh_interval = mkDefOpt types.str "30s" ''
+        The time after which the provided names are refreshed.
+
+        On large setup it might be a good idea to increase this value
+        because the catalog will change all the time.
+      '';
     };
   };
 
@@ -375,112 +446,77 @@ let
           Patterns for files from which target groups are extracted. Refer
           to the Prometheus documentation for permitted filename patterns
           and formats.
-
-        '';
-      };
-      refresh_interval = mkOption {
-        type = types.str;
-        default = "30s";
-        description = ''
-          Refresh interval to re-read the files.
         '';
       };
+
+      refresh_interval = mkDefOpt types.str "5m" ''
+        Refresh interval to re-read the files.
+      '';
     };
   };
 
   promTypes.relabel_config = types.submodule {
     options = {
-      source_labels = mkOption {
-        type = with types; nullOr (listOf str);
-        default = null;
-        description = ''
-          The source labels select values from existing labels. Their content
-          is concatenated using the configured separator and matched against
-          the configured regular expression.
-        '';
-      };
-      separator = mkOption {
-        type = types.str;
-        default = ";";
-        description = ''
-          Separator placed between concatenated source label values.
-        '';
-      };
-      target_label = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = ''
-          Label to which the resulting value is written in a replace action.
-          It is mandatory for replace actions.
-        '';
-      };
-      regex = mkOption {
-        type = types.str;
-        default = "(.*)";
-        description = ''
-          Regular expression against which the extracted value is matched.
-        '';
-      };
-      replacement = mkOption {
-        type = types.str;
-        default = "$1";
-        description = ''
-          Replacement value against which a regex replace is performed if the
-          regular expression matches.
-        '';
-      };
-      action = mkOption {
-        type = types.enum ["replace" "keep" "drop"];
-        default = "replace";
-        description = ''
-          Action to perform based on regex matching.
-        '';
-      };
+      source_labels = mkOpt (types.listOf types.str) ''
+        The source labels select values from existing labels. Their content
+        is concatenated using the configured separator and matched against
+        the configured regular expression.
+      '';
+
+      separator = mkDefOpt types.str ";" ''
+        Separator placed between concatenated source label values.
+      '';
+
+      target_label = mkOpt types.str ''
+        Label to which the resulting value is written in a replace action.
+        It is mandatory for replace actions.
+      '';
+
+      regex = mkDefOpt types.str "(.*)" ''
+        Regular expression against which the extracted value is matched.
+      '';
+
+      modulus = mkOpt types.int ''
+        Modulus to take of the hash of the source label values.
+      '';
+
+      replacement = mkDefOpt types.str "$1" ''
+        Replacement value against which a regex replace is performed if the
+        regular expression matches.
+      '';
+
+      action = mkDefOpt (types.enum ["replace" "keep" "drop"]) "replace" ''
+        Action to perform based on regex matching.
+      '';
+
     };
   };
 
   promTypes.tls_config = types.submodule {
     options = {
-      ca_file = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = ''
-          CA certificate to validate API server certificate with.
-        '';
-      };
-      cert_file = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = ''
-          Certificate file for client cert authentication to the server.
-        '';
-      };
-      key_file = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = ''
-          Key file for client cert authentication to the server.
-        '';
-      };
-      server_name = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = ''
-          ServerName extension to indicate the name of the server.
-          http://tools.ietf.org/html/rfc4366#section-3.1
-        '';
-      };
-      insecure_skip_verify = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Disable validation of the server certificate.
-        '';
-      };
+      ca_file = mkOpt types.str ''
+        CA certificate to validate API server certificate with.
+      '';
+
+      cert_file = mkOpt types.str ''
+        Certificate file for client cert authentication to the server.
+      '';
+
+      key_file = mkOpt types.str ''
+        Key file for client cert authentication to the server.
+      '';
+
+      server_name = mkOpt types.str ''
+        ServerName extension to indicate the name of the server.
+        http://tools.ietf.org/html/rfc4366#section-3.1
+      '';
+
+      insecure_skip_verify = mkOpt types.bool ''
+        Disable validation of the server certificate.
+      '';
     };
   };
 
- 
 in {
   options = {
     services.prometheus = {
@@ -550,7 +586,6 @@ in {
       globalConfig = mkOption {
         type = promTypes.globalConfig;
         default = {};
-        apply = _filter;
         description = ''
           Parameters that are valid in all  configuration contexts. They
           also serve as defaults for other configuration sections
@@ -576,7 +611,6 @@ in {
       scrapeConfigs = mkOption {
         type = types.listOf promTypes.scrape_config;
         default = [];
-        apply = x: map _filter x;
         description = ''
           A list of scrape configurations.
         '';
@@ -674,7 +708,6 @@ in {
       globalConfig = mkOption {
         type = promTypes.globalConfig;
         default = {};
-        apply = _filter;
         description = ''
           Parameters that are valid in all  configuration contexts. They
           also serve as defaults for other configuration sections
@@ -700,7 +733,6 @@ in {
       scrapeConfigs = mkOption {
         type = types.listOf promTypes.scrape_config;
         default = [];
-        apply = x: map _filter x;
         description = ''
           A list of scrape configurations.
         '';
diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix
index fa53107ef24b..20e7eba43412 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters.nix
@@ -34,6 +34,7 @@ let
     unifi     = import ./exporters/unifi.nix     { inherit config lib pkgs; };
     varnish   = import ./exporters/varnish.nix   { inherit config lib pkgs; };
     bind      = import ./exporters/bind.nix      { inherit config lib pkgs; };
+    wireguard = import ./exporters/wireguard.nix { inherit config lib pkgs; };
   };
 
   mkExporterOpts = ({ name, port }: {
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix b/nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix
index a3f1d9d31323..530206681d36 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix
@@ -28,7 +28,7 @@ in
     serviceConfig = {
       DynamicUser = true;
       ExecStart = ''
-        ${pkgs.prometheus-fritzbox-exporter}/bin/fritzbox_exporter \
+        ${pkgs.prometheus-fritzbox-exporter}/bin/exporter \
           -listen-address ${cfg.listenAddress}:${toString cfg.port} \
           -gateway-address ${cfg.gatewayAddress} \
           -gateway-port ${toString cfg.gatewayPort} \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix b/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix
new file mode 100644
index 000000000000..c5b84e574b8d
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix
@@ -0,0 +1,42 @@
+{ config, lib, pkgs }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.wireguard;
+in {
+  port = 9586;
+  extraOpts = {
+    verbose = mkEnableOption "Verbose logging mode for prometheus-wireguard-exporter";
+
+    wireguardConfig = mkOption {
+      type = with types; nullOr (either path str);
+      default = null;
+
+      description = ''
+        Path to the Wireguard Config to
+        <link xlink:href="https://github.com/MindFlavor/prometheus_wireguard_exporter/tree/2.0.0#usage">add the peer's name to the stats of a peer</link>.
+
+        Please note that <literal>networking.wg-quick</literal> is required for this feature
+        as <literal>networking.wireguard</literal> uses
+        <citerefentry><refentrytitle>wg</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+        to set the peers up.
+      '';
+    };
+  };
+  serviceOpts = {
+    script = ''
+      ${pkgs.prometheus-wireguard-exporter}/bin/prometheus_wireguard_exporter \
+        -p ${toString cfg.port} \
+        ${optionalString cfg.verbose "-v"} \
+        ${optionalString (cfg.wireguardConfig != null) "-n ${cfg.wireguardConfig}"}
+    '';
+
+    path = [ pkgs.wireguard-tools ];
+
+    serviceConfig = {
+      DynamicUser = true;
+      AmbientCapabilities = [ "CAP_NET_ADMIN" ];
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/prometheus/pushgateway.nix b/nixos/modules/services/monitoring/prometheus/pushgateway.nix
new file mode 100644
index 000000000000..f8fcc3eb97ef
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/pushgateway.nix
@@ -0,0 +1,166 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.pushgateway;
+
+  cmdlineArgs =
+       opt "web.listen-address" cfg.web.listen-address
+    ++ opt "web.telemetry-path" cfg.web.telemetry-path
+    ++ opt "web.external-url" cfg.web.external-url
+    ++ opt "web.route-prefix" cfg.web.route-prefix
+    ++ optional cfg.persistMetrics ''--persistence.file="/var/lib/${cfg.stateDir}/metrics"''
+    ++ opt "persistence.interval" cfg.persistence.interval
+    ++ opt "log.level" cfg.log.level
+    ++ opt "log.format" cfg.log.format
+    ++ cfg.extraFlags;
+
+  opt = k : v : optional (v != null) ''--${k}="${v}"'';
+
+in {
+  options = {
+    services.prometheus.pushgateway = {
+      enable = mkEnableOption "Prometheus Pushgateway";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.prometheus-pushgateway;
+        defaultText = "pkgs.prometheus-pushgateway";
+        description = ''
+          Package that should be used for the prometheus pushgateway.
+        '';
+      };
+
+      web.listen-address = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Address to listen on for the web interface, API and telemetry.
+
+          <literal>null</literal> will default to <literal>:9091</literal>.
+        '';
+      };
+
+      web.telemetry-path = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Path under which to expose metrics.
+
+          <literal>null</literal> will default to <literal>/metrics</literal>.
+        '';
+      };
+
+      web.external-url = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          The URL under which Pushgateway is externally reachable.
+        '';
+      };
+
+      web.route-prefix = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Prefix for the internal routes of web endpoints.
+
+          Defaults to the path of
+          <option>services.prometheus.pushgateway.web.external-url</option>.
+        '';
+      };
+
+      persistence.interval = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "10m";
+        description = ''
+          The minimum interval at which to write out the persistence file.
+
+          <literal>null</literal> will default to <literal>5m</literal>.
+        '';
+      };
+
+      log.level = mkOption {
+        type = types.nullOr (types.enum ["debug" "info" "warn" "error" "fatal"]);
+        default = null;
+        description = ''
+          Only log messages with the given severity or above.
+
+          <literal>null</literal> will default to <literal>info</literal>.
+        '';
+      };
+
+      log.format = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "logger:syslog?appname=bob&local=7";
+        description = ''
+          Set the log target and format.
+
+          <literal>null</literal> will default to <literal>logger:stderr</literal>.
+        '';
+      };
+
+      extraFlags = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          Extra commandline options when launching the Pushgateway.
+        '';
+      };
+
+      persistMetrics = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to persist metrics to a file.
+
+          When enabled metrics will be saved to a file called
+          <literal>metrics</literal> in the directory
+          <literal>/var/lib/pushgateway</literal>. The directory below
+          <literal>/var/lib</literal> can be set using
+          <option>services.prometheus.pushgateway.stateDir</option>.
+        '';
+      };
+
+      stateDir = mkOption {
+        type = types.str;
+        default = "pushgateway";
+        description = ''
+          Directory below <literal>/var/lib</literal> to store metrics.
+
+          This directory will be created automatically using systemd's
+          StateDirectory mechanism when
+          <option>services.prometheus.pushgateway.persistMetrics</option>
+          is enabled.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = !hasPrefix "/" cfg.stateDir;
+        message =
+          "The option services.prometheus.pushgateway.stateDir" +
+          " shouldn't be an absolute directory." +
+          " It should be a directory relative to /var/lib.";
+      }
+    ];
+    systemd.services.pushgateway = {
+      wantedBy = [ "multi-user.target" ];
+      after    = [ "network.target" ];
+      serviceConfig = {
+        Restart  = "always";
+        DynamicUser = true;
+        ExecStart = "${cfg.package}/bin/pushgateway" +
+          optionalString (length cmdlineArgs != 0) (" \\\n  " +
+            concatStringsSep " \\\n  " cmdlineArgs);
+        StateDirectory = if cfg.persistMetrics then cfg.stateDir else null;
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/vnstat.nix b/nixos/modules/services/monitoring/vnstat.nix
index cb2f8c07edb9..e9bedb704a43 100644
--- a/nixos/modules/services/monitoring/vnstat.nix
+++ b/nixos/modules/services/monitoring/vnstat.nix
@@ -28,14 +28,29 @@ in {
       path = [ pkgs.coreutils ];
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
-      unitConfig.documentation = "man:vnstatd(1) man:vnstat(1) man:vnstat.conf(5)";
+      documentation = [
+        "man:vnstatd(1)"
+        "man:vnstat(1)"
+        "man:vnstat.conf(5)"
+      ];
       preStart = "chmod 755 /var/lib/vnstat";
       serviceConfig = {
         ExecStart = "${pkgs.vnstat}/bin/vnstatd -n";
         ExecReload = "${pkgs.procps}/bin/kill -HUP $MAINPID";
-        ProtectHome = true;
+
+        # Hardening (from upstream example service)
+        ProtectSystem = "strict";
+        StateDirectory = "vnstat";
         PrivateDevices = true;
+        ProtectKernelTunables = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectKernelModules = true;
         PrivateTmp = true;
+        MemoryDenyWriteExecute = true;
+        RestrictRealtime = true;
+        RestrictNamespaces = true;
+
         User = "vnstatd";
       };
     };
diff --git a/nixos/modules/services/network-filesystems/ipfs.nix b/nixos/modules/services/network-filesystems/ipfs.nix
index d4fa1eccdf3b..f19bf9d81396 100644
--- a/nixos/modules/services/network-filesystems/ipfs.nix
+++ b/nixos/modules/services/network-filesystems/ipfs.nix
@@ -226,18 +226,19 @@ in {
       ipfs.gid = config.ids.gids.ipfs;
     };
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' - ${cfg.user} ${cfg.group} - -"
+    ] ++ optionals cfg.autoMount [
+      "d '${cfg.ipfsMountDir}' - ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.ipnsMountDir}' - ${cfg.user} ${cfg.group} - -"
+    ];
+
     systemd.services.ipfs-init = recursiveUpdate commonEnv {
       description = "IPFS Initializer";
 
       after = [ "local-fs.target" ];
       before = [ "ipfs.service" "ipfs-offline.service" "ipfs-norouting.service" ];
 
-      preStart = ''
-        install -m 0755 -o ${cfg.user} -g ${cfg.group} -d ${cfg.dataDir}
-      '' + optionalString cfg.autoMount ''
-        install -m 0755 -o ${cfg.user} -g ${cfg.group} -d ${cfg.ipfsMountDir}
-        install -m 0755 -o ${cfg.user} -g ${cfg.group} -d ${cfg.ipnsMountDir}
-      '';
       script = ''
         if [[ ! -f ${cfg.dataDir}/config ]]; then
           ipfs init ${optionalString cfg.emptyRepo "-e"} \
@@ -253,7 +254,6 @@ in {
       serviceConfig = {
         Type = "oneshot";
         RemainAfterExit = true;
-        PermissionsStartOnly = true;
       };
     };
 
diff --git a/nixos/modules/services/network-filesystems/tahoe.nix b/nixos/modules/services/network-filesystems/tahoe.nix
index 534862a3c9e4..7d75eb286106 100644
--- a/nixos/modules/services/network-filesystems/tahoe.nix
+++ b/nixos/modules/services/network-filesystems/tahoe.nix
@@ -234,16 +234,19 @@ in
               Type = "simple";
               PIDFile = pidfile;
               # Believe it or not, Tahoe is very brittle about the order of
-              # arguments to $(tahoe start). The node directory must come first,
+              # arguments to $(tahoe run). The node directory must come first,
               # and arguments which alter Twisted's behavior come afterwards.
               ExecStart = ''
-                ${settings.package}/bin/tahoe start ${lib.escapeShellArg nodedir} -n -l- --pidfile=${lib.escapeShellArg pidfile}
+                ${settings.package}/bin/tahoe run ${lib.escapeShellArg nodedir} --pidfile=${lib.escapeShellArg pidfile}
               '';
             };
             preStart = ''
               if [ ! -d ${lib.escapeShellArg nodedir} ]; then
                 mkdir -p /var/db/tahoe-lafs
-                tahoe create-introducer ${lib.escapeShellArg nodedir}
+                # See https://github.com/NixOS/nixpkgs/issues/25273
+                tahoe create-introducer \
+                  --hostname="${config.networking.hostName}" \
+                  ${lib.escapeShellArg nodedir}
               fi
 
               # Tahoe has created a predefined tahoe.cfg which we must now
@@ -334,10 +337,10 @@ in
               Type = "simple";
               PIDFile = pidfile;
               # Believe it or not, Tahoe is very brittle about the order of
-              # arguments to $(tahoe start). The node directory must come first,
+              # arguments to $(tahoe run). The node directory must come first,
               # and arguments which alter Twisted's behavior come afterwards.
               ExecStart = ''
-                ${settings.package}/bin/tahoe start ${lib.escapeShellArg nodedir} -n -l- --pidfile=${lib.escapeShellArg pidfile}
+                ${settings.package}/bin/tahoe run ${lib.escapeShellArg nodedir} --pidfile=${lib.escapeShellArg pidfile}
               '';
             };
             preStart = ''
diff --git a/nixos/modules/services/networking/bind.nix b/nixos/modules/services/networking/bind.nix
index 98486cefd528..7f89cff22329 100644
--- a/nixos/modules/services/networking/bind.nix
+++ b/nixos/modules/services/networking/bind.nix
@@ -184,7 +184,7 @@ in
       preStart = ''
         mkdir -m 0755 -p /etc/bind
         if ! [ -f "/etc/bind/rndc.key" ]; then
-          ${pkgs.bind.out}/sbin/rndc-confgen -r /dev/urandom -c /etc/bind/rndc.key -u ${bindUser} -a -A hmac-sha256 2>/dev/null
+          ${pkgs.bind.out}/sbin/rndc-confgen -c /etc/bind/rndc.key -u ${bindUser} -a -A hmac-sha256 2>/dev/null
         fi
 
         ${pkgs.coreutils}/bin/mkdir -p /run/named
diff --git a/nixos/modules/services/networking/bird.nix b/nixos/modules/services/networking/bird.nix
index 555733aab7c0..4ae35875c0f0 100644
--- a/nixos/modules/services/networking/bird.nix
+++ b/nixos/modules/services/networking/bird.nix
@@ -14,15 +14,6 @@ let
           bird6 = "1.9.x with IPv6 suport";
           bird2 = "2.x";
         }.${variant};
-      configFile = pkgs.stdenv.mkDerivation {
-        name = "${variant}.conf";
-        text = cfg.config;
-        preferLocalBuild = true;
-        buildCommand = ''
-          echo -n "$text" > $out
-          ${pkg}/bin/${birdBin} -d -p -c $out
-        '';
-      };
     in {
       ###### interface
       options = {
@@ -41,14 +32,24 @@ let
       ###### implementation
       config = mkIf cfg.enable {
         environment.systemPackages = [ pkg ];
+
+        environment.etc."bird/${variant}.conf".source = pkgs.writeTextFile {
+          name = "${variant}.conf";
+          text = cfg.config;
+          checkPhase = ''
+            ${pkg}/bin/${birdBin} -d -p -c $out
+          '';
+        };
+
         systemd.services.${variant} = {
           description = "BIRD Internet Routing Daemon (${descr})";
           wantedBy = [ "multi-user.target" ];
           reloadIfChanged = true;
+          restartTriggers = [ config.environment.etc."bird/${variant}.conf".source ];
           serviceConfig = {
             Type = "forking";
             Restart = "on-failure";
-            ExecStart = "${pkg}/bin/${birdBin} -c ${configFile} -u ${variant} -g ${variant}";
+            ExecStart = "${pkg}/bin/${birdBin} -c /etc/bird/${variant}.conf -u ${variant} -g ${variant}";
             ExecReload = "${pkg}/bin/${birdc} configure";
             ExecStop = "${pkg}/bin/${birdc} down";
             CapabilityBoundingSet = [ "CAP_CHOWN" "CAP_FOWNER" "CAP_DAC_OVERRIDE" "CAP_SETUID" "CAP_SETGID"
diff --git a/nixos/modules/services/networking/bitcoind.nix b/nixos/modules/services/networking/bitcoind.nix
new file mode 100644
index 000000000000..e94265564595
--- /dev/null
+++ b/nixos/modules/services/networking/bitcoind.nix
@@ -0,0 +1,195 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.bitcoind;
+  pidFile = "${cfg.dataDir}/bitcoind.pid";
+  configFile = pkgs.writeText "bitcoin.conf" ''
+    ${optionalString cfg.testnet "testnet=1"}
+    ${optionalString (cfg.dbCache != null) "dbcache=${toString cfg.dbCache}"}
+    ${optionalString (cfg.prune != null) "prune=${toString cfg.prune}"}
+
+    # Connection options
+    ${optionalString (cfg.port != null) "port=${toString cfg.port}"}
+
+    # RPC server options
+    ${optionalString (cfg.rpc.port != null) "rpcport=${toString cfg.rpc.port}"}
+    ${concatMapStringsSep  "\n"
+      (rpcUser: "rpcauth=${rpcUser.name}:${rpcUser.passwordHMAC}")
+      (attrValues cfg.rpc.users)
+    }
+
+    # Extra config options (from bitcoind nixos service)
+    ${cfg.extraConfig}
+  '';
+  cmdlineOptions = escapeShellArgs [
+    "-conf=${cfg.configFile}"
+    "-datadir=${cfg.dataDir}"
+    "-pid=${pidFile}"
+  ];
+  hexStr = types.strMatching "[0-9a-f]+";
+  rpcUserOpts = { name, ... }: {
+    options = {
+      name = mkOption {
+        type = types.str;
+        example = "alice";
+        description = ''
+          Username for JSON-RPC connections.
+        '';
+      };
+      passwordHMAC = mkOption {
+        type = with types; uniq (strMatching "[0-9a-f]+\\$[0-9a-f]{64}");
+        example = "f7efda5c189b999524f151318c0c86$d5b51b3beffbc02b724e5d095828e0bc8b2456e9ac8757ae3211a5d9b16a22ae";
+        description = ''
+          Password HMAC-SHA-256 for JSON-RPC connections. Must be a string of the
+          format &lt;SALT-HEX&gt;$&lt;HMAC-HEX&gt;.
+        '';
+      };
+    };
+    config = {
+      name = mkDefault name;
+    };
+  };
+in {
+  options = {
+
+    services.bitcoind = {
+      enable = mkEnableOption "Bitcoin daemon";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.altcoins.bitcoind;
+        defaultText = "pkgs.altcoins.bitcoind";
+        description = "The package providing bitcoin binaries.";
+      };
+      configFile = mkOption {
+        type = types.path;
+        default = configFile;
+        example = "/etc/bitcoind.conf";
+        description = "The configuration file path to supply bitcoind.";
+      };
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        example = ''
+          par=16
+          rpcthreads=16
+          logips=1
+        '';
+        description = "Additional configurations to be appended to <filename>bitcoin.conf</filename>.";
+      };
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/lib/bitcoind";
+        description = "The data directory for bitcoind.";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "bitcoin";
+        description = "The user as which to run bitcoind.";
+      };
+      group = mkOption {
+        type = types.str;
+        default = cfg.user;
+        description = "The group as which to run bitcoind.";
+      };
+
+      rpc = {
+        port = mkOption {
+          type = types.nullOr types.port;
+          default = null;
+          description = "Override the default port on which to listen for JSON-RPC connections.";
+        };
+        users = mkOption {
+          default = {};
+          example = literalExample ''
+            {
+              alice.passwordHMAC = "f7efda5c189b999524f151318c0c86$d5b51b3beffbc02b724e5d095828e0bc8b2456e9ac8757ae3211a5d9b16a22ae";
+              bob.passwordHMAC = "b2dd077cb54591a2f3139e69a897ac$4e71f08d48b4347cf8eff3815c0e25ae2e9a4340474079f55705f40574f4ec99";
+            }
+          '';
+          type = with types; loaOf (submodule rpcUserOpts);
+          description = ''
+            RPC user information for JSON-RPC connnections.
+          '';
+        };
+      };
+
+      testnet = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to use the test chain.";
+      };
+      port = mkOption {
+        type = types.nullOr types.port;
+        default = null;
+        description = "Override the default port on which to listen for connections.";
+      };
+      dbCache = mkOption {
+        type = types.nullOr (types.ints.between 4 16384);
+        default = null;
+        example = 4000;
+        description = "Override the default database cache size in megabytes.";
+      };
+      prune = mkOption {
+        type = types.nullOr (types.coercedTo
+          (types.enum [ "disable" "manual" ])
+          (x: if x == "disable" then 0 else 1)
+          types.ints.unsigned
+        );
+        default = null;
+        example = 10000;
+        description = ''
+          Reduce storage requirements by enabling pruning (deleting) of old
+          blocks. This allows the pruneblockchain RPC to be called to delete
+          specific blocks, and enables automatic pruning of old blocks if a
+          target size in MiB is provided. This mode is incompatible with -txindex
+          and -rescan. Warning: Reverting this setting requires re-downloading
+          the entire blockchain. ("disable" = disable pruning blocks, "manual"
+          = allow manual pruning via RPC, >=550 = automatically prune block files
+          to stay under the specified target size in MiB)
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' 0770 '${cfg.user}' '${cfg.group}' - -"
+      "L '${cfg.dataDir}/bitcoin.conf' - - - - '${cfg.configFile}'"
+    ];
+    systemd.services.bitcoind = {
+      description = "Bitcoin daemon";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart = "${cfg.package}/bin/bitcoind ${cmdlineOptions}";
+        Restart = "on-failure";
+
+        # Hardening measures
+        PrivateTmp = "true";
+        ProtectSystem = "full";
+        NoNewPrivileges = "true";
+        PrivateDevices = "true";
+        MemoryDenyWriteExecute = "true";
+
+        # Permission for preStart
+        PermissionsStartOnly = "true";
+      };
+    };
+    users.users.${cfg.user} = {
+      name = cfg.user;
+      group = cfg.group;
+      description = "Bitcoin daemon user";
+      home = cfg.dataDir;
+    };
+    users.groups.${cfg.group} = {
+      name = cfg.group;
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/btsync.nix b/nixos/modules/services/networking/btsync.nix
deleted file mode 100644
index 33e85ef58e6e..000000000000
--- a/nixos/modules/services/networking/btsync.nix
+++ /dev/null
@@ -1,324 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.btsync;
-
-  bittorrentSync = cfg.package;
-
-  listenAddr = cfg.httpListenAddr + ":" + (toString cfg.httpListenPort);
-
-  optionalEmptyStr = b: v: optionalString (b != "") v;
-
-  webUIConfig = optionalString cfg.enableWebUI
-    ''
-      "webui":
-      {
-        ${optionalEmptyStr cfg.httpLogin     "\"login\":          \"${cfg.httpLogin}\","}
-        ${optionalEmptyStr cfg.httpPass      "\"password\":       \"${cfg.httpPass}\","}
-        ${optionalEmptyStr cfg.apiKey        "\"api_key\":        \"${cfg.apiKey}\","}
-        ${optionalEmptyStr cfg.directoryRoot "\"directory_root\": \"${cfg.directoryRoot}\","}
-        "listen": "${listenAddr}"
-      }
-    '';
-
-  knownHosts = e:
-    optionalString (e ? "knownHosts")
-      (concatStringsSep "," (map (v: "\"${v}\"") e."knownHosts"));
-
-  sharedFoldersRecord =
-    concatStringsSep "," (map (entry:
-      let helper = attr: v:
-        if (entry ? attr) then boolToString entry.attr else boolToString v;
-      in
-      ''
-        {
-          "secret": "${entry.secret}",
-          "dir":    "${entry.directory}",
-
-          "use_relay_server": ${helper "useRelayServer" true},
-          "use_tracker":      ${helper "useTracker"     true},
-          "use_dht":          ${helper "useDHT"        false},
-
-          "search_lan":       ${helper "searchLAN"      true},
-          "use_sync_trash":   ${helper "useSyncTrash"   true},
-
-          "known_hosts": [${knownHosts entry}]
-        }
-      '') cfg.sharedFolders);
-
-  sharedFoldersConfig = optionalString (cfg.sharedFolders != [])
-    ''
-      "shared_folders":
-        [
-        ${sharedFoldersRecord}
-        ]
-    '';
-
-  configFile = pkgs.writeText "btsync.config"
-    ''
-      {
-        "device_name":     "${cfg.deviceName}",
-        "storage_path":    "${cfg.storagePath}",
-        "listening_port":  ${toString cfg.listeningPort},
-        "use_gui":         false,
-
-        "check_for_updates": ${boolToString cfg.checkForUpdates},
-        "use_upnp":          ${boolToString cfg.useUpnp},
-        "download_limit":    ${toString cfg.downloadLimit},
-        "upload_limit":      ${toString cfg.uploadLimit},
-        "lan_encrypt_data":  ${boolToString cfg.encryptLAN},
-
-        ${webUIConfig}
-        ${sharedFoldersConfig}
-      }
-    '';
-in
-{
-  options = {
-    services.btsync = {
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          If enabled, start the Bittorrent Sync daemon. Once enabled, you can
-          interact with the service through the Web UI, or configure it in your
-          NixOS configuration. Enabling the <literal>btsync</literal> service
-          also installs a systemd user unit which can be used to start
-          user-specific copies of the daemon. Once installed, you can use
-          <literal>systemctl --user start btsync</literal> as your user to start
-          the daemon using the configuration file located at
-          <literal>$HOME/.config/btsync.conf</literal>.
-        '';
-      };
-
-      deviceName = mkOption {
-        type = types.str;
-        example = "Voltron";
-        description = ''
-          Name of the Bittorrent Sync device.
-        '';
-      };
-
-      listeningPort = mkOption {
-        type = types.int;
-        default = 0;
-        example = 44444;
-        description = ''
-          Listening port. Defaults to 0 which randomizes the port.
-        '';
-      };
-
-      checkForUpdates = mkOption {
-        type = types.bool;
-        default = true;
-        description = ''
-          Determines whether to check for updates and alert the user
-          about them in the UI.
-        '';
-      };
-
-      useUpnp = mkOption {
-        type = types.bool;
-        default = true;
-        description = ''
-          Use Universal Plug-n-Play (UPnP)
-        '';
-      };
-
-      downloadLimit = mkOption {
-        type = types.int;
-        default = 0;
-        example = 1024;
-        description = ''
-          Download speed limit. 0 is unlimited (default).
-        '';
-      };
-
-      uploadLimit = mkOption {
-        type = types.int;
-        default = 0;
-        example = 1024;
-        description = ''
-          Upload speed limit. 0 is unlimited (default).
-        '';
-      };
-
-      httpListenAddr = mkOption {
-        type = types.str;
-        default = "0.0.0.0";
-        example = "1.2.3.4";
-        description = ''
-          HTTP address to bind to.
-        '';
-      };
-
-      httpListenPort = mkOption {
-        type = types.int;
-        default = 9000;
-        description = ''
-          HTTP port to bind on.
-        '';
-      };
-
-      httpLogin = mkOption {
-        type = types.str;
-        example = "allyourbase";
-        default = "";
-        description = ''
-          HTTP web login username.
-        '';
-      };
-
-      httpPass = mkOption {
-        type = types.str;
-        example = "arebelongtous";
-        default = "";
-        description = ''
-          HTTP web login password.
-        '';
-      };
-
-      encryptLAN = mkOption {
-        type = types.bool;
-        default = true;
-        description = "Encrypt LAN data.";
-      };
-
-      enableWebUI = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Enable Web UI for administration. Bound to the specified
-          <literal>httpListenAddress</literal> and
-          <literal>httpListenPort</literal>.
-          '';
-      };
-
-      package = mkOption {
-        type = types.package;
-        example = literalExample "pkgs.bittorrentSync20";
-        description = ''
-          Branch of bittorrent sync to use.
-        '';
-      };
-
-      storagePath = mkOption {
-        type = types.path;
-        default = "/var/lib/btsync/";
-        description = ''
-          Where BitTorrent Sync will store it's database files (containing
-          things like username info and licenses). Generally, you should not
-          need to ever change this.
-        '';
-      };
-
-      apiKey = mkOption {
-        type = types.str;
-        default = "";
-        description = "API key, which enables the developer API.";
-      };
-
-      directoryRoot = mkOption {
-        type = types.str;
-        default = "";
-        example = "/media";
-        description = "Default directory to add folders in the web UI.";
-      };
-
-      sharedFolders = mkOption {
-        default = [];
-        example =
-          [ { secret         = "AHMYFPCQAHBM7LQPFXQ7WV6Y42IGUXJ5Y";
-              directory      = "/home/user/sync_test";
-              useRelayServer = true;
-              useTracker     = true;
-              useDHT         = false;
-              searchLAN      = true;
-              useSyncTrash   = true;
-              knownHosts     =
-                [ "192.168.1.2:4444"
-                  "192.168.1.3:4444"
-                ];
-            }
-          ];
-        description = ''
-          Shared folder list. If enabled, web UI must be
-          disabled. Secrets can be generated using <literal>btsync
-          --generate-secret</literal>. Note that this secret will be
-          put inside the Nix store, so it is realistically not very
-          secret.
-
-          If you would like to be able to modify the contents of this
-          directories, it is recommended that you make your user a
-          member of the <literal>btsync</literal> group.
-
-          Directories in this list should be in the
-          <literal>btsync</literal> group, and that group must have
-          write access to the directory. It is also recommended that
-          <literal>chmod g+s</literal> is applied to the directory
-          so that any sub directories created will also belong to
-          the <literal>btsync</literal> group. Also,
-          <literal>setfacl -d -m group:btsync:rwx</literal> and
-          <literal>setfacl -m group:btsync:rwx</literal> should also
-          be applied so that the sub directories are writable by
-          the group.
-        '';
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    assertions =
-      [ { assertion = cfg.deviceName != "";
-          message   = "Device name cannot be empty.";
-        }
-        { assertion = cfg.enableWebUI -> cfg.sharedFolders == [];
-          message   = "If using shared folders, the web UI cannot be enabled.";
-        }
-        { assertion = cfg.apiKey != "" -> cfg.enableWebUI;
-          message   = "If you're using an API key, you must enable the web server.";
-        }
-      ];
-
-    services.btsync.package = mkOptionDefault pkgs.bittorrentSync14;
-
-    users.users.btsync = {
-      description     = "Bittorrent Sync Service user";
-      home            = cfg.storagePath;
-      createHome      = true;
-      uid             = config.ids.uids.btsync;
-      group           = "btsync";
-    };
-
-    users.groups = [
-      { name = "btsync";
-      }];
-
-    systemd.services.btsync = with pkgs; {
-      description = "Bittorrent Sync Service";
-      wantedBy    = [ "multi-user.target" ];
-      after       = [ "network.target" "local-fs.target" ];
-      serviceConfig = {
-        Restart   = "on-abort";
-        UMask     = "0002";
-        User      = "btsync";
-        ExecStart =
-          "${bittorrentSync}/bin/btsync --nodaemon --config ${configFile}";
-      };
-    };
-
-    systemd.user.services.btsync = with pkgs; {
-      description = "Bittorrent Sync user service";
-      after       = [ "network.target" "local-fs.target" ];
-      serviceConfig = {
-        Restart   = "on-abort";
-        ExecStart =
-          "${bittorrentSync}/bin/btsync --nodaemon --config %h/.config/btsync.conf";
-      };
-    };
-
-    environment.systemPackages = [ cfg.package ];
-  };
-}
diff --git a/nixos/modules/services/networking/ejabberd.nix b/nixos/modules/services/networking/ejabberd.nix
index ef5e2cee6f20..6a38f85c48a2 100644
--- a/nixos/modules/services/networking/ejabberd.nix
+++ b/nixos/modules/services/networking/ejabberd.nix
@@ -11,7 +11,7 @@ let
     ${cfg.ctlConfig}
   '';
 
-  ectl = ''${cfg.package}/bin/ejabberdctl ${if cfg.configFile == null then "" else "--config ${cfg.configFile}"} --ctl-config "${ctlcfg}" --spool "${cfg.spoolDir}" --logs "${cfg.logsDir}"'';
+  ectl = ''${cfg.package}/bin/ejabberdctl ${optionalString (cfg.configFile != null) "--config ${cfg.configFile}"} --ctl-config "${ctlcfg}" --spool "${cfg.spoolDir}" --logs "${cfg.logsDir}"'';
 
   dumps = lib.escapeShellArgs cfg.loadDumps;
 
@@ -111,28 +111,17 @@ in {
       description = "ejabberd server";
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
-      path = [ pkgs.findutils pkgs.coreutils pkgs.runit ] ++ lib.optional cfg.imagemagick pkgs.imagemagick;
+      path = [ pkgs.findutils pkgs.coreutils ] ++ lib.optional cfg.imagemagick pkgs.imagemagick;
 
       serviceConfig = {
-        ExecStart = ''${ectl} foreground'';
-        # FIXME: runit is used for `chpst` -- can we get rid of this?
-        ExecStop = ''${pkgs.runit}/bin/chpst -u "${cfg.user}:${cfg.group}" ${ectl} stop'';
-        ExecReload = ''${pkgs.runit}/bin/chpst -u "${cfg.user}:${cfg.group}" ${ectl} reload_config'';
         User = cfg.user;
         Group = cfg.group;
-        PermissionsStartOnly = true;
+        ExecStart = "${ectl} foreground";
+        ExecStop = "${ectl} stop";
+        ExecReload = "${ectl} reload_config";
       };
 
       preStart = ''
-        mkdir -p -m750 "${cfg.logsDir}"
-        chown "${cfg.user}:${cfg.group}" "${cfg.logsDir}"
-
-        mkdir -p -m750 "/var/lock/ejabberdctl"
-        chown "${cfg.user}:${cfg.group}" "/var/lock/ejabberdctl"
-
-        mkdir -p -m750 "${cfg.spoolDir}"
-        chown -R "${cfg.user}:${cfg.group}" "${cfg.spoolDir}"
-
         if [ -z "$(ls -A '${cfg.spoolDir}')" ]; then
           touch "${cfg.spoolDir}/.firstRun"
         fi
@@ -149,13 +138,18 @@ in {
           for src in ${dumps}; do
             find "$src" -type f | while read dump; do
               echo "Loading configuration dump at $dump"
-              chpst -u "${cfg.user}:${cfg.group}" ${ectl} load "$dump"
+              ${ectl} load "$dump"
             done
           done
         fi
       '';
     };
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.logsDir}' 0750 ${cfg.user} ${cfg.group} -"
+      "d '${cfg.spoolDir}' 0700 ${cfg.user} ${cfg.group} -"
+    ];
+
     security.pam.services.ejabberd = {};
 
   };
diff --git a/nixos/modules/services/networking/flannel.nix b/nixos/modules/services/networking/flannel.nix
index c1f778ac139a..dd2f6454e954 100644
--- a/nixos/modules/services/networking/flannel.nix
+++ b/nixos/modules/services/networking/flannel.nix
@@ -92,7 +92,7 @@ in {
         Needed when running with Kubernetes as backend as this cannot be auto-detected";
       '';
       type = types.nullOr types.str;
-      default = with config.networking; (hostName + optionalString (!isNull domain) ".${domain}");
+      default = with config.networking; (hostName + optionalString (domain != null) ".${domain}");
       example = "node1.example.com";
     };
 
diff --git a/nixos/modules/services/networking/hostapd.nix b/nixos/modules/services/networking/hostapd.nix
index 3fbc08e90607..7add48308f80 100644
--- a/nixos/modules/services/networking/hostapd.nix
+++ b/nixos/modules/services/networking/hostapd.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, pkgs, utils, ... }:
 
 # TODO:
 #
@@ -12,6 +12,8 @@ let
 
   cfg = config.services.hostapd;
 
+  escapedInterface = utils.escapeSystemdPath cfg.interface;
+
   configFile = pkgs.writeText "hostapd.conf" ''
     interface=${cfg.interface}
     driver=${cfg.driver}
@@ -157,8 +159,8 @@ in
       { description = "hostapd wireless AP";
 
         path = [ pkgs.hostapd ];
-        after = [ "sys-subsystem-net-devices-${cfg.interface}.device" ];
-        bindsTo = [ "sys-subsystem-net-devices-${cfg.interface}.device" ];
+        after = [ "sys-subsystem-net-devices-${escapedInterface}.device" ];
+        bindsTo = [ "sys-subsystem-net-devices-${escapedInterface}.device" ];
         requiredBy = [ "network-link-${cfg.interface}.service" ];
 
         serviceConfig =
diff --git a/nixos/modules/services/networking/i2pd.nix b/nixos/modules/services/networking/i2pd.nix
index 0e9b354cfcaf..f2be417738ee 100644
--- a/nixos/modules/services/networking/i2pd.nix
+++ b/nixos/modules/services/networking/i2pd.nix
@@ -12,9 +12,9 @@ let
   boolOpt = k: v: k + " = " + boolToString v;
   intOpt = k: v: k + " = " + toString v;
   lstOpt = k: xs: k + " = " + concatStringsSep "," xs;
-  optionalNullString = o: s: optional (! isNull s) (strOpt o s);
-  optionalNullBool = o: b: optional (! isNull b) (boolOpt o b);
-  optionalNullInt = o: i: optional (! isNull i) (intOpt o i);
+  optionalNullString = o: s: optional (s != null) (strOpt o s);
+  optionalNullBool = o: b: optional (b != null) (boolOpt o b);
+  optionalNullInt = o: i: optional (i != null) (intOpt o i);
   optionalEmptyList = o: l: optional ([] != l) (lstOpt o l);
 
   mkEnableTrueOption = name: mkEnableOption name // { default = true; };
@@ -225,7 +225,7 @@ let
   i2pdSh = pkgs.writeScriptBin "i2pd" ''
     #!/bin/sh
     exec ${pkgs.i2pd}/bin/i2pd \
-      ${if isNull cfg.address then "" else "--host="+cfg.address} \
+      ${if cfg.address == null then "" else "--host="+cfg.address} \
       --service \
       --conf=${i2pdConf} \
       --tunconf=${tunnelConf}
@@ -470,7 +470,7 @@ in
         '';
       };
 
-      trust.hidden = mkEnableOption "Router concealment.";
+      trust.hidden = mkEnableOption "Router concealment";
 
       websocket = mkEndpointOpt "websockets" "127.0.0.1" 7666;
 
@@ -478,7 +478,7 @@ in
       exploratory.outbound = i2cpOpts "exploratory";
 
       ntcp2.enable = mkEnableTrueOption "NTCP2.";
-      ntcp2.published = mkEnableOption "NTCP2 publication.";
+      ntcp2.published = mkEnableOption "NTCP2 publication";
       ntcp2.port = mkOption {
         type = types.int;
         default = 0;
diff --git a/nixos/modules/services/networking/miredo.nix b/nixos/modules/services/networking/miredo.nix
index 8694d08385ca..2c8393fb5b41 100644
--- a/nixos/modules/services/networking/miredo.nix
+++ b/nixos/modules/services/networking/miredo.nix
@@ -20,7 +20,7 @@ in
 
     services.miredo = {
 
-      enable = mkEnableOption "the Miredo IPv6 tunneling service.";
+      enable = mkEnableOption "the Miredo IPv6 tunneling service";
 
       package = mkOption {
         type = types.package;
diff --git a/nixos/modules/services/networking/monero.nix b/nixos/modules/services/networking/monero.nix
index 8241c32bad07..831e4d60d8da 100644
--- a/nixos/modules/services/networking/monero.nix
+++ b/nixos/modules/services/networking/monero.nix
@@ -51,7 +51,7 @@ in
 
     services.monero = {
 
-      enable = mkEnableOption "Monero node daemon.";
+      enable = mkEnableOption "Monero node daemon";
 
       mining.enable = mkOption {
         type = types.bool;
diff --git a/nixos/modules/services/networking/mosquitto.nix b/nixos/modules/services/networking/mosquitto.nix
index 9974cbd89d1e..1d49c137723c 100644
--- a/nixos/modules/services/networking/mosquitto.nix
+++ b/nixos/modules/services/networking/mosquitto.nix
@@ -44,7 +44,7 @@ in
 
   options = {
     services.mosquitto = {
-      enable = mkEnableOption "Enable the MQTT Mosquitto broker.";
+      enable = mkEnableOption "the MQTT Mosquitto broker";
 
       host = mkOption {
         default = "127.0.0.1";
@@ -65,7 +65,7 @@ in
       };
 
       ssl = {
-        enable = mkEnableOption "Enable SSL listener.";
+        enable = mkEnableOption "SSL listener";
 
         cafile = mkOption {
           type = types.nullOr types.path;
diff --git a/nixos/modules/services/networking/mtprotoproxy.nix b/nixos/modules/services/networking/mtprotoproxy.nix
new file mode 100644
index 000000000000..24bf33815da8
--- /dev/null
+++ b/nixos/modules/services/networking/mtprotoproxy.nix
@@ -0,0 +1,110 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.mtprotoproxy;
+
+  configOpts = {
+    PORT = cfg.port;
+    USERS = cfg.users;
+    SECURE_ONLY = cfg.secureOnly;
+  } // lib.optionalAttrs (cfg.adTag != null) { AD_TAG = cfg.adTag; }
+    // cfg.extraConfig;
+
+  convertOption = opt:
+    if isString opt || isInt opt then
+      builtins.toJSON opt
+    else if isBool opt then
+      if opt then "True" else "False"
+    else if isList opt then
+      "[" + concatMapStringsSep "," convertOption opt + "]"
+    else if isAttrs opt then
+      "{" + concatStringsSep "," (mapAttrsToList (name: opt: "${builtins.toJSON name}: ${convertOption opt}") opt) + "}"
+    else
+      throw "Invalid option type";
+
+  configFile = pkgs.writeText "config.py" (concatStringsSep "\n" (mapAttrsToList (name: opt: "${name} = ${convertOption opt}") configOpts));
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.mtprotoproxy = {
+
+      enable = mkEnableOption "mtprotoproxy";
+
+      port = mkOption {
+        type = types.int;
+        default = 3256;
+        description = ''
+          TCP port to accept mtproto connections on.
+        '';
+      };
+
+      users = mkOption {
+        type = types.attrsOf types.str;
+        example = {
+          "tg" = "00000000000000000000000000000000";
+          "tg2" = "0123456789abcdef0123456789abcdef";
+        };
+        description = ''
+          Allowed users and their secrets. A secret is a 32 characters long hex string.
+        '';
+      };
+
+      secureOnly = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Don't allow users to connect in non-secure mode (without random padding).
+        '';
+      };
+
+      adTag = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        # Taken from mtproxyproto's repo.
+        example = "3c09c680b76ee91a4c25ad51f742267d";
+        description = ''
+          Tag for advertising that can be obtained from @MTProxybot.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.attrs;
+        default = {};
+        example = {
+          "STATS_PRINT_PERIOD" = 600;
+        };
+        description = ''
+          Extra configuration options for mtprotoproxy.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.services.mtprotoproxy = {
+      description = "MTProto Proxy Daemon";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = "${pkgs.mtprotoproxy}/bin/mtprotoproxy ${configFile}";
+        DynamicUser = true;
+      };
+    };
+
+  };
+
+}
diff --git a/nixos/modules/services/networking/murmur.nix b/nixos/modules/services/networking/murmur.nix
index a6e90feff7ea..7ac4d0c6419d 100644
--- a/nixos/modules/services/networking/murmur.nix
+++ b/nixos/modules/services/networking/murmur.nix
@@ -4,6 +4,7 @@ with lib;
 
 let
   cfg = config.services.murmur;
+  forking = cfg.logFile != null;
   configFile = pkgs.writeText "murmurd.ini" ''
     database=/var/lib/murmur/murmur.sqlite
     dbDriver=QSQLITE
@@ -12,8 +13,8 @@ let
     autobanTimeframe=${toString cfg.autobanTimeframe}
     autobanTime=${toString cfg.autobanTime}
 
-    logfile=/var/log/murmur/murmurd.log
-    pidfile=${cfg.pidfile}
+    logfile=${optionalString (cfg.logFile != null) cfg.logFile}
+    ${optionalString forking "pidfile=/run/murmur/murmurd.pid"}
 
     welcometext="${cfg.welcometext}"
     port=${toString cfg.port}
@@ -78,10 +79,11 @@ in
         description = "The amount of time an IP ban lasts (in seconds).";
       };
 
-      pidfile = mkOption {
-        type = types.path;
-        default = "/run/murmur/murmurd.pid";
-        description = "Path to PID file for Murmur daemon.";
+      logFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/var/log/murmur/murmurd.log";
+        description = "Path to the log file for Murmur daemon. Empty means log to journald.";
       };
 
       welcometext = mkOption {
@@ -251,19 +253,13 @@ in
       after       = [ "network-online.target "];
 
       serviceConfig = {
-        Type      = "forking";
-        RuntimeDirectory = "murmur";
-        PIDFile   = cfg.pidfile;
-        Restart   = "always";
+        # murmurd doesn't fork when logging to the console.
+        Type      = if forking then "forking" else "simple";
+        PIDFile   = mkIf forking "/run/murmur/murmurd.pid";
+        RuntimeDirectory = mkIf forking "murmur";
         User      = "murmur";
         ExecStart = "${pkgs.murmur}/bin/murmurd -ini ${configFile}";
-        PermissionsStartOnly = true;
       };
-
-      preStart = ''
-        mkdir -p /var/log/murmur
-        chown -R murmur /var/log/murmur
-      '';
     };
   };
 }
diff --git a/nixos/modules/services/networking/mxisd.nix b/nixos/modules/services/networking/mxisd.nix
index 0aa6d0d9ecd3..02e89f441b34 100644
--- a/nixos/modules/services/networking/mxisd.nix
+++ b/nixos/modules/services/networking/mxisd.nix
@@ -103,21 +103,12 @@ in {
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
 
-      # mxisd / spring.boot needs the configuration to be named "application.yaml"
-      preStart = ''
-        config=${cfg.dataDir}/application.yaml
-        cp ${configFile} $config
-        chmod 444 $config
-      '';
-
       serviceConfig = {
         Type = "simple";
         User = "mxisd";
         Group = "mxisd";
-        ExecStart = "${cfg.package}/bin/mxisd --spring.config.location=${cfg.dataDir}/ --spring.profiles.active=systemd --java.security.egd=file:/dev/./urandom";
+        ExecStart = "${cfg.package}/bin/mxisd -c ${configFile}";
         WorkingDirectory = cfg.dataDir;
-        PermissionsStartOnly = true;
-        SuccessExitStatus = 143;
         Restart = "on-failure";
       };
     };
diff --git a/nixos/modules/services/networking/namecoind.nix b/nixos/modules/services/networking/namecoind.nix
index 8de23b442f93..a569ca87e262 100644
--- a/nixos/modules/services/networking/namecoind.nix
+++ b/nixos/modules/services/networking/namecoind.nix
@@ -1,3 +1,4 @@
+
 { config, lib, pkgs, ... }:
 
 with lib;
@@ -43,7 +44,7 @@ in
 
     services.namecoind = {
 
-      enable = mkEnableOption "namecoind, Namecoin client.";
+      enable = mkEnableOption "namecoind, Namecoin client";
 
       wallet = mkOption {
         type = types.path;
diff --git a/nixos/modules/services/networking/networkmanager.nix b/nixos/modules/services/networking/networkmanager.nix
index 5e5544471818..f1ba7dd4e4f2 100644
--- a/nixos/modules/services/networking/networkmanager.nix
+++ b/nixos/modules/services/networking/networkmanager.nix
@@ -477,6 +477,10 @@ in {
       '';
     };
 
+    systemd.services.NetworkManager-wait-online = {
+      wantedBy = [ "network-online.target" ];
+    };
+
     systemd.services.nm-setup-hostsdirs = mkIf dynamicHostsEnabled {
       wantedBy = [ "NetworkManager.service" ];
       before = [ "NetworkManager.service" ];
diff --git a/nixos/modules/services/networking/nullidentdmod.nix b/nixos/modules/services/networking/nullidentdmod.nix
index 786b5227dbad..b0d338a27941 100644
--- a/nixos/modules/services/networking/nullidentdmod.nix
+++ b/nixos/modules/services/networking/nullidentdmod.nix
@@ -3,7 +3,7 @@
 
 in {
   options.services.nullidentdmod = with types; {
-    enable = mkEnableOption "Enable the nullidentdmod identd daemon";
+    enable = mkEnableOption "the nullidentdmod identd daemon";
 
     userid = mkOption {
       type = nullOr str;
diff --git a/nixos/modules/services/networking/openvpn.nix b/nixos/modules/services/networking/openvpn.nix
index b94b4026fd91..f47122ee70bf 100644
--- a/nixos/modules/services/networking/openvpn.nix
+++ b/nixos/modules/services/networking/openvpn.nix
@@ -85,7 +85,7 @@ in
         {
           server = {
             config = '''
-              # Simplest server configuration: http://openvpn.net/index.php/documentation/miscellaneous/static-key-mini-howto.html.
+              # Simplest server configuration: https://community.openvpn.net/openvpn/wiki/StaticKeyMiniHowto
               # server :
               dev tun
               ifconfig 10.8.0.1 10.8.0.2
diff --git a/nixos/modules/services/networking/prosody.nix b/nixos/modules/services/networking/prosody.nix
index de316e5f4669..40bd9015b1eb 100644
--- a/nixos/modules/services/networking/prosody.nix
+++ b/nixos/modules/services/networking/prosody.nix
@@ -422,6 +422,13 @@ in
         description = "List of administrators of the current host";
       };
 
+      authentication = mkOption {
+        type = types.enum [ "internal_plain" "internal_hashed" "cyrus" "anonymous" ];
+        default = "internal_hashed";
+        example = "internal_plain";
+        description = "Authentication mechanism used for logins.";
+      };
+
       extraConfig = mkOption {
         type = types.lines;
         default = "";
@@ -477,6 +484,7 @@ in
 
       s2s_secure_domains = ${toLua cfg.s2sSecureDomains}
 
+      authentication = ${toLua cfg.authentication}
 
       ${ cfg.extraConfig }
 
diff --git a/nixos/modules/services/networking/smokeping.nix b/nixos/modules/services/networking/smokeping.nix
index 9ba6e48f417c..fab3ed5bb39d 100644
--- a/nixos/modules/services/networking/smokeping.nix
+++ b/nixos/modules/services/networking/smokeping.nix
@@ -285,12 +285,12 @@ in
       uid = config.ids.uids.smokeping;
       description = "smokeping daemon user";
       home = smokepingHome;
+      createHome = true;
     };
     systemd.services.smokeping = {
       wantedBy = [ "multi-user.target"];
       serviceConfig = {
         User = cfg.user;
-        PermissionsStartOnly = true;
         Restart = "on-failure";
       };
       preStart = ''
@@ -300,7 +300,6 @@ in
         cp ${cgiHome} ${smokepingHome}/smokeping.fcgi
         ${cfg.package}/bin/smokeping --check --config=${configPath}
         ${cfg.package}/bin/smokeping --static --config=${configPath}
-        chown -R ${cfg.user} ${smokepingHome}
       '';
       script = ''${cfg.package}/bin/smokeping --config=${configPath} --nodaemon'';
     };
diff --git a/nixos/modules/services/networking/softether.nix b/nixos/modules/services/networking/softether.nix
index 65df93a00da9..0046dcd366fa 100644
--- a/nixos/modules/services/networking/softether.nix
+++ b/nixos/modules/services/networking/softether.nix
@@ -70,6 +70,8 @@ in
 
       systemd.services."softether-init" = {
         description = "SoftEther VPN services initial task";
+        after = [ "keys.target" ];
+        wants = [ "keys.target" ];
         wantedBy = [ "network.target" ];
         serviceConfig = {
           Type = "oneshot";
diff --git a/nixos/modules/services/networking/ssh/sshd.nix b/nixos/modules/services/networking/ssh/sshd.nix
index cbb305cd3825..89f56a726f46 100644
--- a/nixos/modules/services/networking/ssh/sshd.nix
+++ b/nixos/modules/services/networking/ssh/sshd.nix
@@ -4,6 +4,15 @@ with lib;
 
 let
 
+  sshconf = pkgs.runCommand "sshd.conf-validated" { nativeBuildInputs = [ cfgc.package ]; } ''
+    cat >$out <<EOL
+    ${cfg.extraConfig}
+    EOL
+
+    ssh-keygen -f mock-hostkey -N ""
+    sshd -t -f $out -h mock-hostkey
+  '';
+
   cfg  = config.services.openssh;
   cfgc = config.programs.ssh;
 
@@ -339,7 +348,7 @@ in
 
     environment.etc = authKeysFiles //
       { "ssh/moduli".source = cfg.moduliFile;
-        "ssh/sshd_config".text = cfg.extraConfig;
+        "ssh/sshd_config".source = sshconf;
       };
 
     systemd =
diff --git a/nixos/modules/services/networking/strongswan-swanctl/param-constructors.nix b/nixos/modules/services/networking/strongswan-swanctl/param-constructors.nix
index 5e74a96664f0..95a174122d04 100644
--- a/nixos/modules/services/networking/strongswan-swanctl/param-constructors.nix
+++ b/nixos/modules/services/networking/strongswan-swanctl/param-constructors.nix
@@ -56,7 +56,7 @@ rec {
   };
 
   documentDefault = description : strongswanDefault :
-    if isNull strongswanDefault
+    if strongswanDefault == null
     then description
     else description + ''
       </para><para>
diff --git a/nixos/modules/services/networking/strongswan-swanctl/param-lib.nix b/nixos/modules/services/networking/strongswan-swanctl/param-lib.nix
index fb87e81f3215..193ad27f035a 100644
--- a/nixos/modules/services/networking/strongswan-swanctl/param-lib.nix
+++ b/nixos/modules/services/networking/strongswan-swanctl/param-lib.nix
@@ -45,10 +45,10 @@ rec {
     filterEmptySets (
       (mapParamsRecursive (path: name: param:
         let value = attrByPath path null cfg;
-        in optionalAttrs (!isNull value) (param.render name value)
+        in optionalAttrs (value != null) (param.render name value)
       ) ps));
 
-  filterEmptySets = set : filterAttrs (n: v: !(isNull v)) (mapAttrs (name: value:
+  filterEmptySets = set : filterAttrs (n: v: (v != null)) (mapAttrs (name: value:
     if isAttrs value
     then let value' = filterEmptySets value;
          in if value' == {}
diff --git a/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix b/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix
index d4f7e95f859f..808cb863a9cf 100644
--- a/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix
+++ b/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix
@@ -6,7 +6,7 @@
 #
 #   git clone https://github.com/strongswan/strongswan.git
 #   cd strongswan
-#   git diff 5.5.3..5.6.0 src/swanctl/swanctl.opt
+#   git diff 5.7.2..5.8.0 src/swanctl/swanctl.opt
 
 lib: with (import ./param-constructors.nix lib);
 
@@ -227,6 +227,22 @@ in {
       irrespective of the value of this option (even when set to no).
     '';
 
+    childless = mkEnumParam [ "allow" "force" "never" ] "allow" ''
+      Use childless IKE_SA initiation (RFC 6023) for IKEv2.  Acceptable values
+      are <literal>allow</literal> (the default), <literal>force</literal> and
+      <literal>never</literal>. If set to <literal>allow</literal>, responders
+      will accept childless IKE_SAs (as indicated via notify in the IKE_SA_INIT
+      response) while initiators continue to create regular IKE_SAs with the
+      first CHILD_SA created during IKE_AUTH, unless the IKE_SA is initiated
+      explicitly without any children (which will fail if the responder does not
+      support or has disabled this extension).  If set to
+      <literal>force</literal>, only childless initiation is accepted and the
+      first CHILD_SA is created with a separate CREATE_CHILD_SA exchange
+      (e.g. to use an independent DH exchange for all CHILD_SAs). Finally,
+      setting the option to <literal>never</literal> disables support for
+      childless IKE_SAs as responder.
+    '';
+
     send_certreq = mkYesNoParam yes ''
       Send certificate request payloads to offer trusted root CA certificates to
       the peer. Certificate requests help the peer to choose an appropriate
@@ -350,6 +366,16 @@ in {
       name from either the pools section or an external pool.
     '';
 
+    if_id_in = mkStrParam "0" ''
+      XFRM interface ID set on inbound policies/SA, can be overridden by child
+      config, see there for details.
+    '';
+
+    if_id_out = mkStrParam "0" ''
+      XFRM interface ID set on outbound policies/SA, can be overridden by child
+      config, see there for details.
+    '';
+
     mediation = mkYesNoParam no ''
       Whether this connection is a mediation connection, that is, whether this
       connection is used to mediate other connections using the IKEv2 Mediation
@@ -799,7 +825,7 @@ in {
         Updown script to invoke on CHILD_SA up and down events.
       '';
 
-      hostaccess = mkYesNoParam yes ''
+      hostaccess = mkYesNoParam no ''
         Hostaccess variable to pass to <literal>updown</literal> script.
       '';
 
@@ -960,6 +986,26 @@ in {
         mask requires at least Linux 4.19.
       '';
 
+      if_id_in = mkStrParam "0" ''
+        XFRM interface ID set on inbound policies/SA. This allows installing
+        duplicate policies/SAs and associates them with an interface with the
+        same ID. The special value <literal>%unique</literal> sets a unique
+        interface ID on each CHILD_SA instance, beyond that the value
+        <literal>%unique-dir</literal> assigns a different unique interface ID
+        for each CHILD_SA direction (in/out).
+      '';
+
+      if_id_out = mkStrParam "0" ''
+        XFRM interface ID set on outbound policies/SA. This allows installing
+        duplicate policies/SAs and associates them with an interface with the
+        same ID. The special value <literal>%unique</literal> sets a unique
+        interface ID on each CHILD_SA instance, beyond that the value
+        <literal>%unique-dir</literal> assigns a different unique interface ID
+        for each CHILD_SA direction (in/out).
+
+        The daemon will not install routes for CHILD_SAs that have this option set.
+     '';
+
       tfc_padding = mkParamOfType (with lib.types; either int (enum ["mtu"])) 0 ''
         Pads ESP packets with additional data to have a consistent ESP packet
         size for improved Traffic Flow Confidentiality. The padding defines the
diff --git a/nixos/modules/services/networking/syncthing.nix b/nixos/modules/services/networking/syncthing.nix
index 702481ec5177..d78a54a3327b 100644
--- a/nixos/modules/services/networking/syncthing.nix
+++ b/nixos/modules/services/networking/syncthing.nix
@@ -5,6 +5,60 @@ with lib;
 let
   cfg = config.services.syncthing;
   defaultUser = "syncthing";
+
+  devices = mapAttrsToList (name: device: {
+    deviceID = device.id;
+    inherit (device) name addresses introducer;
+  }) cfg.declarative.devices;
+
+  folders = mapAttrsToList ( _: folder: {
+    inherit (folder) path id label type;
+    devices = map (device: { deviceId = cfg.declarative.devices.${device}.id; }) folder.devices;
+    rescanIntervalS = folder.rescanInterval;
+    fsWatcherEnabled = folder.watch;
+    fsWatcherDelayS = folder.watchDelay;
+    ignorePerms = folder.ignorePerms;
+  }) (filterAttrs (
+    _: folder:
+    folder.enable
+  ) cfg.declarative.folders);
+
+  # get the api key by parsing the config.xml
+  getApiKey = pkgs.writers.writeDash "getAPIKey" ''
+    ${pkgs.libxml2}/bin/xmllint \
+      --xpath 'string(configuration/gui/apikey)'\
+      ${cfg.configDir}/config.xml
+  '';
+
+  updateConfig = pkgs.writers.writeDash "merge-syncthing-config" ''
+    set -efu
+    # wait for syncthing port to open
+    until ${pkgs.curl}/bin/curl -Ss ${cfg.guiAddress} -o /dev/null; do
+      sleep 1
+    done
+
+    API_KEY=$(${getApiKey})
+    OLD_CFG=$(${pkgs.curl}/bin/curl -Ss \
+      -H "X-API-Key: $API_KEY" \
+      ${cfg.guiAddress}/rest/system/config)
+
+    # generate the new config by merging with the nixos config options
+    NEW_CFG=$(echo "$OLD_CFG" | ${pkgs.jq}/bin/jq -s '.[] as $in | $in * {
+      "devices": (${builtins.toJSON devices}${optionalString (! cfg.declarative.overrideDevices) " + $in.devices"}),
+      "folders": (${builtins.toJSON folders}${optionalString (! cfg.declarative.overrideFolders) " + $in.folders"})
+    }')
+
+    # POST the new config to syncthing
+    echo "$NEW_CFG" | ${pkgs.curl}/bin/curl -Ss \
+      -H "X-API-Key: $API_KEY" \
+      ${cfg.guiAddress}/rest/system/config -d @-
+
+    # restart syncthing after sending the new config
+    ${pkgs.curl}/bin/curl -Ss \
+      -H "X-API-Key: $API_KEY" \
+      -X POST \
+      ${cfg.guiAddress}/rest/system/restart
+  '';
 in {
   ###### interface
   options = {
@@ -16,6 +70,202 @@ in {
         available on http://127.0.0.1:8384/.
       '';
 
+      declarative = {
+        cert = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            Path to users cert.pem file, will be copied into the syncthing's
+            <literal>configDir</literal>
+          '';
+        };
+
+        key = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            Path to users key.pem file, will be copied into the syncthing's
+            <literal>configDir</literal>
+          '';
+        };
+
+        overrideDevices = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            Whether to delete the devices which are not configured via the
+            <literal>declarative.devices</literal> option.
+            If set to false, devices added via the webinterface will
+            persist but will have to be deleted manually.
+          '';
+        };
+
+        devices = mkOption {
+          default = {};
+          description = ''
+            Peers/devices which syncthing should communicate with.
+          '';
+          example = {
+            bigbox = {
+              id = "7CFNTQM-IMTJBHJ-3UWRDIU-ZGQJFR6-VCXZ3NB-XUH3KZO-N52ITXR-LAIYUAU";
+              addresses = [ "tcp://192.168.0.10:51820" ];
+            };
+          };
+          type = types.attrsOf (types.submodule ({ config, ... }: {
+            options = {
+
+              name = mkOption {
+                type = types.str;
+                default = config._module.args.name;
+                description = ''
+                  Name of the device
+                '';
+              };
+
+              addresses = mkOption {
+                type = types.listOf types.str;
+                default = [];
+                description = ''
+                  The addresses used to connect to the device.
+                  If this is let empty, dynamic configuration is attempted
+                '';
+              };
+
+              id = mkOption {
+                type = types.str;
+                description = ''
+                  The id of the other peer, this is mandatory. It's documented at
+                  https://docs.syncthing.net/dev/device-ids.html
+                '';
+              };
+
+              introducer = mkOption {
+                type = types.bool;
+                default = false;
+                description = ''
+                  If the device should act as an introducer and be allowed
+                  to add folders on this computer.
+                '';
+              };
+
+            };
+          }));
+        };
+
+        overrideFolders = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            Whether to delete the folders which are not configured via the
+            <literal>declarative.folders</literal> option.
+            If set to false, folders added via the webinterface will persist
+            but will have to be deleted manually.
+          '';
+        };
+
+        folders = mkOption {
+          default = {};
+          description = ''
+            folders which should be shared by syncthing.
+          '';
+          example = {
+            "/home/user/sync" = {
+              id = "syncme";
+              devices = [ "bigbox" ];
+            };
+          };
+          type = types.attrsOf (types.submodule ({ config, ... }: {
+            options = {
+
+              enable = mkOption {
+                type = types.bool;
+                default = true;
+                description = ''
+                  share this folder.
+                  This option is useful when you want to define all folders
+                  in one place, but not every machine should share all folders.
+                '';
+              };
+
+              path = mkOption {
+                type = types.str;
+                default = config._module.args.name;
+                description = ''
+                  The path to the folder which should be shared.
+                '';
+              };
+
+              id = mkOption {
+                type = types.str;
+                default = config._module.args.name;
+                description = ''
+                  The id of the folder. Must be the same on all devices.
+                '';
+              };
+
+              label = mkOption {
+                type = types.str;
+                default = config._module.args.name;
+                description = ''
+                  The label of the folder.
+                '';
+              };
+
+              devices = mkOption {
+                type = types.listOf types.str;
+                default = [];
+                description = ''
+                  The devices this folder should be shared with. Must be defined
+                  in the <literal>declarative.devices</literal> attribute.
+                '';
+              };
+
+              rescanInterval = mkOption {
+                type = types.int;
+                default = 3600;
+                description = ''
+                  How often the folders should be rescaned for changes.
+                '';
+              };
+
+              type = mkOption {
+                type = types.enum [ "sendreceive" "sendonly" "receiveonly" ];
+                default = "sendreceive";
+                description = ''
+                  Whether to send only changes from this folder, only receive them
+                  or propagate both.
+                '';
+              };
+
+              watch = mkOption {
+                type = types.bool;
+                default = true;
+                description = ''
+                  Whether the folder should be watched for changes by inotify.
+                '';
+              };
+
+              watchDelay = mkOption {
+                type = types.int;
+                default = 10;
+                description = ''
+                  The delay after an inotify event is triggered.
+                '';
+              };
+
+              ignorePerms = mkOption {
+                type = types.bool;
+                default = true;
+                description = ''
+                  Whether to propagate permission changes.
+                '';
+              };
+
+            };
+          }));
+        };
+      };
+
       guiAddress = mkOption {
         type = types.str;
         default = "127.0.0.1:8384";
@@ -151,7 +401,23 @@ in {
           RestartForceExitStatus="3 4";
           User = cfg.user;
           Group = cfg.group;
-          PermissionsStartOnly = true;
+          ExecStartPre = mkIf (cfg.declarative.cert != null || cfg.declarative.key != null)
+            "+${pkgs.writers.writeBash "syncthing-copy-keys" ''
+              mkdir -p ${cfg.configDir}
+              chown ${cfg.user}:${cfg.group} ${cfg.configDir}
+              chmod 700 ${cfg.configDir}
+              ${optionalString (cfg.declarative.cert != null) ''
+                cp ${toString cfg.declarative.cert} ${cfg.configDir}/cert.pem
+                chown ${cfg.user}:${cfg.group} ${cfg.configDir}/cert.pem
+                chmod 400 ${cfg.configDir}/cert.pem
+              ''}
+              ${optionalString (cfg.declarative.key != null) ''
+                cp ${toString cfg.declarative.key} ${cfg.configDir}/key.pem
+                chown ${cfg.user}:${cfg.group} ${cfg.configDir}/key.pem
+                chmod 400 ${cfg.configDir}/key.pem
+              ''}
+            ''}"
+          ;
           ExecStart = ''
             ${cfg.package}/bin/syncthing \
               -no-browser \
@@ -160,6 +426,19 @@ in {
           '';
         };
       };
+      syncthing-init = mkIf (
+        cfg.declarative.devices != {} || cfg.declarative.folders != {}
+      ) {
+        after = [ "syncthing.service" ];
+        wantedBy = [ "multi-user.target" ];
+
+        serviceConfig = {
+          User = cfg.user;
+          RemainAfterExit = true;
+          Type = "oneshot";
+          ExecStart = updateConfig;
+        };
+      };
 
       syncthing-resume = {
         wantedBy = [ "suspend.target" ];
diff --git a/nixos/modules/services/networking/tedicross.nix b/nixos/modules/services/networking/tedicross.nix
new file mode 100644
index 000000000000..0716975f594a
--- /dev/null
+++ b/nixos/modules/services/networking/tedicross.nix
@@ -0,0 +1,100 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  dataDir = "/var/lib/tedicross";
+  cfg = config.services.tedicross;
+  configJSON = pkgs.writeText "tedicross-settings.json" (builtins.toJSON cfg.config);
+  configYAML = pkgs.runCommand "tedicross-settings.yaml" { preferLocalBuild = true; } ''
+    ${pkgs.remarshal}/bin/json2yaml -i ${configJSON} -o $out
+  '';
+
+in {
+  options = {
+    services.tedicross = {
+      enable = mkEnableOption "the TediCross Telegram-Discord bridge service";
+
+      config = mkOption {
+        type = types.attrs;
+        # from https://github.com/TediCross/TediCross/blob/master/example.settings.yaml
+        example = literalExample ''
+          {
+            telegram = {
+              useFirstNameInsteadOfUsername = false;
+              colonAfterSenderName = false;
+              skipOldMessages = true;
+              sendEmojiWithStickers = true;
+            };
+            discord = {
+              useNickname = false;
+              skipOldMessages = true;
+              displayTelegramReplies = "embed";
+              replyLength = 100;
+            };
+            bridges = [
+              {
+                name = "Default bridge";
+                direction = "both";
+                telegram = {
+                  chatId = -123456789;
+                  relayJoinMessages = true;
+                  relayLeaveMessages = true;
+                  sendUsernames = true;
+                  ignoreCommands = true;
+                };
+                discord = {
+                  serverId = "DISCORD_SERVER_ID";
+                  channelId = "DISCORD_CHANNEL_ID";
+                  relayJoinMessages = true;
+                  relayLeaveMessages = true;
+                  sendUsernames = true;
+                  crossDeleteOnTelegram = true;
+                };
+              }
+            ];
+
+            debug = false;
+          }
+        '';
+        description = ''
+          <filename>settings.yaml</filename> configuration as a Nix attribute set.
+          Secret tokens should be specified using <option>environmentFile</option>
+          instead of this world-readable file.
+        '';
+      };
+
+      environmentFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          File containing environment variables to be passed to the TediCross service,
+          in which secret tokens can be specified securely using the
+          <literal>TELEGRAM_BOT_TOKEN</literal> and <literal>DISCORD_BOT_TOKEN</literal>
+          keys.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    # from https://github.com/TediCross/TediCross/blob/master/guides/autostart/Linux.md
+    systemd.services.tedicross = {
+      description = "TediCross Telegram-Discord bridge service";
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
+      after = [ "network-online.target" ];
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = "${pkgs.nodePackages.tedicross}/bin/tedicross --config='${configYAML}' --data-dir='${dataDir}'";
+        Restart = "always";
+        DynamicUser = true;
+        StateDirectory = baseNameOf dataDir;
+        EnvironmentFile = cfg.environmentFile;
+      };
+    };
+  };
+
+  meta.maintainers = with maintainers; [ pacien ];
+}
+
diff --git a/nixos/modules/services/networking/tinc.nix b/nixos/modules/services/networking/tinc.nix
index 3379efd1afce..e98aafc20937 100644
--- a/nixos/modules/services/networking/tinc.nix
+++ b/nixos/modules/services/networking/tinc.nix
@@ -153,7 +153,6 @@ in
       ({
         description = "Tinc Daemon - ${network}";
         wantedBy = [ "multi-user.target" ];
-        after = [ "network.target" ];
         path = [ data.package ];
         restartTriggers = [ config.environment.etc."tinc/${network}/tinc.conf".source ];
         serviceConfig = {
diff --git a/nixos/modules/services/networking/toxvpn.nix b/nixos/modules/services/networking/toxvpn.nix
index b7655f4bec62..7830dfb1834c 100644
--- a/nixos/modules/services/networking/toxvpn.nix
+++ b/nixos/modules/services/networking/toxvpn.nix
@@ -5,7 +5,7 @@ with lib;
 {
   options = {
     services.toxvpn = {
-      enable = mkEnableOption "enable toxvpn running on startup";
+      enable = mkEnableOption "toxvpn running on startup";
 
       localip = mkOption {
         type        = types.string;
diff --git a/nixos/modules/services/networking/wg-quick.nix b/nixos/modules/services/networking/wg-quick.nix
new file mode 100644
index 000000000000..b770d47d269e
--- /dev/null
+++ b/nixos/modules/services/networking/wg-quick.nix
@@ -0,0 +1,312 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.networking.wg-quick;
+
+  kernel = config.boot.kernelPackages;
+
+  # interface options
+
+  interfaceOpts = { ... }: {
+    options = {
+      address = mkOption {
+        example = [ "192.168.2.1/24" ];
+        default = [];
+        type = with types; listOf str;
+        description = "The IP addresses of the interface.";
+      };
+
+      dns = mkOption {
+        example = [ "192.168.2.2" ];
+        default = [];
+        type = with types; listOf str;
+        description = "The IP addresses of DNS servers to configure.";
+      };
+
+      privateKey = mkOption {
+        example = "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=";
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Base64 private key generated by wg genkey.
+
+          Warning: Consider using privateKeyFile instead if you do not
+          want to store the key in the world-readable Nix store.
+        '';
+      };
+
+      privateKeyFile = mkOption {
+        example = "/private/wireguard_key";
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Private key file as generated by wg genkey.
+        '';
+      };
+
+      listenPort = mkOption {
+        default = null;
+        type = with types; nullOr int;
+        example = 51820;
+        description = ''
+          16-bit port for listening. Optional; if not specified,
+          automatically generated based on interface name.
+        '';
+      };
+
+      preUp = mkOption {
+        example = literalExample ''
+          ${pkgs.iproute}/bin/ip netns add foo
+        '';
+        default = "";
+        type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
+        description = ''
+          Commands called at the start of the interface setup.
+        '';
+      };
+
+      preDown = mkOption {
+        example = literalExample ''
+          ${pkgs.iproute}/bin/ip netns del foo
+        '';
+        default = "";
+        type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
+        description = ''
+          Command called before the interface is taken down.
+        '';
+      };
+
+      postUp = mkOption {
+        example = literalExample ''
+          ${pkgs.iproute}/bin/ip netns add foo
+        '';
+        default = "";
+        type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
+        description = ''
+          Commands called after the interface setup.
+        '';
+      };
+
+      postDown = mkOption {
+        example = literalExample ''
+          ${pkgs.iproute}/bin/ip netns del foo
+        '';
+        default = "";
+        type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
+        description = ''
+          Command called after the interface is taken down.
+        '';
+      };
+
+      table = mkOption {
+        example = "main";
+        default = null;
+        type = with types; nullOr str;
+        description = ''
+          The kernel routing table to add this interface's
+          associated routes to. Setting this is useful for e.g. policy routing
+          ("ip rule") or virtual routing and forwarding ("ip vrf"). Both numeric
+          table IDs and table names (/etc/rt_tables) can be used. Defaults to
+          "main".
+        '';
+      };
+
+      mtu = mkOption {
+        example = 1248;
+        default = null;
+        type = with types; nullOr int;
+        description = ''
+          If not specified, the MTU is automatically determined
+          from the endpoint addresses or the system default route, which is usually
+          a sane choice. However, to manually specify an MTU to override this
+          automatic discovery, this value may be specified explicitly.
+        '';
+      };
+
+      peers = mkOption {
+        default = [];
+        description = "Peers linked to the interface.";
+        type = with types; listOf (submodule peerOpts);
+      };
+    };
+  };
+
+  # peer options
+
+  peerOpts = {
+    options = {
+      publicKey = mkOption {
+        example = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
+        type = types.str;
+        description = "The base64 public key the peer.";
+      };
+
+      presharedKey = mkOption {
+        default = null;
+        example = "rVXs/Ni9tu3oDBLS4hOyAUAa1qTWVA3loR8eL20os3I=";
+        type = with types; nullOr str;
+        description = ''
+          Base64 preshared key generated by wg genpsk. Optional,
+          and may be omitted. This option adds an additional layer of
+          symmetric-key cryptography to be mixed into the already existing
+          public-key cryptography, for post-quantum resistance.
+
+          Warning: Consider using presharedKeyFile instead if you do not
+          want to store the key in the world-readable Nix store.
+        '';
+      };
+
+      presharedKeyFile = mkOption {
+        default = null;
+        example = "/private/wireguard_psk";
+        type = with types; nullOr str;
+        description = ''
+          File pointing to preshared key as generated by wg pensk. Optional,
+          and may be omitted. This option adds an additional layer of
+          symmetric-key cryptography to be mixed into the already existing
+          public-key cryptography, for post-quantum resistance.
+        '';
+      };
+
+      allowedIPs = mkOption {
+        example = [ "10.192.122.3/32" "10.192.124.1/24" ];
+        type = with types; listOf str;
+        description = ''List of IP (v4 or v6) addresses with CIDR masks from
+        which this peer is allowed to send incoming traffic and to which
+        outgoing traffic for this peer is directed. The catch-all 0.0.0.0/0 may
+        be specified for matching all IPv4 addresses, and ::/0 may be specified
+        for matching all IPv6 addresses.'';
+      };
+
+      endpoint = mkOption {
+        default = null;
+        example = "demo.wireguard.io:12913";
+        type = with types; nullOr str;
+        description = ''Endpoint IP or hostname of the peer, followed by a colon,
+        and then a port number of the peer.'';
+      };
+
+      persistentKeepalive = mkOption {
+        default = null;
+        type = with types; nullOr int;
+        example = 25;
+        description = ''This is optional and is by default off, because most
+        users will not need it. It represents, in seconds, between 1 and 65535
+        inclusive, how often to send an authenticated empty packet to the peer,
+        for the purpose of keeping a stateful firewall or NAT mapping valid
+        persistently. For example, if the interface very rarely sends traffic,
+        but it might at anytime receive traffic from a peer, and it is behind
+        NAT, the interface might benefit from having a persistent keepalive
+        interval of 25 seconds; however, most users will not need this.'';
+      };
+    };
+  };
+
+  writeScriptFile = name: text: ((pkgs.writeShellScriptBin name text) + "/bin/${name}");
+
+  generateUnit = name: values:
+    assert assertMsg ((values.privateKey != null) != (values.privateKeyFile != null)) "Only one of privateKey or privateKeyFile may be set";
+    let
+      preUpFile = if values.preUp != "" then writeScriptFile "preUp.sh" values.preUp else null;
+      postUp =
+            optional (values.privateKeyFile != null) "wg set ${name} private-key <(cat ${values.privateKeyFile})" ++
+            (concatMap (peer: optional (peer.presharedKeyFile != null) "wg set ${name} peer ${peer.publicKey} preshared-key <(cat ${peer.presharedKeyFile})") values.peers) ++
+            optional (values.postUp != null) values.postUp;
+      postUpFile = if postUp != [] then writeScriptFile "postUp.sh" (concatMapStringsSep "\n" (line: line) postUp) else null;
+      preDownFile = if values.preDown != "" then writeScriptFile "preDown.sh" values.preDown else null;
+      postDownFile = if values.postDown != "" then writeScriptFile "postDown.sh" values.postDown else null;
+      configDir = pkgs.writeTextFile {
+        name = "config-${name}";
+        executable = false;
+        destination = "/${name}.conf";
+        text =
+        ''
+        [interface]
+        ${concatMapStringsSep "\n" (address:
+          "Address = ${address}"
+        ) values.address}
+        ${concatMapStringsSep "\n" (dns:
+          "DNS = ${dns}"
+        ) values.dns}
+        '' +
+        optionalString (values.table != null) "Table = ${values.table}\n" +
+        optionalString (values.mtu != null) "MTU = ${toString values.mtu}\n" +
+        optionalString (values.privateKey != null) "PrivateKey = ${values.privateKey}\n" +
+        optionalString (values.listenPort != null) "ListenPort = ${toString values.listenPort}\n" +
+        optionalString (preUpFile != null) "PreUp = ${preUpFile}\n" +
+        optionalString (postUpFile != null) "PostUp = ${postUpFile}\n" +
+        optionalString (preDownFile != null) "PreDown = ${preDownFile}\n" +
+        optionalString (postDownFile != null) "PostDown = ${postDownFile}\n" +
+        concatMapStringsSep "\n" (peer:
+          assert assertMsg (!((peer.presharedKeyFile != null) && (peer.presharedKey != null))) "Only one of presharedKey or presharedKeyFile may be set";
+          "[Peer]\n" +
+          "PublicKey = ${peer.publicKey}\n" +
+          optionalString (peer.presharedKey != null) "PresharedKey = ${peer.presharedKey}\n" +
+          optionalString (peer.endpoint != null) "Endpoint = ${peer.endpoint}\n" +
+          optionalString (peer.persistentKeepalive != null) "PersistentKeepalive = ${toString peer.persistentKeepalive}\n" +
+          optionalString (peer.allowedIPs != []) "AllowedIPs = ${concatStringsSep "," peer.allowedIPs}\n"
+        ) values.peers;
+      };
+      configPath = "${configDir}/${name}.conf";
+    in
+    nameValuePair "wg-quick-${name}"
+      {
+        description = "wg-quick WireGuard Tunnel - ${name}";
+        requires = [ "network-online.target" ];
+        after = [ "network.target" "network-online.target" ];
+        wantedBy = [ "multi-user.target" ];
+        environment.DEVICE = name;
+        path = [ pkgs.kmod pkgs.wireguard-tools ];
+
+        serviceConfig = {
+          Type = "oneshot";
+          RemainAfterExit = true;
+        };
+
+        script = ''
+          ${optionalString (!config.boot.isContainer) "modprobe wireguard"}
+          wg-quick up ${configPath}
+        '';
+
+        preStop = ''
+          wg-quick down ${configPath}
+        '';
+      };
+in {
+
+  ###### interface
+
+  options = {
+    networking.wg-quick = {
+      interfaces = mkOption {
+        description = "Wireguard interfaces.";
+        default = {};
+        example = {
+          wg0 = {
+            address = [ "192.168.20.4/24" ];
+            privateKey = "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=";
+            peers = [
+              { allowedIPs = [ "192.168.20.1/32" ];
+                publicKey  = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
+                endpoint   = "demo.wireguard.io:12913"; }
+            ];
+          };
+        };
+        type = with types; attrsOf (submodule interfaceOpts);
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf (cfg.interfaces != {}) {
+    boot.extraModulePackages = [ kernel.wireguard ];
+    environment.systemPackages = [ pkgs.wireguard-tools ];
+    # This is forced to false for now because the default "--validmark" rpfilter we apply on reverse path filtering
+    # breaks the wg-quick routing because wireguard packets leave with a fwmark from wireguard.
+    networking.firewall.checkReversePath = false;
+    systemd.services = mapAttrs' generateUnit cfg.interfaces;
+  };
+}
diff --git a/nixos/modules/services/networking/wireguard.nix b/nixos/modules/services/networking/wireguard.nix
index 41aff1480a05..4176da2c8cb8 100644
--- a/nixos/modules/services/networking/wireguard.nix
+++ b/nixos/modules/services/networking/wireguard.nix
@@ -26,19 +26,28 @@ let
         type = with types; nullOr str;
         default = null;
         description = ''
-          Base64 private key generated by wg genkey.
+          Base64 private key generated by <command>wg genkey</command>.
 
           Warning: Consider using privateKeyFile instead if you do not
           want to store the key in the world-readable Nix store.
         '';
       };
 
+      generatePrivateKeyFile = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Automatically generate a private key with
+          <command>wg genkey</command>, at the privateKeyFile location.
+        '';
+      };
+
       privateKeyFile = mkOption {
         example = "/private/wireguard_key";
         type = with types; nullOr str;
         default = null;
         description = ''
-          Private key file as generated by wg genkey.
+          Private key file as generated by <command>wg genkey</command>.
         '';
       };
 
@@ -124,8 +133,8 @@ let
         example = "rVXs/Ni9tu3oDBLS4hOyAUAa1qTWVA3loR8eL20os3I=";
         type = with types; nullOr str;
         description = ''
-          Base64 preshared key generated by wg genpsk. Optional,
-          and may be omitted. This option adds an additional layer of
+          Base64 preshared key generated by <command>wg genpsk</command>.
+          Optional, and may be omitted. This option adds an additional layer of
           symmetric-key cryptography to be mixed into the already existing
           public-key cryptography, for post-quantum resistance.
 
@@ -139,8 +148,8 @@ let
         example = "/private/wireguard_psk";
         type = with types; nullOr str;
         description = ''
-          File pointing to preshared key as generated by wg pensk. Optional,
-          and may be omitted. This option adds an additional layer of
+          File pointing to preshared key as generated by <command>wg pensk</command>.
+          Optional, and may be omitted. This option adds an additional layer of
           symmetric-key cryptography to be mixed into the already existing
           public-key cryptography, for post-quantum resistance.
         '';
@@ -182,9 +191,101 @@ let
 
   };
 
-  generateUnit = name: values:
+
+  generatePathUnit = name: values:
+    assert (values.privateKey == null);
+    assert (values.privateKeyFile != null);
+    nameValuePair "wireguard-${name}"
+      {
+        description = "WireGuard Tunnel - ${name} - Private Key";
+        requiredBy = [ "wireguard-${name}.service" ];
+        before = [ "wireguard-${name}.service" ];
+        pathConfig.PathExists = values.privateKeyFile;
+      };
+
+  generateKeyServiceUnit = name: values:
+    assert values.generatePrivateKeyFile;
+    nameValuePair "wireguard-${name}-key"
+      {
+        description = "WireGuard Tunnel - ${name} - Key Generator";
+        wantedBy = [ "wireguard-${name}.service" ];
+        requiredBy = [ "wireguard-${name}.service" ];
+        before = [ "wireguard-${name}.service" ];
+        path = with pkgs; [ wireguard ];
+
+        serviceConfig = {
+          Type = "oneshot";
+          RemainAfterExit = true;
+        };
+
+        script = ''
+          mkdir --mode 0644 -p "${dirOf values.privateKeyFile}"
+          if [ ! -f "${values.privateKeyFile}" ]; then
+            touch "${values.privateKeyFile}"
+            chmod 0600 "${values.privateKeyFile}"
+            wg genkey > "${values.privateKeyFile}"
+            chmod 0400 "${values.privateKeyFile}"
+          fi
+        '';
+      };
+
+  generatePeerUnit = { interfaceName, interfaceCfg, peer }:
+    let
+      keyToUnitName = replaceChars
+        [ "/" "-"    " "     "+"     "="      ]
+        [ "-" "\\x2d" "\\x20" "\\x2b" "\\x3d" ];
+      unitName = keyToUnitName peer.publicKey;
+      psk =
+        if peer.presharedKey != null
+          then pkgs.writeText "wg-psk" peer.presharedKey
+          else peer.presharedKeyFile;
+    in nameValuePair "wireguard-${interfaceName}-peer-${unitName}"
+      {
+        description = "WireGuard Peer - ${interfaceName} - ${peer.publicKey}";
+        requires = [ "wireguard-${interfaceName}.service" ];
+        after = [ "wireguard-${interfaceName}.service" ];
+        wantedBy = [ "multi-user.target" "wireguard-${interfaceName}.service" ];
+        environment.DEVICE = interfaceName;
+        environment.WG_ENDPOINT_RESOLUTION_RETRIES = "infinity";
+        path = with pkgs; [ iproute wireguard-tools ];
+
+        serviceConfig = {
+          Type = "oneshot";
+          RemainAfterExit = true;
+        };
+
+        script = let
+          wg_setup = "wg set ${interfaceName} peer ${peer.publicKey}" +
+            optionalString (psk != null) " preshared-key ${psk}" +
+            optionalString (peer.endpoint != null) " endpoint ${peer.endpoint}" +
+            optionalString (peer.persistentKeepalive != null) " persistent-keepalive ${toString peer.persistentKeepalive}" +
+            optionalString (peer.allowedIPs != []) " allowed-ips ${concatStringsSep "," peer.allowedIPs}";
+          route_setup =
+            optionalString (interfaceCfg.allowedIPsAsRoutes != false)
+              (concatMapStringsSep "\n"
+                (allowedIP:
+                  "ip route replace ${allowedIP} dev ${interfaceName} table ${interfaceCfg.table}"
+                ) peer.allowedIPs);
+        in ''
+          ${wg_setup}
+          ${route_setup}
+        '';
+
+        postStop = let
+          route_destroy = optionalString (interfaceCfg.allowedIPsAsRoutes != false)
+            (concatMapStringsSep "\n"
+              (allowedIP:
+                "ip route delete ${allowedIP} dev ${interfaceName} table ${interfaceCfg.table}"
+              ) peer.allowedIPs);
+        in ''
+          wg set ${interfaceName} peer ${peer.publicKey} remove
+          ${route_destroy}
+        '';
+      };
+
+  generateInterfaceUnit = name: values:
     # exactly one way to specify the private key must be set
-    assert (values.privateKey != null) != (values.privateKeyFile != null);
+    #assert (values.privateKey != null) != (values.privateKeyFile != null);
     let privKey = if values.privateKeyFile != null then values.privateKeyFile else pkgs.writeText "wg-key" values.privateKey;
     in
     nameValuePair "wireguard-${name}"
@@ -202,7 +303,7 @@ let
         };
 
         script = ''
-          ${optionalString (!config.boot.isContainer) "modprobe wireguard"}
+          ${optionalString (!config.boot.isContainer) "modprobe wireguard || true"}
 
           ${values.preSetup}
 
@@ -215,25 +316,8 @@ let
           wg set ${name} private-key ${privKey} ${
             optionalString (values.listenPort != null) " listen-port ${toString values.listenPort}"}
 
-          ${concatMapStringsSep "\n" (peer:
-            assert (peer.presharedKeyFile == null) || (peer.presharedKey == null); # at most one of the two must be set
-            let psk = if peer.presharedKey != null then pkgs.writeText "wg-psk" peer.presharedKey else peer.presharedKeyFile;
-            in
-              "wg set ${name} peer ${peer.publicKey}" +
-              optionalString (psk != null) " preshared-key ${psk}" +
-              optionalString (peer.endpoint != null) " endpoint ${peer.endpoint}" +
-              optionalString (peer.persistentKeepalive != null) " persistent-keepalive ${toString peer.persistentKeepalive}" +
-              optionalString (peer.allowedIPs != []) " allowed-ips ${concatStringsSep "," peer.allowedIPs}"
-            ) values.peers}
-
           ip link set up dev ${name}
 
-          ${optionalString (values.allowedIPsAsRoutes != false) (concatStringsSep "\n" (concatMap (peer:
-              (map (allowedIP:
-                "ip route replace ${allowedIP} dev ${name} table ${values.table}"
-              ) peer.allowedIPs)
-            ) values.peers))}
-
           ${values.postSetup}
         '';
 
@@ -253,8 +337,16 @@ in
 
     networking.wireguard = {
 
+      enable = mkOption {
+        description = "Whether to enable WireGuard.";
+        type = types.bool;
+        # 2019-05-25: Backwards compatibility.
+        default = cfg.interfaces != {};
+        example = true;
+      };
+
       interfaces = mkOption {
-        description = "Wireguard interfaces.";
+        description = "WireGuard interfaces.";
         default = {};
         example = {
           wg0 = {
@@ -277,13 +369,40 @@ in
 
   ###### implementation
 
-  config = mkIf (cfg.interfaces != {}) {
+  config = mkIf cfg.enable (let
+    all_peers = flatten
+      (mapAttrsToList (interfaceName: interfaceCfg:
+        map (peer: { inherit interfaceName interfaceCfg peer;}) interfaceCfg.peers
+      ) cfg.interfaces);
+  in {
+
+    assertions = (attrValues (
+        mapAttrs (name: value: {
+          assertion = (value.privateKey != null) != (value.privateKeyFile != null);
+          message = "Either networking.wireguard.interfaces.${name}.privateKey or networking.wireguard.interfaces.${name}.privateKeyFile must be set.";
+        }) cfg.interfaces))
+      ++ (attrValues (
+        mapAttrs (name: value: {
+          assertion = value.generatePrivateKeyFile -> (value.privateKey == null);
+          message = "networking.wireguard.interfaces.${name}.generatePrivateKey must not be set if networking.wireguard.interfaces.${name}.privateKey is set.";
+        }) cfg.interfaces))
+        ++ map ({ interfaceName, peer, ... }: {
+          assertion = (peer.presharedKey == null) || (peer.presharedKeyFile == null);
+          message = "networking.wireguard.interfaces.${interfaceName} peer «${peer.publicKey}» has both presharedKey and presharedKeyFile set, but only one can be used.";
+        }) all_peers;
 
     boot.extraModulePackages = [ kernel.wireguard ];
     environment.systemPackages = [ pkgs.wireguard-tools ];
 
-    systemd.services = mapAttrs' generateUnit cfg.interfaces;
+    systemd.services =
+      (mapAttrs' generateInterfaceUnit cfg.interfaces)
+      // (listToAttrs (map generatePeerUnit all_peers))
+      // (mapAttrs' generateKeyServiceUnit
+      (filterAttrs (name: value: value.generatePrivateKeyFile) cfg.interfaces));
 
-  };
+    systemd.paths = mapAttrs' generatePathUnit
+      (filterAttrs (name: value: value.privateKeyFile != null) cfg.interfaces);
+
+  });
 
 }
diff --git a/nixos/modules/services/networking/xinetd.nix b/nixos/modules/services/networking/xinetd.nix
index 002245027804..2d7cd5cebb48 100644
--- a/nixos/modules/services/networking/xinetd.nix
+++ b/nixos/modules/services/networking/xinetd.nix
@@ -146,7 +146,7 @@ in
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       path = [ pkgs.xinetd ];
-      script = "xinetd -syslog daemon -dontfork -stayalive -f ${configFile}";
+      script = "exec xinetd -syslog daemon -dontfork -stayalive -f ${configFile}";
     };
   };
 }
diff --git a/nixos/modules/services/networking/zeronet.nix b/nixos/modules/services/networking/zeronet.nix
index 8b60799891ca..611a51c74ce2 100644
--- a/nixos/modules/services/networking/zeronet.nix
+++ b/nixos/modules/services/networking/zeronet.nix
@@ -5,13 +5,15 @@ let
 
   zConfFile = pkgs.writeTextFile {
     name = "zeronet.conf";
-    
+
     text = ''
       [global]
       data_dir = ${cfg.dataDir}
       log_dir = ${cfg.logDir}
     '' + lib.optionalString (cfg.port != null) ''
       ui_port = ${toString cfg.port}
+    '' + lib.optionalString (cfg.fileserverPort != null) ''
+      fileserver_port = ${toString cfg.fileserverPort}
     '' + lib.optionalString (cfg.torAlways) ''
       tor = always
     '' + cfg.extraConfig;
@@ -41,6 +43,15 @@ in with lib; {
       description = "Optional zeronet web UI port.";
     };
 
+    fileserverPort = mkOption {
+      # Not optional: when absent zeronet tries to write one to the
+      # read-only config file and crashes
+      type = types.int;
+      default = 12261;
+      example = 12261;
+      description = "Zeronet fileserver port.";
+    };
+
     tor = mkOption {
       type = types.bool;
       default = false;
diff --git a/nixos/modules/services/networking/znc/default.nix b/nixos/modules/services/networking/znc/default.nix
index 1ad8855b86db..46bff6954cdd 100644
--- a/nixos/modules/services/networking/znc/default.nix
+++ b/nixos/modules/services/networking/znc/default.nix
@@ -47,11 +47,11 @@ let
           #     Baz=baz
           #     Qux=qux
           #   </Foo>
-          set = concatMap (subname: [
+          set = concatMap (subname: optionals (value.${subname} != null) ([
               "<${name} ${subname}>"
             ] ++ map (line: "\t${line}") (toLines value.${subname}) ++ [
               "</${name}>"
-            ]) (filter (v: v != null) (attrNames value));
+            ])) (filter (v: v != null) (attrNames value));
 
         }.${builtins.typeOf value};
 
diff --git a/nixos/modules/services/printing/cupsd.nix b/nixos/modules/services/printing/cupsd.nix
index 9e9bdedff126..86b0c8d69beb 100644
--- a/nixos/modules/services/printing/cupsd.nix
+++ b/nixos/modules/services/printing/cupsd.nix
@@ -60,6 +60,8 @@ let
 
     TempDir ${cfg.tempDir}
 
+    SetEnv PATH /var/lib/cups/path/lib/cups/filter:/var/lib/cups/path/bin
+
     # User and group used to run external programs, including
     # those that actually send the job to the printer.  Note that
     # Udev sets the group of printer devices to `lp', so we want
@@ -76,8 +78,6 @@ let
     '') cfg.listenAddresses}
     Listen /run/cups/cups.sock
 
-    SetEnv PATH /var/lib/cups/path/lib/cups/filter:/var/lib/cups/path/bin
-
     DefaultShared ${if cfg.defaultShared then "Yes" else "No"}
 
     Browsing ${if cfg.browsing then "Yes" else "No"}
diff --git a/nixos/modules/services/search/kibana.nix b/nixos/modules/services/search/kibana.nix
index ba58630a467a..c096af731ad4 100644
--- a/nixos/modules/services/search/kibana.nix
+++ b/nixos/modules/services/search/kibana.nix
@@ -31,7 +31,7 @@ let
 
 in {
   options.services.kibana = {
-    enable = mkEnableOption "enable kibana service";
+    enable = mkEnableOption "kibana service";
 
     listenAddress = mkOption {
       description = "Kibana listening host";
@@ -129,7 +129,7 @@ in {
 
           This defaults to the singleton list [ca] when the <option>ca</option> option is defined.
         '';
-        default = if isNull cfg.elasticsearch.ca then [] else [ca];
+        default = if cfg.elasticsearch.ca == null then [] else [ca];
         type = types.listOf types.path;
       };
 
diff --git a/nixos/modules/services/search/solr.nix b/nixos/modules/services/search/solr.nix
index 6659cc8a2d1e..5ef7d9893a49 100644
--- a/nixos/modules/services/search/solr.nix
+++ b/nixos/modules/services/search/solr.nix
@@ -11,7 +11,7 @@ in
 {
   options = {
     services.solr = {
-      enable = mkEnableOption "Enables the solr service.";
+      enable = mkEnableOption "Solr";
 
       # default to the 8.x series not forcing major version upgrade of those on the 7.x series
       package = mkOption {
diff --git a/nixos/modules/services/security/bitwarden_rs/backup.sh b/nixos/modules/services/security/bitwarden_rs/backup.sh
new file mode 100644
index 000000000000..264a7da9cbb6
--- /dev/null
+++ b/nixos/modules/services/security/bitwarden_rs/backup.sh
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+
+# Based on: https://github.com/dani-garcia/bitwarden_rs/wiki/Backing-up-your-vault
+if ! mkdir -p "$BACKUP_FOLDER"; then
+  echo "Could not create backup folder '$BACKUP_FOLDER'" >&2
+  exit 1
+fi
+
+if [[ ! -f "$DATA_FOLDER"/db.sqlite3 ]]; then
+  echo "Could not find SQLite database file '$DATA_FOLDER/db.sqlite3'" >&2
+  exit 1
+fi
+
+sqlite3 "$DATA_FOLDER"/db.sqlite3 ".backup '$BACKUP_FOLDER/db.sqlite3'"
+cp "$DATA_FOLDER"/rsa_key.{der,pem,pub.der} "$BACKUP_FOLDER"
+cp -r "$DATA_FOLDER"/attachments "$BACKUP_FOLDER"
+cp -r "$DATA_FOLDER"/icon_cache "$BACKUP_FOLDER"
diff --git a/nixos/modules/services/security/bitwarden_rs/default.nix b/nixos/modules/services/security/bitwarden_rs/default.nix
new file mode 100644
index 000000000000..bb036ee020f4
--- /dev/null
+++ b/nixos/modules/services/security/bitwarden_rs/default.nix
@@ -0,0 +1,126 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.bitwarden_rs;
+  user = config.users.users.bitwarden_rs.name;
+  group = config.users.groups.bitwarden_rs.name;
+
+  # Convert name from camel case (e.g. disable2FARemember) to upper case snake case (e.g. DISABLE_2FA_REMEMBER).
+  nameToEnvVar = name:
+    let
+      parts = builtins.split "([A-Z0-9]+)" name;
+      partsToEnvVar = parts: foldl' (key: x: let last = stringLength key - 1; in
+        if isList x then key + optionalString (key != "" && substring last 1 key != "_") "_" + head x
+        else if key != "" && elem (substring 0 1 x) lowerChars then # to handle e.g. [ "disable" [ "2FAR" ] "emember" ]
+          substring 0 last key + optionalString (substring (last - 1) 1 key != "_") "_" + substring last 1 key + toUpper x
+        else key + toUpper x) "" parts;
+    in if builtins.match "[A-Z0-9_]+" name != null then name else partsToEnvVar parts;
+
+  configFile = pkgs.writeText "bitwarden_rs.env" (concatMapStrings (s: s + "\n") (
+    (concatLists (mapAttrsToList (name: value:
+      if value != null then [ "${nameToEnvVar name}=${if isBool value then boolToString value else toString value}" ] else []
+    ) cfg.config))));
+
+in {
+  options.services.bitwarden_rs = with types; {
+    enable = mkEnableOption "bitwarden_rs";
+
+    backupDir = mkOption {
+      type = nullOr str;
+      default = null;
+      description = ''
+        The directory under which bitwarden_rs will backup its persistent data.
+      '';
+    };
+
+    config = mkOption {
+      type = attrsOf (nullOr (either (either bool int) str));
+      default = {};
+      example = literalExample ''
+        {
+          domain = https://bw.domain.tld:8443;
+          signupsAllowed = true;
+          rocketPort = 8222;
+          rocketLog = "critical";
+        }
+      '';
+      description = ''
+        The configuration of bitwarden_rs is done through environment variables,
+        therefore the names are converted from camel case (e.g. disable2FARemember)
+        to upper case snake case (e.g. DISABLE_2FA_REMEMBER).
+        In this conversion digits (0-9) are handled just like upper case characters,
+        so foo2 would be converted to FOO_2.
+        Names already in this format remain unchanged, so FOO2 remains FOO2 if passed as such,
+        even though foo2 would have been converted to FOO_2.
+        This allows working around any potential future conflicting naming conventions.
+
+        Based on the attributes passed to this config option a environment file will be generated
+        that is passed to bitwarden_rs's systemd service.
+
+        The available configuration options can be found in
+        <link xlink:href="https://github.com/dani-garcia/bitwarden_rs/blob/1.8.0/.env.template">the environment template file</link>.
+      '';
+      apply = config: optionalAttrs config.webVaultEnabled {
+        webVaultFolder = "${pkgs.bitwarden_rs-vault}/share/bitwarden_rs/vault";
+      } // config;
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.bitwarden_rs.config = {
+      dataFolder = "/var/lib/bitwarden_rs";
+      webVaultEnabled = mkDefault true;
+    };
+
+    users.users.bitwarden_rs = { inherit group; };
+    users.groups.bitwarden_rs = { };
+
+    systemd.services.bitwarden_rs = {
+      after = [ "network.target" ];
+      path = with pkgs; [ openssl ];
+      serviceConfig = {
+        User = user;
+        Group = group;
+        EnvironmentFile = configFile;
+        ExecStart = "${pkgs.bitwarden_rs}/bin/bitwarden_rs";
+        LimitNOFILE = "1048576";
+        LimitNPROC = "64";
+        PrivateTmp = "true";
+        PrivateDevices = "true";
+        ProtectHome = "true";
+        ProtectSystem = "strict";
+        AmbientCapabilities = "CAP_NET_BIND_SERVICE";
+        StateDirectory = "bitwarden_rs";
+      };
+      wantedBy = [ "multi-user.target" ];
+    };
+
+    systemd.services.backup-bitwarden_rs = mkIf (cfg.backupDir != null) {
+      description = "Backup bitwarden_rs";
+      environment = {
+        DATA_FOLDER = "/var/lib/bitwarden_rs";
+        BACKUP_FOLDER = cfg.backupDir;
+      };
+      path = with pkgs; [ sqlite ];
+      serviceConfig = {
+        SyslogIdentifier = "backup-bitwarden_rs";
+        User = mkDefault user;
+        Group = mkDefault group;
+        ExecStart = "${pkgs.bash}/bin/bash ${./backup.sh}";
+      };
+      wantedBy = [ "multi-user.target" ];
+    };
+
+    systemd.timers.backup-bitwarden_rs = mkIf (cfg.backupDir != null) {
+      description = "Backup bitwarden_rs on time";
+      timerConfig = {
+        OnCalendar = mkDefault "23:00";
+        Persistent = "true";
+        Unit = "backup-bitwarden_rs.service";
+      };
+      wantedBy = [ "multi-user.target" ];
+    };
+  };
+}
diff --git a/nixos/modules/services/security/fprintd.nix b/nixos/modules/services/security/fprintd.nix
index a35b065ba815..5662ebc61d20 100644
--- a/nixos/modules/services/security/fprintd.nix
+++ b/nixos/modules/services/security/fprintd.nix
@@ -25,29 +25,38 @@ in
         '';
       };
 
+      package = mkOption {
+        type = types.package;
+        default = pkgs.fprintd;
+        defaultText = "pkgs.fprintd";
+        example = "pkgs.fprintd-thinkpad";
+        description = ''
+          fprintd package to use.
+        '';
+      };
+
     };
-    
+
   };
-  
-  
+
+
   ###### implementation
-  
+
   config = mkIf cfg.enable {
 
     services.dbus.packages = [ pkgs.fprintd ];
 
     environment.systemPackages = [ pkgs.fprintd ];
 
-    systemd.services.fprintd = {
-      description = "Fingerprint Authentication Daemon";
+    systemd.packages = [ cfg.package ];
 
-      serviceConfig = {
-        Type = "dbus";
-        BusName = "net.reactivated.Fprint";
-        ExecStart = "${pkgs.fprintd}/libexec/fprintd";
-      };
-    };
+
+    # The upstream unit does not use StateDirectory, and will
+    # fail if the directory it needs is not present. Should be
+    # fixed when https://gitlab.freedesktop.org/libfprint/fprintd/merge_requests/5
+    # is merged.
+    systemd.services.fprintd.serviceConfig.StateDirectory = "fprint";
 
   };
-  
+
 }
diff --git a/nixos/modules/services/security/munge.nix b/nixos/modules/services/security/munge.nix
index 504bc66c6d10..1c4f8e20552f 100644
--- a/nixos/modules/services/security/munge.nix
+++ b/nixos/modules/services/security/munge.nix
@@ -49,21 +49,16 @@ in
 
       path = [ pkgs.munge pkgs.coreutils ];
 
-      preStart = ''
-        chmod 0400 ${cfg.password}
-        mkdir -p /var/lib/munge -m 0711
-        chown -R munge:munge /var/lib/munge
-        mkdir -p /run/munge -m 0755
-        chown -R munge:munge /run/munge
-      '';
-
       serviceConfig = {
+        ExecStartPre = "+${pkgs.coreutils}/bin/chmod 0400 ${cfg.password}";
         ExecStart = "${pkgs.munge}/bin/munged --syslog --key-file ${cfg.password}";
         PIDFile = "/run/munge/munged.pid";
         ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
-        PermissionsStartOnly = "true";
         User = "munge";
         Group = "munge";
+        StateDirectory = "munge";
+        StateDirectoryMode = "0711";
+        RuntimeDirectory = "munge";
       };
 
     };
diff --git a/nixos/modules/services/security/oauth2_proxy.nix b/nixos/modules/services/security/oauth2_proxy.nix
index 0c5fe8c0ef5f..61f203ef9e7d 100644
--- a/nixos/modules/services/security/oauth2_proxy.nix
+++ b/nixos/modules/services/security/oauth2_proxy.nix
@@ -58,11 +58,11 @@ let
       httponly = cookie.httpOnly;
     };
     set-xauthrequest = setXauthrequest;
-  } // lib.optionalAttrs (!isNull cfg.email.addresses) {
+  } // lib.optionalAttrs (cfg.email.addresses != null) {
     authenticated-emails-file = authenticatedEmailsFile;
   } // lib.optionalAttrs (cfg.passBasicAuth) {
     basic-auth-password = cfg.basicAuthPassword;
-  } // lib.optionalAttrs (!isNull cfg.htpasswd.file) {
+  } // lib.optionalAttrs (cfg.htpasswd.file != null) {
     display-htpasswd-file = cfg.htpasswd.displayForm;
   } // lib.optionalAttrs tls.enable {
     tls-cert = tls.certificate;
@@ -71,7 +71,7 @@ let
   } // (getProviderOptions cfg cfg.provider) // cfg.extraConfig;
 
   mapConfig = key: attr:
-  if (!isNull attr && attr != []) then (
+  if attr != null && attr != [] then (
     if isDerivation attr then mapConfig key (toString attr) else
     if (builtins.typeOf attr) == "set" then concatStringsSep " "
       (mapAttrsToList (name: value: mapConfig (key + "-" + name) value) attr) else
@@ -538,7 +538,7 @@ in
 
   config = mkIf cfg.enable {
 
-    services.oauth2_proxy = mkIf (!isNull cfg.keyFile) {
+    services.oauth2_proxy = mkIf (cfg.keyFile != null) {
       clientID = mkDefault null;
       clientSecret = mkDefault null;
       cookie.secret = mkDefault null;
diff --git a/nixos/modules/services/security/sks.nix b/nixos/modules/services/security/sks.nix
index 8136a5c763a9..1b7a2ad13980 100644
--- a/nixos/modules/services/security/sks.nix
+++ b/nixos/modules/services/security/sks.nix
@@ -116,20 +116,22 @@ in {
           ${lib.optionalString (cfg.webroot != null)
             "ln -sfT \"${cfg.webroot}\" web"}
           mkdir -p dump
-          # Check that both database configs are symlinks before overwriting them
-          if [ -e KDB/DB_CONFIG ] && [ ! -L KBD/DB_CONFIG ]; then
-            echo "KDB/DB_CONFIG exists but is not a symlink." >&2
-            exit 1
-          fi
-          if [ -e PTree/DB_CONFIG ] && [ ! -L PTree/DB_CONFIG ]; then
-            echo "PTree/DB_CONFIG exists but is not a symlink." >&2
-            exit 1
-          fi
-          ln -sf ${dbConfig} KDB/DB_CONFIG
-          ln -sf ${dbConfig} PTree/DB_CONFIG
           ${sksPkg}/bin/sks build dump/*.gpg -n 10 -cache 100 || true #*/
           ${sksPkg}/bin/sks cleandb || true
           ${sksPkg}/bin/sks pbuild -cache 20 -ptree_cache 70 || true
+          # Check that both database configs are symlinks before overwriting them
+          # TODO: The initial build will be without DB_CONFIG, but this will
+          # hopefully not cause any significant problems. It might be better to
+          # create both directories manually but we have to check that this does
+          # not affect the initial build of the DB.
+          for CONFIG_FILE in KDB/DB_CONFIG PTree/DB_CONFIG; do
+            if [ -e $CONFIG_FILE ] && [ ! -L $CONFIG_FILE ]; then
+              echo "$CONFIG_FILE exists but is not a symlink." >&2
+              echo "Please remove $PWD/$CONFIG_FILE manually to continue." >&2
+              exit 1
+            fi
+            ln -sf ${dbConfig} $CONFIG_FILE
+          done
         '';
         serviceConfig = {
           WorkingDirectory = "~";
diff --git a/nixos/modules/services/security/tor.nix b/nixos/modules/services/security/tor.nix
index 61b751bb518b..6f4852c3ba1a 100644
--- a/nixos/modules/services/security/tor.nix
+++ b/nixos/modules/services/security/tor.nix
@@ -81,7 +81,7 @@ let
 
     ${optionalString (elem cfg.relay.role ["bridge" "private-bridge"]) ''
       BridgeRelay 1
-      ServerTransportPlugin obfs2,obfs3 exec ${pkgs.pythonPackages.obfsproxy}/bin/obfsproxy managed
+      ServerTransportPlugin ${concatStringsSep "," cfg.relay.bridgeTransports} exec ${obfs4}/bin/obfs4proxy managed
       ExtORPort auto
       ${optionalString (cfg.relay.role == "private-bridge") ''
         ExtraInfoStatistics 0
@@ -355,7 +355,7 @@ in
                 <para>
                   Regular bridge. Works like a regular relay, but
                   doesn't list you in the public relay directory and
-                  hides your Tor node behind obfsproxy.
+                  hides your Tor node behind obfs4proxy.
                 </para>
 
                 <para>
@@ -424,6 +424,13 @@ in
           '';
         };
 
+        bridgeTransports = mkOption {
+          type = types.listOf types.str;
+          default = ["obfs4"];
+          example = ["obfs2" "obfs3" "obfs4" "scramblesuit"];
+          description = "List of pluggable transports";
+        };
+
         nickname = mkOption {
           type = types.str;
           default = "anonymous";
diff --git a/nixos/modules/services/security/vault.nix b/nixos/modules/services/security/vault.nix
index 0b28bc894458..8176c168ca94 100644
--- a/nixos/modules/services/security/vault.nix
+++ b/nixos/modules/services/security/vault.nix
@@ -119,6 +119,10 @@ in
     };
     users.groups.vault.gid = config.ids.gids.vault;
 
+    systemd.tmpfiles.rules = optional (cfg.storagePath != null) [
+      "d '${cfg.storagePath}' 0700 vault vault - -"
+    ];
+
     systemd.services.vault = {
       description = "Vault server daemon";
 
@@ -128,14 +132,9 @@ in
 
       restartIfChanged = false; # do not restart on "nixos-rebuild switch". It would seal the storage and disrupt the clients.
 
-      preStart = optionalString (cfg.storagePath != null) ''
-        install -d -m0700 -o vault -g vault "${cfg.storagePath}"
-      '';
-
       serviceConfig = {
         User = "vault";
         Group = "vault";
-        PermissionsStartOnly = true;
         ExecStart = "${cfg.package}/bin/vault server -config ${configFile}";
         PrivateDevices = true;
         PrivateTmp = true;
diff --git a/nixos/modules/services/system/earlyoom.nix b/nixos/modules/services/system/earlyoom.nix
index daa46838bfa8..39d1bf274bd2 100644
--- a/nixos/modules/services/system/earlyoom.nix
+++ b/nixos/modules/services/system/earlyoom.nix
@@ -63,6 +63,17 @@ in
           Enable debugging messages.
         '';
       };
+
+      notificationsCommand = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "sudo -u example_user DISPLAY=:0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus notify-send";
+        description = ''
+          Command used to send notifications.
+
+          See <link xlink:href="https://github.com/rfjakob/earlyoom#notifications">README</link> for details.
+        '';
+      };
     };
   };
 
@@ -88,7 +99,9 @@ in
           -s ${toString ecfg.freeSwapThreshold} \
           ${optionalString ecfg.useKernelOOMKiller "-k"} \
           ${optionalString ecfg.ignoreOOMScoreAdjust "-i"} \
-          ${optionalString ecfg.enableDebugInfo "-d"}
+          ${optionalString ecfg.enableDebugInfo "-d"} \
+          ${optionalString (ecfg.notificationsCommand != null)
+            "-N ${escapeShellArg ecfg.notificationsCommand}"}
         '';
       };
     };
diff --git a/nixos/modules/services/system/localtime.nix b/nixos/modules/services/system/localtime.nix
index c7e897c96448..8e9286b94078 100644
--- a/nixos/modules/services/system/localtime.nix
+++ b/nixos/modules/services/system/localtime.nix
@@ -20,7 +20,13 @@ in {
   };
 
   config = mkIf cfg.enable {
-    services.geoclue2.enable = true;
+    services.geoclue2 = {
+      enable = true;
+      appConfig."localtime" = {
+        isAllowed = true;
+        isSystem = true;
+      };
+    };
 
     # so polkit will pick up the rules
     environment.systemPackages = [ pkgs.localtime ];
diff --git a/nixos/modules/services/torrent/deluge.nix b/nixos/modules/services/torrent/deluge.nix
index 84f0437b9411..01a5890a7845 100644
--- a/nixos/modules/services/torrent/deluge.nix
+++ b/nixos/modules/services/torrent/deluge.nix
@@ -5,8 +5,33 @@ with lib;
 let
   cfg = config.services.deluge;
   cfg_web = config.services.deluge.web;
+
   openFilesLimit = 4096;
+  listenPortsDefault = [ 6881 6889 ];
+
+  listToRange = x: { from = elemAt x 0; to = elemAt x 1; };
+
+  configDir = "${cfg.dataDir}/.config/deluge";
+  configFile = pkgs.writeText "core.conf" (builtins.toJSON cfg.config);
+  declarativeLockFile = "${configDir}/.declarative";
 
+  preStart = if cfg.declarative then ''
+    if [ -e ${declarativeLockFile} ]; then
+      # Was declarative before, no need to back up anything
+      ln -sf ${configFile} ${configDir}/core.conf
+      ln -sf ${cfg.authFile} ${configDir}/auth
+    else
+      # Declarative for the first time, backup stateful files
+      ln -sb --suffix=.stateful ${configFile} ${configDir}/core.conf
+      ln -sb --suffix=.stateful ${cfg.authFile} ${configDir}/auth
+      echo "Autogenerated file that signifies that this server configuration is managed declaratively by NixOS" \
+        > ${declarativeLockFile}
+    fi
+  '' else ''
+    if [ -e ${declarativeLockFile} ]; then
+      rm ${declarativeLockFile}
+    fi
+  '';
 in {
   options = {
     services = {
@@ -15,42 +40,151 @@ in {
 
         openFilesLimit = mkOption {
           default = openFilesLimit;
-          example = 8192;
           description = ''
             Number of files to allow deluged to open.
           '';
         };
+
+        config = mkOption {
+          type = types.attrs;
+          default = {};
+          example = literalExample ''
+            {
+              download_location = "/srv/torrents/";
+              max_upload_speed = "1000.0";
+              share_ratio_limit = "2.0";
+              allow_remote = true;
+              daemon_port = 58846;
+              listen_ports = [ ${toString listenPortsDefault} ];
+            }
+          '';
+          description = ''
+            Deluge core configuration for the core.conf file. Only has an effect
+            when <option>services.deluge.declarative</option> is set to
+            <literal>true</literal>. String values must be quoted, integer and
+            boolean values must not. See
+            <link xlink:href="https://git.deluge-torrent.org/deluge/tree/deluge/core/preferencesmanager.py#n41"/>
+            for the availaible options.
+          '';
+        };
+
+        declarative = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Whether to use a declarative deluge configuration.
+            Only if set to <literal>true</literal>, the options
+            <option>services.deluge.config</option>,
+            <option>services.deluge.openFirewall</option> and
+            <option>services.deluge.authFile</option> will be
+            applied.
+          '';
+        };
+
+        openFirewall = mkOption {
+          default = false;
+          type = types.bool;
+          description = ''
+            Whether to open the firewall for the ports in
+            <option>services.deluge.config.listen_ports</option>. It only takes effet if
+            <option>services.deluge.declarative</option> is set to
+            <literal>true</literal>.
+
+            It does NOT apply to the daemon port nor the web UI port. To access those
+            ports secuerly check the documentation
+            <link xlink:href="https://dev.deluge-torrent.org/wiki/UserGuide/ThinClient#CreateSSHTunnel"/>
+            or use a VPN or configure certificates for deluge.
+          '';
+        };
+
+        dataDir = mkOption {
+          type = types.path;
+          default = "/var/lib/deluge";
+          description = ''
+            The directory where deluge will create files.
+          '';
+        };
+
+        authFile = mkOption {
+          type = types.path;
+          example = "/run/keys/deluge-auth";
+          description = ''
+            The file managing the authentication for deluge, the format of this
+            file is straightforward, each line contains a
+            username:password:level tuple in plaintext. It only has an effect
+            when <option>services.deluge.declarative</option> is set to
+            <literal>true</literal>.
+            See <link xlink:href="https://dev.deluge-torrent.org/wiki/UserGuide/Authentication"/> for
+            more informations.
+          '';
+        };
       };
 
-      deluge.web.enable = mkEnableOption "Deluge Web daemon";
+      deluge.web = {
+        enable = mkEnableOption "Deluge Web daemon";
+        port = mkOption {
+        type = types.port;
+          default = 8112;
+          description = ''
+            Deluge web UI port.
+          '';
+        };
+      };
     };
   };
 
   config = mkIf cfg.enable {
 
+    systemd.tmpfiles.rules = [ "d '${configDir}' 0770 deluge deluge" ]
+    ++ optional (cfg.config ? "download_location")
+      "d '${cfg.config.download_location}' 0770 deluge deluge"
+    ++ optional (cfg.config ? "torrentfiles_location")
+      "d '${cfg.config.torrentfiles_location}' 0770 deluge deluge"
+    ++ optional (cfg.config ? "move_completed_path")
+      "d '${cfg.config.move_completed_path}' 0770 deluge deluge";
+
     systemd.services.deluged = {
       after = [ "network.target" ];
       description = "Deluge BitTorrent Daemon";
       wantedBy = [ "multi-user.target" ];
       path = [ pkgs.deluge ];
       serviceConfig = {
-        ExecStart = "${pkgs.deluge}/bin/deluged -d";
-        # To prevent "Quit & shutdown daemon" from working; we want systemd to manage it!
+        ExecStart = ''
+          ${pkgs.deluge}/bin/deluged \
+            --do-not-daemonize \
+            --config ${configDir}
+        '';
+        # To prevent "Quit & shutdown daemon" from working; we want systemd to
+        # manage it!
         Restart = "on-success";
         User = "deluge";
         Group = "deluge";
+        UMask = "0002";
         LimitNOFILE = cfg.openFilesLimit;
       };
+      preStart = preStart;
     };
 
     systemd.services.delugeweb = mkIf cfg_web.enable {
-      after = [ "network.target" ];
+      after = [ "network.target" "deluged.service"];
+      requires = [ "deluged.service" ];
       description = "Deluge BitTorrent WebUI";
       wantedBy = [ "multi-user.target" ];
       path = [ pkgs.deluge ];
-      serviceConfig.ExecStart = "${pkgs.deluge}/bin/deluge --ui web";
-      serviceConfig.User = "deluge";
-      serviceConfig.Group = "deluge";
+      serviceConfig = {
+        ExecStart = ''
+          ${pkgs.deluge}/bin/deluge-web \
+            --config ${configDir} \
+            --port ${toString cfg.web.port}
+        '';
+        User = "deluge";
+        Group = "deluge";
+      };
+    };
+
+    networking.firewall = mkIf (cfg.declarative && cfg.openFirewall && !(cfg.config.random_port or true)) {
+      allowedTCPPortRanges = singleton (listToRange (cfg.config.listen_ports or listenPortsDefault));
+      allowedUDPPortRanges = singleton (listToRange (cfg.config.listen_ports or listenPortsDefault));
     };
 
     environment.systemPackages = [ pkgs.deluge ];
@@ -58,7 +192,7 @@ in {
     users.users.deluge = {
       group = "deluge";
       uid = config.ids.uids.deluge;
-      home = "/var/lib/deluge/";
+      home = cfg.dataDir;
       createHome = true;
       description = "Deluge Daemon user";
     };
diff --git a/nixos/modules/services/torrent/peerflix.nix b/nixos/modules/services/torrent/peerflix.nix
index bed6661f84d6..a74f65984328 100644
--- a/nixos/modules/services/torrent/peerflix.nix
+++ b/nixos/modules/services/torrent/peerflix.nix
@@ -39,6 +39,10 @@ in {
   ###### implementation
 
   config = mkIf cfg.enable {
+    systemd.tmpfiles.rules = [
+      "d '${cfg.stateDir}' - peerflix - - -"
+    ];
+
     systemd.services.peerflix = {
       description = "Peerflix Daemon";
       wantedBy = [ "multi-user.target" ];
@@ -47,13 +51,11 @@ in {
 
       preStart = ''
         mkdir -p "${cfg.stateDir}"/{torrents,.config/peerflix-server}
-        if [ "$(id -u)" = 0 ]; then chown -R peerflix "${cfg.stateDir}"; fi
         ln -fs "${configFile}" "${cfg.stateDir}/.config/peerflix-server/config.json"
       '';
 
       serviceConfig = {
         ExecStart = "${pkgs.nodePackages.peerflix-server}/bin/peerflix-server";
-        PermissionsStartOnly = true;
         User = "peerflix";
       };
     };
diff --git a/nixos/modules/services/web-apps/codimd.nix b/nixos/modules/services/web-apps/codimd.nix
index ee2fc2b9d857..7ae7cd9c52d8 100644
--- a/nixos/modules/services/web-apps/codimd.nix
+++ b/nixos/modules/services/web-apps/codimd.nix
@@ -899,10 +899,6 @@ in
       description = "CodiMD Service";
       wantedBy = [ "multi-user.target" ];
       after = [ "networking.target" ];
-      preStart = ''
-        mkdir -p ${cfg.workDir}
-        chown -R codimd: ${cfg.workDir}
-      '';
       serviceConfig = {
         WorkingDirectory = cfg.workDir;
         ExecStart = "${pkgs.codimd}/bin/codimd";
@@ -912,7 +908,6 @@ in
         ];
         Restart = "always";
         User = "codimd";
-        PermissionsStartOnly = true;
         PrivateTmp = true;
       };
     };
diff --git a/nixos/modules/services/web-apps/cryptpad.nix b/nixos/modules/services/web-apps/cryptpad.nix
new file mode 100644
index 000000000000..69a89107d310
--- /dev/null
+++ b/nixos/modules/services/web-apps/cryptpad.nix
@@ -0,0 +1,54 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.cryptpad;
+in
+{
+  options.services.cryptpad = {
+    enable = mkEnableOption "the Cryptpad service";
+
+    package = mkOption {
+      default = pkgs.cryptpad;
+      defaultText = "pkgs.cryptpad";
+      type = types.package;
+      description = "
+        Cryptpad package to use.
+      ";
+    };
+
+    configFile = mkOption {
+      type = types.path;
+      default = "${cfg.package}/lib/node_modules/cryptpad/config/config.example.js";
+      defaultText = "\${cfg.package}/lib/node_modules/cryptpad/config/config.example.js";
+      description = ''
+        Path to the JavaScript configuration file.
+
+        See <link
+        xlink:href="https://github.com/xwiki-labs/cryptpad/blob/master/config/config.example.js"/>
+        for a configuration example.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.cryptpad = {
+      description = "Cryptpad Service";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "networking.target" ];
+      serviceConfig = {
+        DynamicUser = true;
+        Environment = [
+          "CRYPTPAD_CONFIG=${cfg.configFile}"
+          "HOME=%S/cryptpad"
+        ];
+        ExecStart = "${cfg.package}/bin/cryptpad";
+        PrivateTmp = true;
+        Restart = "always";
+        StateDirectory = "cryptpad";
+        WorkingDirectory = "%S/cryptpad";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/web-apps/documize.nix b/nixos/modules/services/web-apps/documize.nix
index 206617b0e5ac..37359869cb64 100644
--- a/nixos/modules/services/web-apps/documize.nix
+++ b/nixos/modules/services/web-apps/documize.nix
@@ -3,65 +3,136 @@
 with lib;
 
 let
-
   cfg = config.services.documize;
 
-in
+  mkParams = optional: concatMapStrings (name: let
+    predicate = optional -> cfg.${name} != null;
+    template = " -${name} '${toString cfg.${name}}'";
+  in optionalString predicate template);
 
-  {
-    options.services.documize = {
-      enable = mkEnableOption "Documize Wiki";
+in {
+  options.services.documize = {
+    enable = mkEnableOption "Documize Wiki";
 
-      offline = mkEnableOption "Documize offline mode";
+    package = mkOption {
+      type = types.package;
+      default = pkgs.documize-community;
+      description = ''
+        Which package to use for documize.
+      '';
+    };
 
-      package = mkOption {
-        default = pkgs.documize-community;
-        type = types.package;
-        description = ''
-          Which package to use for documize.
-        '';
-      };
+    salt = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "3edIYV6c8B28b19fh";
+      description = ''
+        The salt string used to encode JWT tokens, if not set a random value will be generated.
+      '';
+    };
 
-      db = mkOption {
-        type = types.str;
-        example = "host=localhost port=5432 sslmode=disable user=admin password=secret dbname=documize";
-        description = ''
-          The DB connection string to use for the database.
-        '';
-      };
+    cert = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        The <filename>cert.pem</filename> file used for https.
+      '';
+    };
 
-      dbtype = mkOption {
-        type = types.enum [ "postgresql" "percona" "mariadb" "mysql" ];
-        description = ''
-          Which database to use for storage.
-        '';
-      };
+    key = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        The <filename>key.pem</filename> file used for https.
+      '';
+    };
 
-      port = mkOption {
-        type = types.port;
-        example = 3000;
-        description = ''
-          Which TCP port to serve.
-        '';
-      };
+    port = mkOption {
+      type = types.port;
+      default = 5001;
+      description = ''
+        The http/https port number.
+      '';
+    };
+
+    forcesslport = mkOption {
+      type = types.nullOr types.port;
+      default = null;
+      description = ''
+        Redirect given http port number to TLS.
+      '';
+    };
+
+    offline = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Set <literal>true</literal> for offline mode.
+      '';
+      apply = v: if true == v then 1 else 0;
+    };
+
+    dbtype = mkOption {
+      type = types.enum [ "mysql" "percona" "mariadb" "postgresql" "sqlserver" ];
+      default = "postgresql";
+      description = ''
+        Specify the database provider:
+        <simplelist type='inline'>
+          <member><literal>mysql</literal></member>
+          <member><literal>percona</literal></member>
+          <member><literal>mariadb</literal></member>
+          <member><literal>postgresql</literal></member>
+          <member><literal>sqlserver</literal></member>
+        </simplelist>
+      '';
     };
 
-    config = mkIf cfg.enable {
-      systemd.services.documize-server = {
-        wantedBy = [ "multi-user.target" ];
-
-        script = ''
-          ${cfg.package}/bin/documize \
-            -db "${cfg.db}" \
-            -dbtype ${cfg.dbtype} \
-            -port ${toString cfg.port} \
-            -offline ${if cfg.offline then "1" else "0"}
-        '';
-
-        serviceConfig = {
-          Restart = "always";
-          DynamicUser = "yes";
-        };
+    db = mkOption {
+      type = types.str;
+      description = ''
+        Database specific connection string for example:
+        <itemizedlist>
+        <listitem><para>MySQL/Percona/MariaDB:
+          <literal>user:password@tcp(host:3306)/documize</literal>
+        </para></listitem>
+        <listitem><para>MySQLv8+:
+          <literal>user:password@tcp(host:3306)/documize?allowNativePasswords=true</literal>
+        </para></listitem>
+        <listitem><para>PostgreSQL:
+          <literal>host=localhost port=5432 dbname=documize user=admin password=secret sslmode=disable</literal>
+        </para></listitem>
+        <listitem><para>MSSQL:
+          <literal>sqlserver://username:password@localhost:1433?database=Documize</literal> or
+          <literal>sqlserver://sa@localhost/SQLExpress?database=Documize</literal>
+        </para></listitem>
+        </itemizedlist>
+      '';
+    };
+
+    location = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        reserved
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.documize-server = {
+      description = "Documize Wiki";
+      documentation = [ https://documize.com/ ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        ExecStart = concatStringsSep " " [
+          "${cfg.package}/bin/documize"
+          (mkParams false [ "db" "dbtype" "port" ])
+          (mkParams true [ "offline" "location" "forcesslport" "key" "cert" "salt" ])
+        ];
+        Restart = "always";
+        DynamicUser = "yes";
       };
     };
-  }
+  };
+}
diff --git a/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix b/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix
index 644aad82df2c..910e1d937bf3 100644
--- a/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix
+++ b/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix
@@ -215,7 +215,7 @@ in {
 
     # /etc/icingaweb2
     environment.etc = let
-      doModule = name: optionalAttrs (cfg.modules."${name}".enable) (nameValuePair "icingaweb2/enabledModules/${name}" { source = "${pkgs.icingaweb2}/modules/${name}"; });
+      doModule = name: optionalAttrs (cfg.modules."${name}".enable) { "icingaweb2/enabledModules/${name}".source = "${pkgs.icingaweb2}/modules/${name}"; };
     in {}
       # Module packages
       // (mapAttrs' (k: v: nameValuePair "icingaweb2/enabledModules/${k}" { source = v; }) cfg.modulePackages)
diff --git a/nixos/modules/services/web-apps/limesurvey.nix b/nixos/modules/services/web-apps/limesurvey.nix
new file mode 100644
index 000000000000..f9e12e3642ea
--- /dev/null
+++ b/nixos/modules/services/web-apps/limesurvey.nix
@@ -0,0 +1,288 @@
+{ config, lib, pkgs, ... }:
+
+let
+
+  inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption;
+  inherit (lib) mapAttrs optional optionalString types;
+
+  cfg = config.services.limesurvey;
+
+  user = "limesurvey";
+  group = config.services.httpd.group;
+  stateDir = "/var/lib/limesurvey";
+
+  php = pkgs.php;
+  pkg = pkgs.limesurvey;
+
+  configType = with types; either (either (attrsOf configType) str) (either int bool) // {
+    description = "limesurvey config type (str, int, bool or attribute set thereof)";
+  };
+
+  limesurveyConfig = pkgs.writeText "config.php" ''
+    <?php
+      return json_decode('${builtins.toJSON cfg.config}', true);
+    ?>
+  '';
+
+  mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql";
+  pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql";
+
+in
+{
+  # interface
+
+  options.services.limesurvey = {
+    enable = mkEnableOption "Limesurvey web application.";
+
+    database = {
+      type = mkOption {
+        type = types.enum [ "mysql" "pgsql" "odbc" "mssql" ];
+        example = "pgsql";
+        default = "mysql";
+        description = "Database engine to use.";
+      };
+
+      host = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = "Database host address.";
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = if cfg.database.type == "pgsql" then 5442 else 3306;
+        defaultText = "3306";
+        description = "Database host port.";
+      };
+
+      name = mkOption {
+        type = types.str;
+        default = "limesurvey";
+        description = "Database name.";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "limesurvey";
+        description = "Database user.";
+      };
+
+      passwordFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/run/keys/limesurvey-dbpassword";
+        description = ''
+          A file containing the password corresponding to
+          <option>database.user</option>.
+        '';
+      };
+
+      socket = mkOption {
+        type = types.nullOr types.path;
+        default =
+          if mysqlLocal then "/run/mysqld/mysqld.sock"
+          else if pgsqlLocal then "/run/postgresql"
+          else null
+        ;
+        defaultText = "/run/mysqld/mysqld.sock";
+        description = "Path to the unix socket file to use for authentication.";
+      };
+
+      createLocally = mkOption {
+        type = types.bool;
+        default = cfg.database.type == "mysql";
+        defaultText = "true";
+        description = ''
+          Create the database and database user locally.
+          This currently only applies if database type "mysql" is selected.
+        '';
+      };
+    };
+
+    virtualHost = mkOption {
+      type = types.submodule ({
+        options = import ../web-servers/apache-httpd/per-server-options.nix {
+          inherit lib;
+          forMainServer = false;
+        };
+      });
+      example = {
+        hostName = "survey.example.org";
+        enableSSL = true;
+        adminAddr = "webmaster@example.org";
+        sslServerCert = "/var/lib/acme/survey.example.org/full.pem";
+        sslServerKey = "/var/lib/acme/survey.example.org/key.pem";
+      };
+      description = ''
+        Apache configuration can be done by adapting <literal>services.httpd.virtualHosts.&lt;name&gt;</literal>.
+        See <xref linkend="opt-services.httpd.virtualHosts"/> for further information.
+      '';
+    };
+
+    poolConfig = mkOption {
+      type = types.lines;
+      default = ''
+        pm = dynamic
+        pm.max_children = 32
+        pm.start_servers = 2
+        pm.min_spare_servers = 2
+        pm.max_spare_servers = 4
+        pm.max_requests = 500
+      '';
+      description = ''
+        Options for LimeSurvey's PHP pool. See the documentation on <literal>php-fpm.conf</literal> for details on configuration directives.
+      '';
+    };
+
+    config = mkOption {
+      type = configType;
+      default = {};
+      description = ''
+        LimeSurvey configuration. Refer to
+        <link xlink:href="https://manual.limesurvey.org/Optional_settings"/>
+        for details on supported values.
+      '';
+    };
+  };
+
+  # implementation
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion = cfg.database.createLocally -> cfg.database.type == "mysql";
+        message = "services.limesurvey.createLocally is currently only supported for database type 'mysql'";
+      }
+      { assertion = cfg.database.createLocally -> cfg.database.user == user;
+        message = "services.limesurvey.database.user must be set to ${user} if services.limesurvey.database.createLocally is set true";
+      }
+      { assertion = cfg.database.createLocally -> cfg.database.socket != null;
+        message = "services.limesurvey.database.socket must be set if services.limesurvey.database.createLocally is set to true";
+      }
+      { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
+        message = "a password cannot be specified if services.limesurvey.database.createLocally is set to true";
+      }
+    ];
+
+    services.limesurvey.config = mapAttrs (name: mkDefault) {
+      runtimePath = "${stateDir}/tmp/runtime";
+      components = {
+        db = {
+          connectionString = "${cfg.database.type}:dbname=${cfg.database.name};host=${if pgsqlLocal then cfg.database.socket else cfg.database.host};port=${toString cfg.database.port}" +
+            optionalString mysqlLocal ";socket=${cfg.database.socket}";
+          username = cfg.database.user;
+          password = mkIf (cfg.database.passwordFile != null) "file_get_contents(\"${toString cfg.database.passwordFile}\");";
+          tablePrefix = "limesurvey_";
+        };
+        assetManager.basePath = "${stateDir}/tmp/assets";
+        urlManager = {
+          urlFormat = "path";
+          showScriptName = false;
+        };
+      };
+      config = {
+        tempdir = "${stateDir}/tmp";
+        uploaddir = "${stateDir}/upload";
+        force_ssl = mkIf cfg.virtualHost.enableSSL "on";
+        config.defaultlang = "en";
+      };
+    };
+
+    services.mysql = mkIf mysqlLocal {
+      enable = true;
+      package = mkDefault pkgs.mariadb;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.database.user;
+          ensurePermissions = {
+            "${cfg.database.name}.*" = "SELECT, CREATE, INSERT, UPDATE, DELETE, ALTER, DROP, INDEX";
+          };
+        }
+      ];
+    };
+
+    services.phpfpm.pools.limesurvey = {
+      phpPackage = php;
+      listen = "/run/phpfpm/limesurvey.sock";
+      extraConfig = ''
+        listen.owner = ${config.services.httpd.user};
+        listen.group = ${config.services.httpd.group};
+        user = ${user};
+        group = ${group};
+
+        env[LIMESURVEY_CONFIG] = ${limesurveyConfig}
+
+        ${cfg.poolConfig}
+      '';
+    };
+
+    services.httpd = {
+      enable = true;
+      adminAddr = mkDefault cfg.virtualHost.adminAddr;
+      extraModules = [ "proxy_fcgi" ];
+      virtualHosts = [
+        (cfg.virtualHost // {
+          documentRoot = mkForce "${pkg}/share/limesurvey";
+          extraConfig = ''
+            Alias "/tmp" "${stateDir}/tmp"
+            <Directory "${stateDir}">
+              AllowOverride all
+              Require all granted
+              Options -Indexes +FollowSymlinks
+            </Directory>
+
+            Alias "/upload" "${stateDir}/upload"
+            <Directory "${stateDir}/upload">
+              AllowOverride all
+              Require all granted
+              Options -Indexes
+            </Directory>
+
+            <Directory "${pkg}/share/limesurvey">
+              <FilesMatch "\.php$">
+                <If "-f %{REQUEST_FILENAME}">
+                  SetHandler "proxy:unix:/run/phpfpm/limesurvey.sock|fcgi://localhost/"
+                </If>
+              </FilesMatch>
+
+              AllowOverride all
+              Options -Indexes
+              DirectoryIndex index.php
+            </Directory>
+          '';
+        })
+      ];
+    };
+
+    systemd.tmpfiles.rules = [
+      "d ${stateDir} 0750 ${user} ${group} - -"
+      "d ${stateDir}/tmp 0750 ${user} ${group} - -"
+      "d ${stateDir}/tmp/assets 0750 ${user} ${group} - -"
+      "d ${stateDir}/tmp/runtime 0750 ${user} ${group} - -"
+      "d ${stateDir}/tmp/upload 0750 ${user} ${group} - -"
+      "C ${stateDir}/upload 0750 ${user} ${group} - ${pkg}/share/limesurvey/upload"
+    ];
+
+    systemd.services.limesurvey-init = {
+      wantedBy = [ "multi-user.target" ];
+      before = [ "phpfpm-limesurvey.service" ];
+      after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
+      environment.LIMESURVEY_CONFIG = limesurveyConfig;
+      script = ''
+        # update or install the database as required
+        ${php}/bin/php ${pkg}/share/limesurvey/application/commands/console.php updatedb || \
+        ${php}/bin/php ${pkg}/share/limesurvey/application/commands/console.php install admin password admin admin@example.com verbose
+      '';
+      serviceConfig = {
+        User = user;
+        Group = group;
+        Type = "oneshot";
+      };
+    };
+
+    systemd.services.httpd.after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
+
+    users.users."${user}".group = group;
+
+  };
+}
diff --git a/nixos/modules/services/web-apps/miniflux.nix b/nixos/modules/services/web-apps/miniflux.nix
index 1d60004e574d..304712d0efc3 100644
--- a/nixos/modules/services/web-apps/miniflux.nix
+++ b/nixos/modules/services/web-apps/miniflux.nix
@@ -85,7 +85,7 @@ in
         DynamicUser = true;
         RuntimeDirectory = "miniflux";
         RuntimeDirectoryMode = "0700";
-        EnvironmentFile = if isNull cfg.adminCredentialsFile
+        EnvironmentFile = if cfg.adminCredentialsFile == null
         then defaultCredentials
         else cfg.adminCredentialsFile;
       };
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
index d0e45e1c12a5..bb39a5d1d714 100644
--- a/nixos/modules/services/web-apps/nextcloud.nix
+++ b/nixos/modules/services/web-apps/nextcloud.nix
@@ -257,6 +257,23 @@ in {
         '';
       };
     };
+    autoUpdateApps = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Run regular auto update of all apps installed from the nextcloud app store.
+        '';
+      };
+      startAt = mkOption {
+        type = with types; either str (listOf str);
+        default = "05:00:00";
+        example = "Sun 14:00:00";
+        description = ''
+          When to run the update. See `systemd.services.&lt;name&gt;.startAt`.
+        '';
+      };
+    };
   };
 
   config = mkIf cfg.enable (mkMerge [
@@ -362,6 +379,11 @@ in {
           serviceConfig.User = "nextcloud";
           serviceConfig.ExecStart = "${phpPackage}/bin/php -f ${pkgs.nextcloud}/cron.php";
         };
+        "nextcloud-update-plugins" = mkIf cfg.autoUpdateApps.enable {
+          serviceConfig.Type = "oneshot";
+          serviceConfig.ExecStart = "${occ}/bin/nextcloud-occ app:update --all";
+          startAt = cfg.autoUpdateApps.startAt;
+        };
       };
 
       services.phpfpm = {
diff --git a/nixos/modules/services/web-apps/nextcloud.xml b/nixos/modules/services/web-apps/nextcloud.xml
index dfefa55c5d5e..d78d866086a6 100644
--- a/nixos/modules/services/web-apps/nextcloud.xml
+++ b/nixos/modules/services/web-apps/nextcloud.xml
@@ -111,5 +111,11 @@
    <link xlink:href="https://github.com/NixOS/nixpkgs/issues/49783">#49783</link>,
    for now it's unfortunately necessary to manually work around these issues.
   </para>
+
+  <para>
+   Right now app installation and configuration is done imperatively in the nextcloud web ui or via the <literal>nextcloud-occ</literal> command line utility.
+   You can activate auto updates for your apps via
+   <literal><link linkend="opt-services.nextcloud.autoUpdateApps.enable">services.nextcloud.autoUpdateApps</link></literal>.
+  </para>
  </section>
 </chapter>
diff --git a/nixos/modules/services/web-apps/nexus.nix b/nixos/modules/services/web-apps/nexus.nix
index 050f8757fa5f..052dbed6d4f8 100644
--- a/nixos/modules/services/web-apps/nexus.nix
+++ b/nixos/modules/services/web-apps/nexus.nix
@@ -83,6 +83,8 @@ in
     users.users."${cfg.user}" = {
       isSystemUser = true;
       group = cfg.group;
+      home = cfg.home;
+      createHome = true;
     };
 
     users.groups."${cfg.group}" = {};
@@ -104,8 +106,6 @@ in
       preStart = ''
         mkdir -p ${cfg.home}/nexus3/etc
 
-        chown -R ${cfg.user}:${cfg.group} ${cfg.home}
-
         if [ ! -f ${cfg.home}/nexus3/etc/nexus.properties ]; then
           echo "# Jetty section" > ${cfg.home}/nexus3/etc/nexus.properties
           echo "application-port=${toString cfg.listenPort}" >> ${cfg.home}/nexus3/etc/nexus.properties
@@ -124,7 +124,6 @@ in
         User = cfg.user;
         Group = cfg.group;
         PrivateTmp = true;
-        PermissionsStartOnly = true;
         LimitNOFILE = 102642;
       };
     };
diff --git a/nixos/modules/services/web-apps/restya-board.nix b/nixos/modules/services/web-apps/restya-board.nix
index 15fd943a0826..2e5e0ea6622d 100644
--- a/nixos/modules/services/web-apps/restya-board.nix
+++ b/nixos/modules/services/web-apps/restya-board.nix
@@ -184,7 +184,7 @@ in
         phpOptions = ''
           date.timezone = "CET"
 
-          ${optionalString (!isNull cfg.email.server) ''
+          ${optionalString (cfg.email.server != null) ''
             SMTP = ${cfg.email.server}
             smtp_port = ${toString cfg.email.port}
             auth_username = ${cfg.email.login}
@@ -282,7 +282,7 @@ in
 
         sed -i "s@^php@${config.services.phpfpm.phpPackage}/bin/php@" "${runDir}/server/php/shell/"*.sh
 
-        ${if (isNull cfg.database.host) then ''
+        ${if (cfg.database.host == null) then ''
           sed -i "s/^.*'R_DB_HOST'.*$/define('R_DB_HOST', 'localhost');/g" "${runDir}/server/php/config.inc.php"
           sed -i "s/^.*'R_DB_PASSWORD'.*$/define('R_DB_PASSWORD', 'restya');/g" "${runDir}/server/php/config.inc.php"
         '' else ''
@@ -311,7 +311,7 @@ in
         chown -R "${cfg.user}"."${cfg.group}" "${cfg.dataDir}/media"
         chown -R "${cfg.user}"."${cfg.group}" "${cfg.dataDir}/client/img"
 
-        ${optionalString (isNull cfg.database.host) ''
+        ${optionalString (cfg.database.host == null) ''
           if ! [ -e "${cfg.dataDir}/.db-initialized" ]; then
             ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} \
               ${config.services.postgresql.package}/bin/psql -U ${config.services.postgresql.superUser} \
@@ -367,14 +367,14 @@ in
     };
     users.groups.restya-board = {};
 
-    services.postgresql.enable = mkIf (isNull cfg.database.host) true;
+    services.postgresql.enable = mkIf (cfg.database.host == null) true;
 
-    services.postgresql.identMap = optionalString (isNull cfg.database.host)
+    services.postgresql.identMap = optionalString (cfg.database.host == null)
       ''
         restya-board-users restya-board restya_board
       '';
 
-    services.postgresql.authentication = optionalString (isNull cfg.database.host)
+    services.postgresql.authentication = optionalString (cfg.database.host == null)
       ''
         local restya_board all ident map=restya-board-users
       '';
diff --git a/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixos/modules/services/web-servers/apache-httpd/default.nix
index 8f00f81b078c..bf99f6c132af 100644
--- a/nixos/modules/services/web-servers/apache-httpd/default.nix
+++ b/nixos/modules/services/web-servers/apache-httpd/default.nix
@@ -8,8 +8,6 @@ let
 
   httpd = mainCfg.package.out;
 
-  version24 = !versionOlder httpd.version "2.4";
-
   httpdConf = mainCfg.configFile;
 
   php = mainCfg.phpPackage.override { apacheHttpd = httpd.dev; /* otherwise it only gets .out */ };
@@ -107,11 +105,10 @@ let
       "auth_basic" "auth_digest"
 
       # Authentication: is the user who he claims to be?
-      "authn_file" "authn_dbm" "authn_anon"
-      (if version24 then "authn_core" else "authn_alias")
+      "authn_file" "authn_dbm" "authn_anon" "authn_core"
 
       # Authorization: is the user allowed access?
-      "authz_user" "authz_groupfile" "authz_host"
+      "authz_user" "authz_groupfile" "authz_host" "authz_core"
 
       # Other modules.
       "ext_filter" "include" "log_config" "env" "mime_magic"
@@ -119,14 +116,9 @@ let
       "mime" "dav" "status" "autoindex" "asis" "info" "dav_fs"
       "vhost_alias" "negotiation" "dir" "imagemap" "actions" "speling"
       "userdir" "alias" "rewrite" "proxy" "proxy_http"
-    ]
-    ++ optionals version24 [
+      "unixd" "cache" "cache_disk" "slotmem_shm" "socache_shmcb"
       "mpm_${mainCfg.multiProcessingModule}"
-      "authz_core"
-      "unixd"
-      "cache" "cache_disk"
-      "slotmem_shm"
-      "socache_shmcb"
+
       # For compatibility with old configurations, the new module mod_access_compat is provided.
       "access_compat"
     ]
@@ -135,19 +127,8 @@ let
     ++ extraApacheModules;
 
 
-  allDenied = if version24 then ''
-    Require all denied
-  '' else ''
-    Order deny,allow
-    Deny from all
-  '';
-
-  allGranted = if version24 then ''
-    Require all granted
-  '' else ''
-    Order allow,deny
-    Allow from all
-  '';
+  allDenied = "Require all denied";
+  allGranted = "Require all granted";
 
 
   loggingConf = (if mainCfg.logFormat != "none" then ''
@@ -180,9 +161,9 @@ let
 
 
   sslConf = ''
-    SSLSessionCache ${if version24 then "shmcb" else "shm"}:${mainCfg.stateDir}/ssl_scache(512000)
+    SSLSessionCache shmcb:${mainCfg.stateDir}/ssl_scache(512000)
 
-    ${if version24 then "Mutex" else "SSLMutex"} posixsem
+    Mutex posixsem
 
     SSLRandomSeed startup builtin
     SSLRandomSeed connect builtin
@@ -322,9 +303,7 @@ let
 
     ServerRoot ${httpd}
 
-    ${optionalString version24 ''
-      DefaultRuntimeDir ${mainCfg.stateDir}/runtime
-    ''}
+    DefaultRuntimeDir ${mainCfg.stateDir}/runtime
 
     PidFile ${mainCfg.stateDir}/httpd.pid
 
@@ -397,14 +376,6 @@ let
     # Generate directives for the main server.
     ${perServerConf true mainCfg}
 
-    # Always enable virtual hosts; it doesn't seem to hurt.
-    ${let
-        listen = concatMap getListen allHosts;
-        uniqueListen = uniqList {inputList = listen;};
-        directives = concatMapStrings (listen: "NameVirtualHost ${listenToString listen}\n") uniqueListen;
-      in optionalString (!version24) directives
-    }
-
     ${let
         makeVirtualHost = vhost: ''
           <VirtualHost ${concatStringsSep " " (map listenToString (getListen vhost))}>
@@ -690,7 +661,7 @@ in
 
         ; Don't advertise PHP
         expose_php = off
-      '' + optionalString (!isNull config.time.timeZone) ''
+      '' + optionalString (config.time.timeZone != null) ''
 
         ; Apparently PHP doesn't use $TZ.
         date.timezone = "${config.time.timeZone}"
@@ -717,10 +688,10 @@ in
           ''
             mkdir -m 0750 -p ${mainCfg.stateDir}
             [ $(id -u) != 0 ] || chown root.${mainCfg.group} ${mainCfg.stateDir}
-            ${optionalString version24 ''
-              mkdir -m 0750 -p "${mainCfg.stateDir}/runtime"
-              [ $(id -u) != 0 ] || chown root.${mainCfg.group} "${mainCfg.stateDir}/runtime"
-            ''}
+
+            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
diff --git a/nixos/modules/services/web-servers/apache-httpd/limesurvey.nix b/nixos/modules/services/web-servers/apache-httpd/limesurvey.nix
deleted file mode 100644
index 5c387700a5d5..000000000000
--- a/nixos/modules/services/web-servers/apache-httpd/limesurvey.nix
+++ /dev/null
@@ -1,196 +0,0 @@
-{ config, lib, pkgs, serverInfo, ... }:
-
-with lib;
-
-let
-
-  httpd = serverInfo.serverConfig.package;
-
-  version24 = !versionOlder httpd.version "2.4";
-
-  allGranted = if version24 then ''
-    Require all granted
-  '' else ''
-    Order allow,deny
-    Allow from all
-  '';
-
-  limesurveyConfig = pkgs.writeText "config.php" ''
-    <?php
-    $config = array();
-    $config['name']  = "${config.siteName}";
-    $config['runtimePath'] = "${config.dataDir}/tmp/runtime";
-    $config['components'] = array();
-    $config['components']['db'] = array();
-    $config['components']['db']['connectionString'] = '${config.dbType}:host=${config.dbHost};port=${toString config.dbPort};user=${config.dbUser};password=${config.dbPassword};dbname=${config.dbName};';
-    $config['components']['db']['username'] = '${config.dbUser}';
-    $config['components']['db']['password'] = '${config.dbPassword}';
-    $config['components']['db']['charset'] = 'utf-8';
-    $config['components']['db']['tablePrefix'] = "prefix_";
-    $config['components']['assetManager'] = array();
-    $config['components']['assetManager']['basePath'] = '${config.dataDir}/tmp/assets';
-    $config['config'] = array();
-    $config['config']['debug'] = 1;
-    $config['config']['tempdir']  = "${config.dataDir}/tmp";
-    $config['config']['tempdir']  = "${config.dataDir}/tmp";
-    $config['config']['uploaddir']  = "${config.dataDir}/upload";
-    $config['config']['force_ssl'] = '${if config.forceSSL then "on" else ""}';
-    $config['config']['defaultlang'] = '${config.defaultLang}';
-    return $config;
-    ?>
-  '';
-
-  limesurveyRoot = "${pkgs.limesurvey}/share/limesurvey/";
-
-in rec {
-
-  extraConfig = ''
-    Alias ${config.urlPrefix}/tmp ${config.dataDir}/tmp
-
-    <Directory ${config.dataDir}/tmp>
-      ${allGranted}
-      Options -Indexes +FollowSymlinks
-    </Directory>
-
-    Alias ${config.urlPrefix}/upload ${config.dataDir}/upload
-
-    <Directory ${config.dataDir}/upload>
-      ${allGranted}
-      Options -Indexes
-    </Directory>
-
-    ${if config.urlPrefix != "" then ''
-      Alias ${config.urlPrefix} ${limesurveyRoot}
-    '' else ''
-      RewriteEngine On
-      RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-f
-      RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-d
-    ''}
-
-    <Directory ${limesurveyRoot}>
-      DirectoryIndex index.php
-    </Directory>
-  '';
-
-  globalEnvVars = [
-    { name = "LIMESURVEY_CONFIG"; value = limesurveyConfig; }
-  ];
-
-  documentRoot = if config.urlPrefix == "" then limesurveyRoot else null;
-
-  enablePHP = true;
-
-  options = {
-
-    id = mkOption {
-      default = "main";
-      description = ''
-        A unique identifier necessary to keep multiple Limesurvey server
-        instances on the same machine apart.  This is used to
-        disambiguate the administrative scripts, which get names like
-        mediawiki-$id-change-password.
-      '';
-    };
-
-    urlPrefix = mkOption {
-      default = "";
-      description = "Url prefix for site.";
-      type = types.str;
-    };
-
-    dbType = mkOption {
-      default = "pgsql";
-      description = "Type of database for limesurvey, for now, only pgsql.";
-      type = types.enum ["pgsql"];
-    };
-
-    dbName = mkOption {
-      default = "limesurvey";
-      description = "Name of the database that holds the limesurvey data.";
-      type = types.str;
-    };
-
-    dbHost = mkOption {
-      default = "localhost";
-      description = "Limesurvey database host.";
-      type = types.str;
-    };
-
-    dbPort = mkOption {
-      default = 5432;
-      description = "Limesurvey database port.";
-      type = types.int;
-    };
-
-    dbUser = mkOption {
-      default = "limesurvey";
-      description = "Limesurvey database user.";
-      type = types.str;
-    };
-
-    dbPassword = mkOption {
-      example = "foobar";
-      description = "Limesurvey database password.";
-      type = types.str;
-    };
-
-    adminUser = mkOption {
-      description = "Limesurvey admin username.";
-      default = "admin";
-      type = types.str;
-    };
-
-    adminPassword = mkOption {
-      description = "Default limesurvey admin password.";
-      default = "admin";
-      type = types.str;
-    };
-
-    adminEmail = mkOption {
-      description = "Limesurvey admin email.";
-      default = "admin@admin.com";
-      type = types.str;
-    };
-
-    forceSSL = mkOption {
-      default = false;
-      description = "Force use of HTTPS connection.";
-      type = types.bool;
-    };
-
-    siteName = mkOption {
-      default = "LimeSurvey";
-      description = "LimeSurvey name of the site.";
-      type = types.str;
-    };
-
-    defaultLang = mkOption {
-      default = "en";
-      description = "LimeSurvey default language.";
-      type = types.str;
-    };
-
-    dataDir = mkOption {
-      default = "/var/lib/limesurvey";
-      description = "LimeSurvey data directory.";
-      type = types.path;
-    };
-  };
-
-  startupScript = pkgs.writeScript "limesurvey_startup.sh" ''
-    if [ ! -f ${config.dataDir}/.created ]; then
-      mkdir -p ${config.dataDir}/{tmp/runtime,tmp/assets,tmp/upload,upload}
-      chmod -R ug+rw ${config.dataDir}
-      chmod -R o-rwx ${config.dataDir}
-      chown -R wwwrun:wwwrun ${config.dataDir}
-
-      ${pkgs.postgresql}/bin/createuser --no-superuser --no-createdb --no-createrole "${config.dbUser}" || true
-      ${pkgs.postgresql}/bin/createdb "${config.dbName}" -O "${config.dbUser}" || true
-      ${pkgs.sudo}/bin/sudo -u postgres ${pkgs.postgresql}/bin/psql -U postgres -d postgres -c "alter user ${config.dbUser} with password '${config.dbPassword}';" || true
-
-      ${pkgs.limesurvey}/bin/limesurvey-console install '${config.adminUser}' '${config.adminPassword}' '${config.adminUser}' '${config.adminEmail}'
-
-      touch ${config.dataDir}/.created
-    fi
-  '';
-}
diff --git a/nixos/modules/services/web-servers/apache-httpd/wordpress.nix b/nixos/modules/services/web-servers/apache-httpd/wordpress.nix
index c68bfd25f6a8..3dddda138fed 100644
--- a/nixos/modules/services/web-servers/apache-httpd/wordpress.nix
+++ b/nixos/modules/services/web-servers/apache-httpd/wordpress.nix
@@ -273,7 +273,7 @@ in
     if [ ! -d ${serverInfo.fullConfig.services.mysql.dataDir}/${config.dbName} ]; then
       echo "Need to create the database '${config.dbName}' and grant permissions to user named '${config.dbUser}'."
       # Wait until MySQL is up
-      while [ ! -e ${serverInfo.fullConfig.services.mysql.pidDir}/mysqld.pid ]; do
+      while [ ! -S /run/mysqld/mysqld.sock ]; do
         sleep 1
       done
       ${pkgs.mysql}/bin/mysql -e 'CREATE DATABASE ${config.dbName};'
diff --git a/nixos/modules/services/web-servers/hydron.nix b/nixos/modules/services/web-servers/hydron.nix
index ed63230bc784..a4a5a435b2e6 100644
--- a/nixos/modules/services/web-servers/hydron.nix
+++ b/nixos/modules/services/web-servers/hydron.nix
@@ -2,7 +2,6 @@
 
 let
   cfg = config.services.hydron;
-  postgres = config.services.postgresql;
 in with lib; {
   options.services.hydron = {
     enable = mkEnableOption "hydron";
@@ -81,40 +80,40 @@ in with lib; {
   };
 
   config = mkIf cfg.enable {
-    security.sudo.enable = cfg.enable;
-    services.postgresql.enable = cfg.enable;
     services.hydron.passwordFile = mkDefault (pkgs.writeText "hydron-password-file" cfg.password);
     services.hydron.postgresArgsFile = mkDefault (pkgs.writeText "hydron-postgres-args" cfg.postgresArgs);
     services.hydron.postgresArgs = mkDefault ''
       {
         "driver": "postgres",
-        "connection": "user=hydron password=${cfg.password} dbname=hydron sslmode=disable"
+        "connection": "user=hydron password=${cfg.password} host=/run/postgresql dbname=hydron sslmode=disable"
       }
     '';
 
+    services.postgresql = {
+      enable = true;
+      ensureDatabases = [ "hydron" ];
+      ensureUsers = [
+        { name = "hydron";
+          ensurePermissions = { "DATABASE hydron" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' 0750 hydron hydron - -"
+      "d '${cfg.dataDir}/.hydron' - hydron hydron - -"
+      "d '${cfg.dataDir}/images' - hydron hydron - -"
+      "Z '${cfg.dataDir}' - hydron hydron - -"
+
+      "L+ '${cfg.dataDir}/.hydron/db_conf.json' - - - - ${cfg.postgresArgsFile}"
+    ];
+
     systemd.services.hydron = {
       description = "hydron";
       after = [ "network.target" "postgresql.service" ];
       wantedBy = [ "multi-user.target" ];
 
-      preStart = ''
-        # Ensure folder exists or create it and permissions are correct
-        mkdir -p ${escapeShellArg cfg.dataDir}/{.hydron,images}
-        ln -sf ${escapeShellArg cfg.postgresArgsFile} ${escapeShellArg cfg.dataDir}/.hydron/db_conf.json
-        chmod 750 ${escapeShellArg cfg.dataDir}
-        chown -R hydron:hydron ${escapeShellArg cfg.dataDir}
-
-        # Ensure the database is correct or create it
-        ${pkgs.sudo}/bin/sudo -u ${postgres.superUser} ${postgres.package}/bin/createuser \
-          -SDR hydron || true
-        ${pkgs.sudo}/bin/sudo -u ${postgres.superUser} ${postgres.package}/bin/createdb \
-          -T template0 -E UTF8 -O hydron hydron || true
-        ${pkgs.sudo}/bin/sudo -u hydron ${postgres.package}/bin/psql \
-          -c "ALTER ROLE hydron WITH PASSWORD '$(cat ${escapeShellArg cfg.passwordFile})';" || true
-      '';
-
       serviceConfig = {
-        PermissionsStartOnly = true;
         User = "hydron";
         Group = "hydron";
         ExecStart = "${pkgs.hydron}/bin/hydron serve"
@@ -139,7 +138,7 @@ in with lib; {
       description = "Automatically import paths into hydron and possibly fetch tags";
       after = [ "network.target" "hydron.service" ];
       wantedBy = [ "timers.target" ];
-      
+
       timerConfig = {
         Persistent = true;
         OnCalendar = cfg.interval;
@@ -148,11 +147,10 @@ in with lib; {
 
     users = {
       groups.hydron.gid = config.ids.gids.hydron;
-      
+
       users.hydron = {
         description = "hydron server service user";
         home = cfg.dataDir;
-        createHome = true;
         group = "hydron";
         uid = config.ids.uids.hydron;
       };
diff --git a/nixos/modules/services/web-servers/lighttpd/cgit.nix b/nixos/modules/services/web-servers/lighttpd/cgit.nix
index e6a054c296dc..4ec4a5a3359e 100644
--- a/nixos/modules/services/web-servers/lighttpd/cgit.nix
+++ b/nixos/modules/services/web-servers/lighttpd/cgit.nix
@@ -44,6 +44,8 @@ in
       example = ''
         cache-size=1000
         scan-path=/srv/git
+        source-filter=''${pkgs.cgit}/lib/cgit/filters/syntax-highlighting.py
+        about-filter=''${pkgs.cgit}/lib/cgit/filters/about-formatting.sh
       '';
       type = types.lines;
       description = ''
diff --git a/nixos/modules/services/web-servers/minio.nix b/nixos/modules/services/web-servers/minio.nix
index f78a966989b6..cd123000f009 100644
--- a/nixos/modules/services/web-servers/minio.nix
+++ b/nixos/modules/services/web-servers/minio.nix
@@ -72,19 +72,16 @@ in
   };
 
   config = mkIf cfg.enable {
+    systemd.tmpfiles.rules = [
+      "d '${cfg.configDir}' - minio minio - -"
+      "d '${cfg.dataDir}' - minio minio - -"
+    ];
+
     systemd.services.minio = {
       description = "Minio Object Storage";
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
-      preStart = ''
-        # Make sure directories exist with correct owner
-        mkdir -p ${cfg.configDir}
-        chown -R minio:minio ${cfg.configDir}
-        mkdir -p ${cfg.dataDir}
-        chown minio:minio ${cfg.dataDir}
-      '';
       serviceConfig = {
-        PermissionsStartOnly = true;
         ExecStart = "${cfg.package}/bin/minio server --json --address ${cfg.listenAddress} --config-dir=${cfg.configDir} ${cfg.dataDir}";
         Type = "simple";
         User = "minio";
diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix
index 3a154ab75ba9..d6653a65a95a 100644
--- a/nixos/modules/services/web-servers/nginx/default.nix
+++ b/nixos/modules/services/web-servers/nginx/default.nix
@@ -16,11 +16,11 @@ let
     } // (optionalAttrs vhostConfig.enableACME {
       sslCertificate = "${acmeDirectory}/${serverName}/fullchain.pem";
       sslCertificateKey = "${acmeDirectory}/${serverName}/key.pem";
-      sslTrustedCertificate = "${acmeDirectory}/${serverName}/full.pem";
+      sslTrustedCertificate = "${acmeDirectory}/${serverName}/fullchain.pem";
     }) // (optionalAttrs (vhostConfig.useACMEHost != null) {
       sslCertificate = "${acmeDirectory}/${vhostConfig.useACMEHost}/fullchain.pem";
       sslCertificateKey = "${acmeDirectory}/${vhostConfig.useACMEHost}/key.pem";
-      sslTrustedCertificate = "${acmeDirectory}/${vhostConfig.useACMEHost}/full.pem";
+      sslTrustedCertificate = "${acmeDirectory}/${vhostConfig.useACMEHost}/fullchain.pem";
     })
   ) cfg.virtualHosts;
   enableIPv6 = config.networking.enableIPv6;
@@ -64,7 +64,7 @@ let
       include ${cfg.package}/conf/uwsgi_params;
 
       ${optionalString (cfg.resolver.addresses != []) ''
-        resolver ${toString cfg.resolver.addresses} ${optionalString (cfg.resolver.valid != "") "valid=${cfg.resolver.valid}"};
+        resolver ${toString cfg.resolver.addresses} ${optionalString (cfg.resolver.valid != "") "valid=${cfg.resolver.valid}"} ${optionalString (!cfg.resolver.ipv6) "ipv6=off"};
       ''}
       ${upstreamConfig}
 
@@ -521,6 +521,15 @@ in
                 An optional valid parameter allows overriding it
               '';
             };
+            ipv6 = mkOption {
+              type = types.bool;
+              default = true;
+              description = ''
+                By default, nginx will look up both IPv4 and IPv6 addresses while resolving.
+                If looking up of IPv6 addresses is not desired, the ipv6=off parameter can be
+                specified.
+              '';
+            };
           };
         };
         description = ''
diff --git a/nixos/modules/services/web-servers/traefik.nix b/nixos/modules/services/web-servers/traefik.nix
index 4674ed0177e6..5bac895d43ac 100644
--- a/nixos/modules/services/web-servers/traefik.nix
+++ b/nixos/modules/services/web-servers/traefik.nix
@@ -84,18 +84,16 @@ in {
   };
 
   config = mkIf cfg.enable {
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' 0700 traefik traefik - -"
+    ];
+
     systemd.services.traefik = {
       description = "Traefik web server";
       after = [ "network-online.target" ];
       wantedBy = [ "multi-user.target" ];
       serviceConfig = {
-        PermissionsStartOnly = true;
         ExecStart = ''${cfg.package.bin}/bin/traefik --configfile=${configFile}'';
-        ExecStartPre = [
-          ''${pkgs.coreutils}/bin/mkdir -p "${cfg.dataDir}"''
-          ''${pkgs.coreutils}/bin/chmod 700 "${cfg.dataDir}"''
-          ''${pkgs.coreutils}/bin/chown -R traefik:traefik "${cfg.dataDir}"''
-        ];
         Type = "simple";
         User = "traefik";
         Group = cfg.group;
diff --git a/nixos/modules/services/web-servers/winstone.nix b/nixos/modules/services/web-servers/winstone.nix
deleted file mode 100644
index 064ead5ce4bb..000000000000
--- a/nixos/modules/services/web-servers/winstone.nix
+++ /dev/null
@@ -1,129 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  cfg = config.services.winstone;
-
-  winstoneOpts = { name, ... }: {
-    options = {
-      name = mkOption {
-        default = name;
-        internal = true;
-      };
-
-      serviceName = mkOption {
-        type = types.str;
-        description = ''
-          The name of the systemd service. By default, it is
-          derived from the winstone instance name.
-        '';
-      };
-
-      warFile = mkOption {
-        type = types.str;
-        description = ''
-          The WAR file that Winstone should serve.
-        '';
-      };
-
-      javaPackage = mkOption {
-        type = types.package;
-        default = pkgs.jre;
-        defaultText = "pkgs.jre";
-        description = ''
-          Which Java derivation to use for running Winstone.
-        '';
-      };
-
-      user = mkOption {
-        type = types.str;
-        description = ''
-          The user that should run this Winstone process and
-          own the working directory.
-        '';
-      };
-
-      group = mkOption {
-        type = types.str;
-        description = ''
-          The group that will own the working directory.
-        '';
-      };
-
-      workDir = mkOption {
-        type = types.str;
-        description = ''
-          The working directory for this Winstone instance. Will
-          contain extracted webapps etc. The directory will be
-          created if it doesn't exist.
-        '';
-      };
-
-      extraJavaOptions = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        description = ''
-          Extra command line options given to the java process running
-          Winstone.
-        '';
-      };
-
-      extraOptions = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        description = ''
-          Extra command line options given to the Winstone process.
-        '';
-      };
-    };
-
-    config = {
-      workDir = mkDefault "/run/winstone/${name}";
-      serviceName = mkDefault "winstone-${name}";
-    };
-  };
-
-  mkService = cfg: let
-    opts = concatStringsSep " " (cfg.extraOptions ++ [
-      "--warfile ${cfg.warFile}"
-    ]);
-
-    javaOpts = concatStringsSep " " (cfg.extraJavaOptions ++ [
-      "-Djava.io.tmpdir=${cfg.workDir}"
-      "-jar ${pkgs.winstone}/lib/winstone.jar"
-    ]);
-  in {
-    wantedBy = [ "multi-user.target" ];
-    description = "winstone service for ${cfg.name}";
-    preStart = ''
-      mkdir -p "${cfg.workDir}"
-      chown ${cfg.user}:${cfg.group} "${cfg.workDir}"
-    '';
-    serviceConfig = {
-      ExecStart = "${cfg.javaPackage}/bin/java ${javaOpts} ${opts}";
-      User = cfg.user;
-      PermissionsStartOnly = true;
-    };
-  };
-
-in {
-
-  options = {
-    services.winstone = mkOption {
-      default = {};
-      type = with types; attrsOf (submodule winstoneOpts);
-      description = ''
-        Defines independent Winstone services, each serving one WAR-file.
-      '';
-    };
-  };
-
-  config = mkIf (cfg != {}) {
-
-    systemd.services = mapAttrs' (n: c: nameValuePair c.serviceName (mkService c)) cfg;
-
-  };
-
-}
diff --git a/nixos/modules/services/x11/colord.nix b/nixos/modules/services/x11/colord.nix
index 17568df091d4..cf113ad2af8c 100644
--- a/nixos/modules/services/x11/colord.nix
+++ b/nixos/modules/services/x11/colord.nix
@@ -29,6 +29,7 @@ in {
     environment.etc."tmpfiles.d/colord.conf".source = "${pkgs.colord}/lib/tmpfiles.d/colord.conf";
 
     users.users.colord = {
+      isSystemUser = true;
       home = "/var/lib/colord";
       group = "colord";
     };
diff --git a/nixos/modules/services/x11/compton.nix b/nixos/modules/services/x11/compton.nix
index cafd8d88ec4d..d4357324c870 100644
--- a/nixos/modules/services/x11/compton.nix
+++ b/nixos/modules/services/x11/compton.nix
@@ -7,10 +7,19 @@ let
 
   cfg = config.services.compton;
 
+  literalAttrs = v:
+    if isString v then toString v
+    else if isAttrs v then "{\n"
+      + concatStringsSep "\n" (mapAttrsToList
+        (name: value: "${literalAttrs name} = ${literalAttrs value};")
+        v)
+      + "\n}"
+    else generators.toPretty {} v;
+
   floatBetween = a: b: with lib; with types;
     addCheck str (x: versionAtLeast x a && versionOlder x b);
 
-  pairOf = x: with types; addCheck (listOf x) (y: lib.length y == 2);
+  pairOf = x: with types; addCheck (listOf x) (y: length y == 2);
 
   opacityRules = optionalString (length cfg.opacityRules != 0)
     (concatMapStringsSep ",\n" (rule: ''"${rule}"'') cfg.opacityRules);
@@ -23,8 +32,7 @@ let
       fade-in-step  = ${elemAt cfg.fadeSteps 0};
       fade-out-step = ${elemAt cfg.fadeSteps 1};
       fade-exclude  = ${toJSON cfg.fadeExclude};
-    '' + 
-    optionalString cfg.shadow ''
+    '' + optionalString cfg.shadow ''
 
       # shadows
       shadow = true;
@@ -37,7 +45,9 @@ let
       # opacity
       active-opacity   = ${cfg.activeOpacity};
       inactive-opacity = ${cfg.inactiveOpacity};
-      menu-opacity     = ${cfg.menuOpacity};
+
+      wintypes:
+      ${literalAttrs cfg.wintypes};
 
       opacity-rule = [
         ${opacityRules}
@@ -45,7 +55,7 @@ let
 
       # other options
       backend = ${toJSON cfg.backend};
-      vsync = ${toJSON cfg.vSync};
+      vsync = ${boolToString cfg.vSync};
       refresh-rate = ${toString cfg.refreshRate};
     '' + cfg.extraOptions);
 
@@ -93,7 +103,7 @@ in {
       example = [
         "window_type *= 'menu'"
         "name ~= 'Firefox$'"
-        "focused = 1" 
+        "focused = 1"
       ];
       description = ''
         List of conditions of windows that should not be faded.
@@ -133,7 +143,7 @@ in {
       example = [
         "window_type *= 'menu'"
         "name ~= 'Firefox$'"
-        "focused = 1" 
+        "focused = 1"
       ];
       description = ''
         List of conditions of windows that should have no shadow.
@@ -168,6 +178,15 @@ in {
       '';
     };
 
+    wintypes = mkOption {
+      type = types.attrs;
+      default = { popup_menu = { opacity = cfg.menuOpacity; }; dropdown_menu = { opacity = cfg.menuOpacity; }; };
+      example = {};
+      description = ''
+        Rules for specific window types.
+      '';
+    };
+
     opacityRules = mkOption {
       type = types.listOf types.str;
       default = [];
@@ -189,15 +208,22 @@ in {
     };
 
     vSync = mkOption {
-      type = types.enum [
-        "none" "drm" "opengl"
-        "opengl-oml" "opengl-swc" "opengl-mswc"
-      ];
-      default = "none";
-      example = "opengl-swc";
+      type = with types; either bool
+        (enum [ "none" "drm" "opengl" "opengl-oml" "opengl-swc" "opengl-mswc" ]);
+      default = false;
+      apply = x:
+        let
+          res = x != "none";
+          msg = "The type of services.compton.vSync has changed to bool:"
+                + " interpreting ${x} as ${boolToString res}";
+        in
+          if isBool x then x
+          else warn msg res;
+
       description = ''
-        Enable vertical synchronization using the specified method.
-        See <literal>compton(1)</literal> man page an explanation.
+        Enable vertical synchronization. Chooses the best method
+        (drm, opengl, opengl-oml, opengl-swc, opengl-mswc) automatically.
+        The bool value should be used, the others are just for backwards compatibility.
       '';
     };
 
diff --git a/nixos/modules/services/x11/desktop-managers/enlightenment.nix b/nixos/modules/services/x11/desktop-managers/enlightenment.nix
index 04e380b61530..4da146940648 100644
--- a/nixos/modules/services/x11/desktop-managers/enlightenment.nix
+++ b/nixos/modules/services/x11/desktop-managers/enlightenment.nix
@@ -25,7 +25,7 @@ in
 
   };
 
-  config = mkIf (xcfg.enable && cfg.enable) {
+  config = mkIf cfg.enable {
 
     environment.systemPackages = [
       e.efl e.enlightenment
@@ -38,10 +38,10 @@ in
       pkgs.xorg.xcursorthemes
     ];
 
-    environment.pathsToLink = [ 
-      "/etc/enlightenment" 
-      "/share/enlightenment" 
-      "/share/elementary" 
+    environment.pathsToLink = [
+      "/etc/enlightenment"
+      "/share/enlightenment"
+      "/share/elementary"
       "/share/locale"
     ];
 
diff --git a/nixos/modules/services/x11/desktop-managers/gnome3.nix b/nixos/modules/services/x11/desktop-managers/gnome3.nix
index 3d748d4308b7..ef6820d33260 100644
--- a/nixos/modules/services/x11/desktop-managers/gnome3.nix
+++ b/nixos/modules/services/x11/desktop-managers/gnome3.nix
@@ -18,7 +18,7 @@ let
   nixos-gsettings-desktop-schemas = pkgs.runCommand "nixos-gsettings-desktop-schemas" { preferLocalBuild = true; }
     ''
      mkdir -p $out/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas
-     cp -rf ${pkgs.gnome3.gsettings-desktop-schemas}/share/gsettings-schemas/gsettings-desktop-schemas*/glib-2.0/schemas/*.xml $out/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas
+     cp -rf ${pkgs.gsettings-desktop-schemas}/share/gsettings-schemas/gsettings-desktop-schemas*/glib-2.0/schemas/*.xml $out/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas
 
      ${concatMapStrings (pkg: "cp -rf ${pkg}/share/gsettings-schemas/*/glib-2.0/schemas/*.xml $out/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas\n") cfg.extraGSettingsOverridePackages}
 
@@ -75,7 +75,7 @@ in {
       debug = mkEnableOption "gnome-session debug messages";
 
       flashback = {
-        enableMetacity = mkEnableOption "Enable the standard GNOME Flashback session with Metacity.";
+        enableMetacity = mkEnableOption "the standard GNOME Flashback session with Metacity";
 
         customSessions = mkOption {
           type = types.listOf (types.submodule {
@@ -120,9 +120,6 @@ in {
     security.polkit.enable = true;
     services.udisks2.enable = true;
     services.accounts-daemon.enable = true;
-    services.geoclue2.enable = mkDefault true;
-    # GNOME should have its own geoclue agent
-    services.geoclue2.enableDemoAgent = false;
     services.dleyna-renderer.enable = mkDefault true;
     services.dleyna-server.enable = mkDefault true;
     services.gnome3.at-spi2-core.enable = true;
@@ -191,6 +188,24 @@ in {
       '') cfg.sessionPath}
     '';
 
+
+    services.geoclue2.enable = mkDefault true;
+    # GNOME should have its own geoclue agent
+    services.geoclue2.enableDemoAgent = false;
+
+    services.geoclue2.appConfig."gnome-datetime-panel" = {
+      isAllowed = true;
+      isSystem = true;
+    };
+    services.geoclue2.appConfig."gnome-color-panel" = {
+      isAllowed = true;
+      isSystem = true;
+    };
+    services.geoclue2.appConfig."org.gnome.Shell" = {
+      isAllowed = true;
+      isSystem = true;
+    };
+
     environment.variables.GNOME_SESSION_DEBUG = optionalString cfg.debug "1";
 
     # Override default mimeapps
diff --git a/nixos/modules/services/x11/desktop-managers/lumina.nix b/nixos/modules/services/x11/desktop-managers/lumina.nix
index 43fed2572b51..2224bcd5a2a5 100644
--- a/nixos/modules/services/x11/desktop-managers/lumina.nix
+++ b/nixos/modules/services/x11/desktop-managers/lumina.nix
@@ -21,23 +21,18 @@ in
   };
 
 
-  config = mkIf (xcfg.enable && cfg.enable) {
+  config = mkIf cfg.enable {
 
     services.xserver.desktopManager.session = singleton {
       name = "lumina";
       start = ''
-        exec ${pkgs.lumina}/bin/start-lumina-desktop
+        exec ${pkgs.lumina.lumina}/bin/start-lumina-desktop
       '';
     };
 
-    environment.systemPackages = [
-      pkgs.fluxbox
-      pkgs.libsForQt5.kwindowsystem
-      pkgs.lumina
-      pkgs.numlockx
-      pkgs.qt5.qtsvg
-      pkgs.xscreensaver
-    ];
+    environment.systemPackages =
+      pkgs.lumina.preRequisitePackages ++
+      pkgs.lumina.corePackages;
 
     # Link some extra directories in /run/current-system/software/share
     environment.pathsToLink = [
diff --git a/nixos/modules/services/x11/desktop-managers/lxqt.nix b/nixos/modules/services/x11/desktop-managers/lxqt.nix
index 686bbd0dcf98..4368adedb34b 100644
--- a/nixos/modules/services/x11/desktop-managers/lxqt.nix
+++ b/nixos/modules/services/x11/desktop-managers/lxqt.nix
@@ -26,7 +26,7 @@ in
 
   };
 
-  config = mkIf (xcfg.enable && cfg.enable) {
+  config = mkIf cfg.enable {
 
     services.xserver.desktopManager.session = singleton {
       name = "lxqt";
diff --git a/nixos/modules/services/x11/desktop-managers/mate.nix b/nixos/modules/services/x11/desktop-managers/mate.nix
index bf6685ff7eac..a2d0810f90df 100644
--- a/nixos/modules/services/x11/desktop-managers/mate.nix
+++ b/nixos/modules/services/x11/desktop-managers/mate.nix
@@ -42,7 +42,7 @@ in
 
   };
 
-  config = mkIf (xcfg.enable && cfg.enable) {
+  config = mkIf cfg.enable {
 
     services.xserver.desktopManager.session = singleton {
       name = "mate";
diff --git a/nixos/modules/services/x11/desktop-managers/maxx.nix b/nixos/modules/services/x11/desktop-managers/maxx.nix
index d7bd2fc5eb0c..1c04104df41e 100644
--- a/nixos/modules/services/x11/desktop-managers/maxx.nix
+++ b/nixos/modules/services/x11/desktop-managers/maxx.nix
@@ -10,9 +10,15 @@ in {
     enable = mkEnableOption "MaXX desktop environment";
   };
 
-  config = mkIf (xcfg.enable && cfg.enable) {
+  config = mkIf cfg.enable {
     environment.systemPackages = [ pkgs.maxx ];
 
+    # there is hardcoded path in binaries
+    system.activationScripts.setup-maxx = ''
+      mkdir -p /opt
+      ln -sfn ${pkgs.maxx}/opt/MaXX /opt
+    '';
+
     services.xserver.desktopManager.session = [
     { name = "MaXX";
       start = ''
diff --git a/nixos/modules/services/x11/desktop-managers/pantheon.nix b/nixos/modules/services/x11/desktop-managers/pantheon.nix
index e1eeb32aa1a0..9a0f77a856a7 100644
--- a/nixos/modules/services/x11/desktop-managers/pantheon.nix
+++ b/nixos/modules/services/x11/desktop-managers/pantheon.nix
@@ -118,9 +118,6 @@ in
       (mkIf config.services.printing.enable  ([pkgs.system-config-printer]) )
     ];
     services.pantheon.contractor.enable = mkDefault true;
-    services.geoclue2.enable = mkDefault true;
-    # pantheon has pantheon-agent-geoclue2
-    services.geoclue2.enableDemoAgent = false;
     services.gnome3.at-spi2-core.enable = true;
     services.gnome3.evince.enable = mkDefault true;
     services.gnome3.evolution-data-server.enable = true;
@@ -140,6 +137,14 @@ in
     services.xserver.updateDbusEnvironment = true;
     services.zeitgeist.enable = mkDefault true;
 
+    services.geoclue2.enable = mkDefault true;
+    # pantheon has pantheon-agent-geoclue2
+    services.geoclue2.enableDemoAgent = false;
+    services.geoclue2.appConfig."io.elementary.desktop.agent-geoclue2" = {
+      isAllowed = true;
+      isSystem = true;
+    };
+
     networking.networkmanager.enable = mkDefault true;
     networking.networkmanager.basePackages =
       { inherit (pkgs) networkmanager modemmanager wpa_supplicant;
diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix
index a9e55eb846ca..dc8bfc7dc172 100644
--- a/nixos/modules/services/x11/desktop-managers/plasma5.nix
+++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix
@@ -36,7 +36,7 @@ in
 
 
   config = mkMerge [
-    (mkIf (xcfg.enable && cfg.enable) {
+    (mkIf cfg.enable {
       services.xserver.desktopManager.session = singleton {
         name = "plasma5";
         bgSupport = true;
@@ -161,7 +161,6 @@ in
 
           qtvirtualkeyboard
 
-          libsForQt56.phonon-backend-gstreamer
           libsForQt5.phonon-backend-gstreamer
 
           xdg-user-dirs # Update user dirs as described in https://freedesktop.org/wiki/Software/xdg-user-dirs/
diff --git a/nixos/modules/services/x11/desktop-managers/xterm.nix b/nixos/modules/services/x11/desktop-managers/xterm.nix
index 6ff1ef0e4c9a..f386ebc4d3c1 100644
--- a/nixos/modules/services/x11/desktop-managers/xterm.nix
+++ b/nixos/modules/services/x11/desktop-managers/xterm.nix
@@ -19,7 +19,7 @@ in
 
   };
 
-  config = mkIf (config.services.xserver.enable && cfg.enable) {
+  config = mkIf cfg.enable {
 
     services.xserver.desktopManager.session = singleton
       { name = "xterm";
diff --git a/nixos/modules/services/x11/display-managers/gdm.nix b/nixos/modules/services/x11/display-managers/gdm.nix
index 3edf7c8d9cab..ff4b91923e02 100644
--- a/nixos/modules/services/x11/display-managers/gdm.nix
+++ b/nixos/modules/services/x11/display-managers/gdm.nix
@@ -14,6 +14,23 @@ let
       exec "$@"
     '';
 
+  # Solves problems like:
+  # https://wiki.archlinux.org/index.php/Talk:Bluetooth_headset#GDMs_pulseaudio_instance_captures_bluetooth_headset
+  # Instead of blacklisting plugins, we use Fedora's PulseAudio configuration for GDM:
+  # https://src.fedoraproject.org/rpms/gdm/blob/master/f/default.pa-for-gdm
+  pulseConfig = pkgs.writeText "default.pa" ''
+    load-module module-device-restore
+    load-module module-card-restore
+    load-module module-udev-detect
+    load-module module-native-protocol-unix
+    load-module module-default-device-restore
+    load-module module-rescue-streams
+    load-module module-always-sink
+    load-module module-intended-roles
+    load-module module-suspend-on-idle
+    load-module module-position-event-sounds
+  '';
+
 in
 
 {
@@ -126,6 +143,11 @@ in
           GDM_X_SESSION_WRAPPER = "${xSessionWrapper}";
         };
         execCmd = "exec ${gdm}/bin/gdm";
+        preStart = optionalString config.hardware.pulseaudio.enable ''
+          mkdir -p /run/gdm/.config/pulse
+          ln -sf ${pulseConfig} /run/gdm/.config/pulse/default.pa
+          chown -R gdm:gdm /run/gdm/.config
+        '';
       };
 
     # Because sd_login_monitor_new requires /run/systemd/machines
diff --git a/nixos/modules/services/x11/display-managers/lightdm.nix b/nixos/modules/services/x11/display-managers/lightdm.nix
index 3ab4f26399f3..afa0cebbc527 100644
--- a/nixos/modules/services/x11/display-managers/lightdm.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm.nix
@@ -189,6 +189,11 @@ in
   config = mkIf cfg.enable {
 
     assertions = [
+      { assertion = xcfg.enable;
+        message = ''
+          LightDM requires services.xserver.enable to be true
+        '';
+      }
       { assertion = cfg.autoLogin.enable -> cfg.autoLogin.user != null;
         message = ''
           LightDM auto-login requires services.xserver.displayManager.lightdm.autoLogin.user to be set
diff --git a/nixos/modules/services/x11/display-managers/sddm.nix b/nixos/modules/services/x11/display-managers/sddm.nix
index 05830e325d51..d1ed345ac579 100644
--- a/nixos/modules/services/x11/display-managers/sddm.nix
+++ b/nixos/modules/services/x11/display-managers/sddm.nix
@@ -195,6 +195,11 @@ in
   config = mkIf cfg.enable {
 
     assertions = [
+      { assertion = xcfg.enable;
+        message = ''
+          SDDM requires services.xserver.enable to be true
+        '';
+      }
       { assertion = cfg.autoLogin.enable -> cfg.autoLogin.user != null;
         message = ''
           SDDM auto-login requires services.xserver.displayManager.sddm.autoLogin.user to be set
@@ -264,8 +269,8 @@ in
     };
 
     environment.etc."sddm.conf".source = cfgFile;
-    environment.pathsToLink = [ 
-      "/share/sddm" 
+    environment.pathsToLink = [
+      "/share/sddm"
     ];
 
     users.groups.sddm.gid = config.ids.gids.sddm;
diff --git a/nixos/modules/services/x11/display-managers/startx.nix b/nixos/modules/services/x11/display-managers/startx.nix
index 15609540a6e7..570469843586 100644
--- a/nixos/modules/services/x11/display-managers/startx.nix
+++ b/nixos/modules/services/x11/display-managers/startx.nix
@@ -20,8 +20,8 @@ in
           Whether to enable the dummy "startx" pseudo-display manager,
           which allows users to start X manually via the "startx" command
           from a vt shell. The X server runs under the user's id, not as root.
-          The user must provide a ~/.xinintrc file containing session startup
-          commands, see startx(1). This is not autmatically generated
+          The user must provide a ~/.xinitrc file containing session startup
+          commands, see startx(1). This is not automatically generated
           from the desktopManager and windowManager settings.
         '';
       };
diff --git a/nixos/modules/services/x11/display-managers/xpra.nix b/nixos/modules/services/x11/display-managers/xpra.nix
index 40a1680da537..c23e479140f0 100644
--- a/nixos/modules/services/x11/display-managers/xpra.nix
+++ b/nixos/modules/services/x11/display-managers/xpra.nix
@@ -33,7 +33,7 @@ in
         description = "Authentication to use when connecting to xpra";
       };
 
-      pulseaudio = mkEnableOption "pulseaudio audio streaming.";
+      pulseaudio = mkEnableOption "pulseaudio audio streaming";
 
       extraOptions = mkOption {
         description = "Extra xpra options";
diff --git a/nixos/modules/services/x11/redshift.nix b/nixos/modules/services/x11/redshift.nix
index b7dd7debcb63..4345a3348081 100644
--- a/nixos/modules/services/x11/redshift.nix
+++ b/nixos/modules/services/x11/redshift.nix
@@ -119,7 +119,13 @@ in {
     # needed so that .desktop files are installed, which geoclue cares about
     environment.systemPackages = [ cfg.package ];
 
-    services.geoclue2.enable = mkIf (cfg.provider == "geoclue2") true;
+    services.geoclue2 = mkIf (cfg.provider == "geoclue2") {
+      enable = true;
+      appConfig."redshift" = {
+        isAllowed = true;
+        isSystem = true;
+      };
+    };
 
     systemd.user.services.redshift = 
     let
diff --git a/nixos/modules/services/x11/window-managers/default.nix b/nixos/modules/services/x11/window-managers/default.nix
index 06c59342b45a..2a1f22fa9a4c 100644
--- a/nixos/modules/services/x11/window-managers/default.nix
+++ b/nixos/modules/services/x11/window-managers/default.nix
@@ -19,6 +19,7 @@ in
     ./herbstluftwm.nix
     ./i3.nix
     ./jwm.nix
+    ./leftwm.nix
     ./metacity.nix
     ./mwm.nix
     ./openbox.nix
diff --git a/nixos/modules/services/x11/window-managers/i3.nix b/nixos/modules/services/x11/window-managers/i3.nix
index c9b0669e7ba5..0ef55d5f2c03 100644
--- a/nixos/modules/services/x11/window-managers/i3.nix
+++ b/nixos/modules/services/x11/window-managers/i3.nix
@@ -60,12 +60,15 @@ in
         ${cfg.extraSessionCommands}
 
         ${cfg.package}/bin/i3 ${optionalString (cfg.configFile != null)
-          "-c \"${cfg.configFile}\""
+          "-c /etc/i3/config"
         } &
         waitPID=$!
       '';
     }];
     environment.systemPackages = [ cfg.package ] ++ cfg.extraPackages;
+    environment.etc."i3/config" = mkIf (cfg.configFile != null) {
+      source = cfg.configFile;
+    };
   };
 
   imports = [
diff --git a/nixos/modules/services/x11/window-managers/leftwm.nix b/nixos/modules/services/x11/window-managers/leftwm.nix
new file mode 100644
index 000000000000..3ef40df95df2
--- /dev/null
+++ b/nixos/modules/services/x11/window-managers/leftwm.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.leftwm;
+in
+{
+  ###### interface
+  options = {
+    services.xserver.windowManager.leftwm.enable = mkEnableOption "leftwm";
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton {
+      name = "leftwm";
+      start = ''
+        ${pkgs.leftwm}/bin/leftwm &
+        waitPID=$!
+      '';
+    };
+    environment.systemPackages = [ pkgs.leftwm ];
+  };
+}
diff --git a/nixos/modules/services/x11/window-managers/xmonad.nix b/nixos/modules/services/x11/window-managers/xmonad.nix
index 43de746ab1f1..a6055f26789e 100644
--- a/nixos/modules/services/x11/window-managers/xmonad.nix
+++ b/nixos/modules/services/x11/window-managers/xmonad.nix
@@ -10,6 +10,14 @@ let
                      optionals cfg.enableContribAndExtras
                      [ self.xmonad-contrib self.xmonad-extras ];
   };
+  xmonadBin = pkgs.writers.writeHaskell "xmonad" {
+    ghc = cfg.haskellPackages.ghc;
+    libraries = [ cfg.haskellPackages.xmonad ] ++
+                cfg.extraPackages cfg.haskellPackages ++
+                optionals cfg.enableContribAndExtras
+                (with cfg.haskellPackages; [ xmonad-contrib xmonad-extras ]);
+  } cfg.config;
+
 in
 {
   options = {
@@ -48,13 +56,36 @@ in
         type = lib.types.bool;
         description = "Enable xmonad-{contrib,extras} in Xmonad.";
       };
+
+      config = mkOption {
+        default = null;
+        type = with lib.types; nullOr (either path string);
+        description = ''
+          Configuration from which XMonad gets compiled. If no value
+          is specified, the xmonad config from $HOME/.xmonad is taken.
+          If you use xmonad --recompile, $HOME/.xmonad will be taken as
+          the configuration, but on the next restart of display-manager
+          this config will be reapplied.
+        '';
+        example = ''
+          import XMonad
+
+          main = launch defaultConfig
+                 { modMask = mod4Mask -- Use Super instead of Alt
+                 , terminal = "urxvt"
+                 }
+        '';
+      };
     };
   };
   config = mkIf cfg.enable {
     services.xserver.windowManager = {
       session = [{
         name = "xmonad";
-        start = ''
+        start = if (cfg.config != null) then ''
+          ${xmonadBin}
+          waitPID=$!
+        '' else ''
           ${xmonad}/bin/xmonad &
           waitPID=$!
         '';
diff --git a/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix
index e767b0eda312..0f057c4ab730 100644
--- a/nixos/modules/services/x11/xserver.nix
+++ b/nixos/modules/services/x11/xserver.nix
@@ -347,6 +347,7 @@ in
 
       xkbDir = mkOption {
         type = types.path;
+        default = "${pkgs.xkeyboard_config}/etc/X11/xkb";
         description = ''
           Path used for -xkbdir xserver parameter.
         '';
@@ -708,8 +709,6 @@ in
         xorg.xf86inputevdev.out
       ];
 
-    services.xserver.xkbDir = mkDefault "${pkgs.xkeyboard_config}/etc/X11/xkb";
-
     system.extraDependencies = singleton (pkgs.runCommand "xkb-validated" {
       inherit (cfg) xkbModel layout xkbVariant xkbOptions;
       nativeBuildInputs = [ pkgs.xkbvalidate ];
diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl
index 397b308b7311..8ff00fa11dc7 100644
--- a/nixos/modules/system/activation/switch-to-configuration.pl
+++ b/nixos/modules/system/activation/switch-to-configuration.pl
@@ -166,24 +166,6 @@ while (my ($unit, $state) = each %{$activePrev}) {
 
     if (-e $prevUnitFile && ($state->{state} eq "active" || $state->{state} eq "activating")) {
         if (! -e $newUnitFile || abs_path($newUnitFile) eq "/dev/null") {
-            # Ignore (i.e. never stop) these units:
-            if ($unit eq "system.slice") {
-                # TODO: This can be removed a few months after 18.09 is out
-                # (i.e. after everyone switched away from 18.03).
-                # Problem: Restarting (stopping) system.slice would not only
-                # stop X11 but also most system units/services. We obviously
-                # don't want this happening to users when they switch from 18.03
-                # to 18.09 or nixos-unstable.
-                # Reason: The following change in systemd:
-                # https://github.com/systemd/systemd/commit/d8e5a9338278d6602a0c552f01f298771a384798
-                # The commit adds system.slice to the perpetual units, which
-                # means removing the unit file and adding it to the source code.
-                # This is done so that system.slice can't be stopped anymore but
-                # in our case it ironically would cause this script to stop
-                # system.slice because the unit was removed (and an older
-                # systemd version is still running).
-                next;
-            }
             my $unitInfo = parseUnit($prevUnitFile);
             $unitsToStop{$unit} = 1 if boolIsTrue($unitInfo->{'X-StopOnRemoval'} // "yes");
         }
diff --git a/nixos/modules/system/boot/binfmt.nix b/nixos/modules/system/boot/binfmt.nix
index 15e84dc021e2..d6c0f0504868 100644
--- a/nixos/modules/system/boot/binfmt.nix
+++ b/nixos/modules/system/boot/binfmt.nix
@@ -1,8 +1,8 @@
-{ config, lib, ... }:
+{ config, lib, pkgs, ... }:
 let
   inherit (lib) mkOption types optionalString;
 
-  cfg = config.boot.binfmtMiscRegistrations;
+  cfg = config.boot.binfmt;
 
   makeBinfmtLine = name: { recognitionType, offset, magicOrExtension
                          , mask, preserveArgvZero, openBinary
@@ -13,125 +13,249 @@ let
     mask' = toString mask;
     interpreter = "/run/binfmt/${name}";
     flags = if !(matchCredentials -> openBinary)
-              then throw "boot.binfmtMiscRegistrations.${name}: you can't specify openBinary = false when matchCredentials = true."
+              then throw "boot.binfmt.registrations.${name}: you can't specify openBinary = false when matchCredentials = true."
             else optionalString preserveArgvZero "P" +
                  optionalString (openBinary && !matchCredentials) "O" +
                  optionalString matchCredentials "C" +
                  optionalString fixBinary "F";
   in ":${name}:${type}:${offset'}:${magicOrExtension}:${mask'}:${interpreter}:${flags}";
 
-  binfmtFile = builtins.toFile "binfmt_nixos.conf"
-    (lib.concatStringsSep "\n" (lib.mapAttrsToList makeBinfmtLine cfg));
-
   activationSnippet = name: { interpreter, ... }:
     "ln -sf ${interpreter} /run/binfmt/${name}";
-  activationScript = ''
-    mkdir -p -m 0755 /run/binfmt
-    ${lib.concatStringsSep "\n" (lib.mapAttrsToList activationSnippet cfg)}
-  '';
+
+  getEmulator = system: (lib.systems.elaborate { inherit system; }).emulator pkgs;
+
+  # Mapping of systems to “magicOrExtension” and “mask”. Mostly taken from:
+  # - https://github.com/cleverca22/nixos-configs/blob/master/qemu.nix
+  # and
+  # - https://github.com/qemu/qemu/blob/master/scripts/qemu-binfmt-conf.sh
+  # TODO: maybe put these in a JSON file?
+  magics = {
+    armv6l-linux = {
+      magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\x00\xff\xfe\xff\xff\xff'';
+    };
+    armv7l-linux = {
+      magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\x00\xff\xfe\xff\xff\xff'';
+    };
+    aarch64-linux = {
+      magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\x00\xff\xfe\xff\xff\xff'';
+    };
+    aarch64_be-linux = {
+      magicOrExtension = ''\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
+    };
+    i386-linux = {
+      magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x03\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
+    i486-linux = {
+      magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
+    i586-linux = {
+      magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
+    i686-linux = {
+      magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
+    x86_64-linux = {
+      magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3e\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
+    alpha-linux = {
+      magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x26\x90'';
+      mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
+    sparc64-linux = {
+      magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x02'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
+    };
+    sparc-linux = {
+      magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x12'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
+    };
+    powerpc-linux = {
+      magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
+    };
+    powerpc64-linux = {
+      magicOrExtension = ''\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x15'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
+    };
+    powerpc64le-linux = {
+      magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x15\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\x00'';
+    };
+    mips-linux = {
+      magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
+    };
+    mipsel-linux = {
+      magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
+    mips64-linux = {
+      magicOrExtension = ''\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
+    };
+    mips64el-linux = {
+      magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
+    riscv32-linux = {
+      magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
+    riscv64-linux = {
+      magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
+    x86_64-windows = {
+      magicOrExtension = ".exe";
+      recognitionType = "extension";
+    };
+    i686-windows = {
+      magicOrExtension = ".exe";
+      recognitionType = "extension";
+    };
+  };
+
 in {
   options = {
-    boot.binfmtMiscRegistrations = mkOption {
-      default = {};
-
-      description = ''
-        Extra binary formats to register with the kernel.
-        See https://www.kernel.org/doc/html/latest/admin-guide/binfmt-misc.html for more details.
-      '';
-
-      type = types.attrsOf (types.submodule ({ config, ... }: {
-        options = {
-          recognitionType = mkOption {
-            default = "magic";
-            description = "Whether to recognize executables by magic number or extension.";
-            type = types.enum [ "magic" "extension" ];
-          };
+    boot.binfmt = {
+      registrations = mkOption {
+        default = {};
 
-          offset = mkOption {
-            default = null;
-            description = "The byte offset of the magic number used for recognition.";
-            type = types.nullOr types.int;
-          };
+        description = ''
+          Extra binary formats to register with the kernel.
+          See https://www.kernel.org/doc/html/latest/admin-guide/binfmt-misc.html for more details.
+        '';
 
-          magicOrExtension = mkOption {
-            description = "The magic number or extension to match on.";
-            type = types.str;
-          };
+        type = types.attrsOf (types.submodule ({ config, ... }: {
+          options = {
+            recognitionType = mkOption {
+              default = "magic";
+              description = "Whether to recognize executables by magic number or extension.";
+              type = types.enum [ "magic" "extension" ];
+            };
 
-          mask = mkOption {
-            default = null;
-            description =
-              "A mask to be ANDed with the byte sequence of the file before matching";
-            type = types.nullOr types.str;
-          };
+            offset = mkOption {
+              default = null;
+              description = "The byte offset of the magic number used for recognition.";
+              type = types.nullOr types.int;
+            };
 
-          interpreter = mkOption {
-            description = ''
-              The interpreter to invoke to run the program.
+            magicOrExtension = mkOption {
+              description = "The magic number or extension to match on.";
+              type = types.str;
+            };
 
-              Note that the actual registration will point to
-              /run/binfmt/''${name}, so the kernel interpreter length
-              limit doesn't apply.
-            '';
-            type = types.path;
-          };
+            mask = mkOption {
+              default = null;
+              description =
+                "A mask to be ANDed with the byte sequence of the file before matching";
+              type = types.nullOr types.str;
+            };
 
-          preserveArgvZero = mkOption {
-            default = false;
-            description = ''
-              Whether to pass the original argv[0] to the interpreter.
+            interpreter = mkOption {
+              description = ''
+                The interpreter to invoke to run the program.
 
-              See the description of the 'P' flag in the kernel docs
-              for more details;
-            '';
-            type = types.bool;
-          };
+                Note that the actual registration will point to
+                /run/binfmt/''${name}, so the kernel interpreter length
+                limit doesn't apply.
+              '';
+              type = types.path;
+            };
 
-          openBinary = mkOption {
-            default = config.matchCredentials;
-            description = ''
-              Whether to pass the binary to the interpreter as an open
-              file descriptor, instead of a path.
-            '';
-            type = types.bool;
-          };
+            preserveArgvZero = mkOption {
+              default = false;
+              description = ''
+                Whether to pass the original argv[0] to the interpreter.
 
-          matchCredentials = mkOption {
-            default = false;
-            description = ''
-              Whether to launch with the credentials and security
-              token of the binary, not the interpreter (e.g. setuid
-              bit).
+                See the description of the 'P' flag in the kernel docs
+                for more details;
+              '';
+              type = types.bool;
+            };
 
-              See the description of the 'C' flag in the kernel docs
-              for more details.
+            openBinary = mkOption {
+              default = config.matchCredentials;
+              description = ''
+                Whether to pass the binary to the interpreter as an open
+                file descriptor, instead of a path.
+              '';
+              type = types.bool;
+            };
 
-              Implies/requires openBinary = true.
-            '';
-            type = types.bool;
-          };
+            matchCredentials = mkOption {
+              default = false;
+              description = ''
+                Whether to launch with the credentials and security
+                token of the binary, not the interpreter (e.g. setuid
+                bit).
 
-          fixBinary = mkOption {
-            default = false;
-            description = ''
-              Whether to open the interpreter file as soon as the
-              registration is loaded, rather than waiting for a
-              relevant file to be invoked.
-
-              See the description of the 'F' flag in the kernel docs
-              for more details.
-            '';
-            type = types.bool;
+                See the description of the 'C' flag in the kernel docs
+                for more details.
+
+                Implies/requires openBinary = true.
+              '';
+              type = types.bool;
+            };
+
+            fixBinary = mkOption {
+              default = false;
+              description = ''
+                Whether to open the interpreter file as soon as the
+                registration is loaded, rather than waiting for a
+                relevant file to be invoked.
+
+                See the description of the 'F' flag in the kernel docs
+                for more details.
+              '';
+              type = types.bool;
+            };
           };
-        };
-      }));
+        }));
+      };
+
+      emulatedSystems = mkOption {
+        default = [];
+        description = ''
+          List of systems to emulate. Will also configure Nix to
+          support your new systems.
+        '';
+        type = types.listOf types.string;
+      };
     };
   };
 
-  config = lib.mkIf (cfg != {}) {
-    environment.etc."binfmt.d/nixos.conf".source = binfmtFile;
-    system.activationScripts.binfmt = activationScript;
-    systemd.additionalUpstreamSystemUnits =
+  config = {
+    boot.binfmt.registrations = builtins.listToAttrs (map (system: {
+      name = system;
+      value = {
+        interpreter = getEmulator system;
+      } // (magics.${system} or (throw "Cannot create binfmt registration for system ${system}"));
+    }) cfg.emulatedSystems);
+    # TODO: add a nix.extraPlatforms option to NixOS!
+    nix.extraOptions = lib.mkIf (cfg.emulatedSystems != []) ''
+      extra-platforms = ${toString (cfg.emulatedSystems ++ lib.optional pkgs.stdenv.hostPlatform.isx86_64 "i686-linux")}
+    '';
+    nix.sandboxPaths = lib.mkIf (cfg.emulatedSystems != [])
+      ([ "/run/binfmt" ] ++ (map (system: dirOf (dirOf (getEmulator system))) cfg.emulatedSystems));
+
+    environment.etc."binfmt.d/nixos.conf".source = builtins.toFile "binfmt_nixos.conf"
+      (lib.concatStringsSep "\n" (lib.mapAttrsToList makeBinfmtLine config.boot.binfmt.registrations));
+    system.activationScripts.binfmt = ''
+      mkdir -p -m 0755 /run/binfmt
+      ${lib.concatStringsSep "\n" (lib.mapAttrsToList activationSnippet config.boot.binfmt.registrations)}
+    '';
+    systemd.additionalUpstreamSystemUnits = lib.mkIf (config.boot.binfmt.registrations != {})
       [ "proc-sys-fs-binfmt_misc.automount"
         "proc-sys-fs-binfmt_misc.mount"
       ];
diff --git a/nixos/modules/system/boot/kernel.nix b/nixos/modules/system/boot/kernel.nix
index 8ea05ed14687..ab919099d112 100644
--- a/nixos/modules/system/boot/kernel.nix
+++ b/nixos/modules/system/boot/kernel.nix
@@ -5,7 +5,7 @@ with lib;
 let
 
   inherit (config.boot) kernelPatches;
-  inherit (config.boot.kernel) features;
+  inherit (config.boot.kernel) features randstructSeed;
   inherit (config.boot.kernelPackages) kernel;
 
   kernelModulesConf = pkgs.writeText "nixos.conf"
@@ -38,6 +38,7 @@ in
       default = pkgs.linuxPackages;
       apply = kernelPackages: kernelPackages.extend (self: super: {
         kernel = super.kernel.override {
+          inherit randstructSeed;
           kernelPatches = super.kernel.kernelPatches ++ kernelPatches;
           features = lib.recursiveUpdate super.kernel.features features;
         };
@@ -67,6 +68,19 @@ in
       description = "A list of additional patches to apply to the kernel.";
     };
 
+    boot.kernel.randstructSeed = mkOption {
+      type = types.str;
+      default = "";
+      example = "my secret seed";
+      description = ''
+        Provides a custom seed for the <varname>RANDSTRUCT</varname> security
+        option of the Linux kernel. Note that <varname>RANDSTRUCT</varname> is
+        only enabled in NixOS hardened kernels. Using a custom seed requires
+        building the kernel and dependent packages locally, since this
+        customization happens at build time.
+      '';
+    };
+
     boot.kernelParams = mkOption {
       type = types.listOf types.str;
       default = [ ];
@@ -298,7 +312,7 @@ in
       # !!! Should this really be needed?
       (isYes "MODULES")
       (isYes "BINFMT_ELF")
-    ];
+    ] ++ (optional (randstructSeed != "") (isYes "GCC_PLUGIN_RANDSTRUCT"));
 
     # nixpkgs kernels are assumed to have all required features
     assertions = if config.boot.kernelPackages.kernel ? features then [] else
diff --git a/nixos/modules/system/boot/kexec.nix b/nixos/modules/system/boot/kexec.nix
index 61f9c6d0e7eb..fd2cb94b756b 100644
--- a/nixos/modules/system/boot/kexec.nix
+++ b/nixos/modules/system/boot/kexec.nix
@@ -13,8 +13,18 @@
         path = [ pkgs.kexectools ];
         script =
           ''
+            # Don't load the current system profile if we already have a kernel loaded
+            if [[ 1 = "$(</sys/kernel/kexec_loaded)" ]] ; then
+              echo "kexec kernel has already been loaded, prepare-kexec skipped"
+              exit 0
+            fi
+
             p=$(readlink -f /nix/var/nix/profiles/system)
-            if ! [ -d $p ]; then exit 1; fi
+            if ! [[ -d $p ]]; then
+              echo "Could not find system profile for prepare-kexec"
+              exit 1
+            fi
+            echo "Loading NixOS system via kexec."
             exec kexec --load $p/kernel --initrd=$p/initrd --append="$(cat $p/kernel-params) init=$p/init"
           '';
       };
diff --git a/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix b/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix
index ff90a9b46179..2d27611946e2 100644
--- a/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix
+++ b/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix
@@ -7,8 +7,8 @@ let
   generationsDirBuilder = pkgs.substituteAll {
     src = ./generations-dir-builder.sh;
     isExecutable = true;
-    inherit (pkgs.buildPackages) bash;
-    path = with pkgs.buildPackages; [coreutils gnused gnugrep];
+    inherit (pkgs) bash;
+    path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
     inherit (config.boot.loader.generationsDir) copyKernels;
   };
 
diff --git a/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix b/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
index 5f5dbe1092d0..af39c7bb6841 100644
--- a/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
+++ b/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
@@ -8,7 +8,7 @@ let
 
   timeoutStr = if blCfg.timeout == null then "-1" else toString blCfg.timeout;
 
-  builder = import ./extlinux-conf-builder.nix { pkgs = pkgs.buildPackages; };
+  builder = import ./extlinux-conf-builder.nix { inherit pkgs; };
 in
 {
   options = {
diff --git a/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix b/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix
index 9ac6b6b12242..576a07c1d272 100644
--- a/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix
+++ b/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix
@@ -3,6 +3,6 @@
 pkgs.substituteAll {
   src = ./extlinux-conf-builder.sh;
   isExecutable = true;
-  path = [pkgs.buildPackages.coreutils pkgs.buildPackages.gnused pkgs.buildPackages.gnugrep];
-  inherit (pkgs.buildPackages) bash;
+  path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
+  inherit (pkgs) bash;
 }
diff --git a/nixos/modules/system/boot/loader/grub/grub.nix b/nixos/modules/system/boot/loader/grub/grub.nix
index 99aa7759c954..4e4d14985b0d 100644
--- a/nixos/modules/system/boot/loader/grub/grub.nix
+++ b/nixos/modules/system/boot/loader/grub/grub.nix
@@ -360,6 +360,7 @@ in
       font = mkOption {
         type = types.nullOr types.path;
         default = "${realGrub}/share/grub/unicode.pf2";
+        defaultText = ''"''${pkgs.grub2}/share/grub/unicode.pf2"'';
         description = ''
           Path to a TrueType, OpenType, or pf2 font to be used by Grub.
         '';
diff --git a/nixos/modules/system/boot/loader/init-script/init-script.nix b/nixos/modules/system/boot/loader/init-script/init-script.nix
index 385a26036784..374d9524ff1e 100644
--- a/nixos/modules/system/boot/loader/init-script/init-script.nix
+++ b/nixos/modules/system/boot/loader/init-script/init-script.nix
@@ -7,8 +7,8 @@ let
   initScriptBuilder = pkgs.substituteAll {
     src = ./init-script-builder.sh;
     isExecutable = true;
-    inherit (pkgs.buildPackages) bash;
-    path = with pkgs.buildPackages; [coreutils gnused gnugrep];
+    inherit (pkgs) bash;
+    path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
   };
 
 in
diff --git a/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.nix b/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.nix
index 94599a0081c6..9d4f8a93d282 100644
--- a/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.nix
+++ b/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.nix
@@ -18,17 +18,18 @@ let
 
   extlinuxConfBuilder =
     import ../generic-extlinux-compatible/extlinux-conf-builder.nix {
-      pkgs = pkgs.buildPackages;
+      inherit pkgs;
     };
 in
 pkgs.substituteAll {
   src = ./uboot-builder.sh;
   isExecutable = true;
-  inherit (pkgs.buildPackages) bash;
-  path = with pkgs.buildPackages; [coreutils gnused gnugrep];
+  inherit (pkgs) bash;
+  path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
   firmware = pkgs.raspberrypifw;
   inherit uboot;
   inherit configTxt;
   inherit extlinuxConfBuilder;
   inherit version;
 }
+
diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
index 6016a85ea061..ebe37ca10a2d 100644
--- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
@@ -33,6 +33,15 @@ initrd {initrd}
 options {kernel_params}
 """
 
+# The boot loader entry for memtest86.
+#
+# TODO: This is hard-coded to use the 64-bit EFI app, but it could probably
+# be updated to use the 32-bit EFI app on 32-bit systems.  The 32-bit EFI
+# app filename is BOOTIA32.efi.
+MEMTEST_BOOT_ENTRY = """title MemTest86
+efi /efi/memtest86/BOOTX64.efi
+"""
+
 def write_loader_conf(profile, generation):
     with open("@efiSysMountPoint@/loader/loader.conf.tmp", 'w') as f:
         if "@timeout@" != "":
@@ -145,7 +154,7 @@ def remove_old_entries(gens):
         except ValueError:
             pass
     for path in glob.iglob("@efiSysMountPoint@/efi/nixos/*"):
-        if not path in known_paths:
+        if not path in known_paths and not os.path.isdir(path):
             os.unlink(path)
 
 def get_profiles():
@@ -199,6 +208,24 @@ def main():
         if os.readlink(system_dir(*gen)) == args.default_config:
             write_loader_conf(*gen)
 
+    memtest_entry_file = "@efiSysMountPoint@/loader/entries/memtest86.conf"
+    if os.path.exists(memtest_entry_file):
+        os.unlink(memtest_entry_file)
+    shutil.rmtree("@efiSysMountPoint@/efi/memtest86", ignore_errors=True)
+    if "@memtest86@" != "":
+        mkdir_p("@efiSysMountPoint@/efi/memtest86")
+        for path in glob.iglob("@memtest86@/*"):
+            if os.path.isdir(path):
+                shutil.copytree(path, os.path.join("@efiSysMountPoint@/efi/memtest86", os.path.basename(path)))
+            else:
+                shutil.copy(path, "@efiSysMountPoint@/efi/memtest86/")
+
+        memtest_entry_file = "@efiSysMountPoint@/loader/entries/memtest86.conf"
+        memtest_entry_file_tmp_path = "%s.tmp" % memtest_entry_file
+        with open(memtest_entry_file_tmp_path, 'w') as f:
+            f.write(MEMTEST_BOOT_ENTRY)
+        os.rename(memtest_entry_file_tmp_path, memtest_entry_file)
+
     # Since fat32 provides little recovery facilities after a crash,
     # it can leave the system in an unbootable state, when a crash/outage
     # happens shortly after an update. To decrease the likelihood of this
diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
index 9ad2a2779e18..3e39dc5dd396 100644
--- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
@@ -12,7 +12,7 @@ let
 
     isExecutable = true;
 
-    inherit (pkgs.buildPackages) python3;
+    inherit (pkgs) python3;
 
     systemd = config.systemd.package;
 
@@ -25,6 +25,8 @@ let
     inherit (cfg) consoleMode;
 
     inherit (efi) efiSysMountPoint canTouchEfiVariables;
+
+    memtest86 = if cfg.memtest86.enable then pkgs.memtest86-efi else "";
   };
 in {
 
@@ -62,8 +64,7 @@ in {
 
       description = ''
         The resolution of the console. The following values are valid:
-        </para>
-        <para>
+
         <itemizedlist>
           <listitem><para>
             <literal>"0"</literal>: Standard UEFI 80x25 mode
@@ -86,6 +87,19 @@ in {
         </itemizedlist>
       '';
     };
+
+    memtest86 = {
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Make MemTest86 available from the systemd-boot menu. MemTest86 is a
+          program for testing memory.  MemTest86 is an unfree program, so
+          this requires <literal>allowUnfree</literal> to be set to
+          <literal>true</literal>.
+        '';
+      };
+    };
   };
 
   config = mkIf cfg.enable {
diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix
index aa4a5f8abcce..b5c8d5241a3d 100644
--- a/nixos/modules/system/boot/luksroot.nix
+++ b/nixos/modules/system/boot/luksroot.nix
@@ -76,6 +76,33 @@ let
         fi
         return 0
     }
+
+    wait_gpgcard() {
+        local secs="''${1:-10}"
+
+        gpg --card-status > /dev/null 2> /dev/null
+        if [ $? != 0 ]; then
+            echo -n "Waiting $secs seconds for GPG Card to appear"
+            local success=false
+            for try in $(seq $secs); do
+                echo -n .
+                sleep 1
+                gpg --card-status > /dev/null 2> /dev/null
+                if [ $? == 0 ]; then
+                    success=true
+                    break
+                fi
+            done
+            if [ $success == true ]; then
+                echo " - success";
+                return 0
+            else
+                echo " - failure";
+                return 1
+            fi
+        fi
+        return 0
+    }
   '';
 
   preCommands = ''
@@ -87,9 +114,19 @@ let
     mkdir -p /crypt-ramfs
     mount -t ramfs none /crypt-ramfs
 
+    # Cryptsetup locking directory
+    mkdir -p /run/cryptsetup
+
     # For Yubikey salt storage
     mkdir -p /crypt-storage
 
+    ${optionalString luks.gpgSupport ''
+    export GPG_TTY=$(tty)
+    export GNUPGHOME=/crypt-ramfs/.gnupg
+
+    gpg-agent --daemon --scdaemon-program $out/bin/scdaemon > /dev/null 2> /dev/null
+    ''}
+        
     # Disable all input echo for the whole stage. We could use read -s
     # instead but that would ocasionally leak characters between read
     # invocations.
@@ -102,7 +139,7 @@ let
     umount /crypt-ramfs 2>/dev/null
   '';
 
-  openCommand = name': { name, device, header, keyFile, keyFileSize, keyFileOffset, allowDiscards, yubikey, fallbackToPassword, ... }: assert name' == name;
+  openCommand = name': { name, device, header, keyFile, keyFileSize, keyFileOffset, allowDiscards, yubikey, gpgCard, fallbackToPassword, ... }: assert name' == name;
   let
     csopen   = "cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} ${optionalString (header != null) "--header=${header}"}";
     cschange = "cryptsetup luksChangeKey ${device} ${optionalString (header != null) "--header=${header}"}";
@@ -179,7 +216,7 @@ let
         ''}
     }
 
-    ${if luks.yubikeySupport && (yubikey != null) then ''
+    ${optionalString (luks.yubikeySupport && (yubikey != null)) ''
     # Yubikey
     rbtohex() {
         ( od -An -vtx1 | tr -d ' \n' )
@@ -275,7 +312,7 @@ let
         umount /crypt-storage
     }
 
-    open_yubikey() {
+    open_with_hardware() {
         if wait_yubikey ${toString yubikey.gracePeriod}; then
             do_open_yubikey
         else
@@ -283,8 +320,75 @@ let
             open_normally
         fi
     }
+    ''}
+
+    ${optionalString (luks.gpgSupport && (gpgCard != null)) ''
+
+    do_open_gpg_card() {
+        # Make all of these local to this function
+        # to prevent their values being leaked
+        local pin
+        local opened
+
+        gpg --import /gpg-keys/${device}/pubkey.asc > /dev/null 2> /dev/null
+
+        gpg --card-status > /dev/null 2> /dev/null
+
+        for try in $(seq 3); do
+            echo -n "PIN for GPG Card associated with device ${device}: "
+            pin=
+            while true; do
+                if [ -e /crypt-ramfs/passphrase ]; then
+                    echo "reused"
+                    pin=$(cat /crypt-ramfs/passphrase)
+                    break
+                else
+                    # and try reading it from /dev/console with a timeout
+                    IFS= read -t 1 -r pin
+                    if [ -n "$pin" ]; then
+                       ${if luks.reusePassphrases then ''
+                         # remember it for the next device
+                         echo -n "$pin" > /crypt-ramfs/passphrase
+                       '' else ''
+                         # Don't save it to ramfs. We are very paranoid
+                       ''}
+                       echo
+                       break
+                    fi
+                fi
+            done
+            echo -n "Verifying passphrase for ${device}..."
+            echo -n "$pin" | gpg -q --batch --passphrase-fd 0 --pinentry-mode loopback -d /gpg-keys/${device}/cryptkey.gpg 2> /dev/null | ${csopen} --key-file=- > /dev/null 2> /dev/null
+            if [ $? == 0 ]; then
+                echo " - success"
+                ${if luks.reusePassphrases then ''
+                  # we don't rm here because we might reuse it for the next device
+                '' else ''
+                  rm -f /crypt-ramfs/passphrase
+                ''}
+                break
+            else
+                echo " - failure"
+                # ask for a different one
+                rm -f /crypt-ramfs/passphrase
+            fi
+        done
+
+        [ "$opened" == false ] && die "Maximum authentication errors reached"
+    }
+
+    open_with_hardware() {
+        if wait_gpgcard ${toString gpgCard.gracePeriod}; then
+            do_open_gpg_card
+        else
+            echo "No GPG Card found, falling back to normal open procedure"
+            open_normally
+        fi
+    }
+    ''}
 
-    open_yubikey
+    ${if (luks.yubikeySupport && (yubikey != null)) || (luks.gpgSupport && (gpgCard != null)) then ''
+    open_with_hardware
     '' else ''
     open_normally
     ''}
@@ -334,6 +438,7 @@ in
       default =
         [ "aes" "aes_generic" "blowfish" "twofish"
           "serpent" "cbc" "xts" "lrw" "sha1" "sha256" "sha512"
+          "af_alg" "algif_skcipher"
 
           (if pkgs.stdenv.hostPlatform.system == "x86_64-linux" then "aes_x86_64" else "aes_i586")
         ];
@@ -470,6 +575,36 @@ in
             '';
           };
 
+          gpgCard = mkOption {
+            default = null;
+            description = ''
+              The option to use this LUKS device with a GPG encrypted luks password by the GPG Smartcard.
+              If null (the default), GPG-Smartcard will be disabled for this device.
+            '';
+
+            type = with types; nullOr (submodule {
+              options = {
+                gracePeriod = mkOption {
+                  default = 10;
+                  type = types.int;
+                  description = "Time in seconds to wait for the GPG Smartcard.";
+                };
+
+                encryptedPass = mkOption {
+                  default = "";
+                  type = types.path;
+                  description = "Path to the GPG encrypted passphrase.";
+                };
+
+                publicKey = mkOption {
+                  default = "";
+                  type = types.path;
+                  description = "Path to the Public Key.";
+                };
+              };
+            });
+          };
+
           yubikey = mkOption {
             default = null;
             description = ''
@@ -551,6 +686,14 @@ in
       }));
     };
 
+    boot.initrd.luks.gpgSupport = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Enables support for authenticating with a GPG encrypted password.
+      '';
+    };
+
     boot.initrd.luks.yubikeySupport = mkOption {
       default = false;
       type = types.bool;
@@ -564,6 +707,12 @@ in
 
   config = mkIf (luks.devices != {} || luks.forceLuksSupportInInitrd) {
 
+    assertions =
+      [ { assertion = !(luks.gpgSupport && luks.yubikeySupport);
+          message = "Yubikey and GPG Card may not be used at the same time.";
+        }
+      ];
+
     # actually, sbp2 driver is the one enabling the DMA attack, but this needs to be tested
     boot.blacklistedKernelModules = optionals luks.mitigateDMAAttacks
       ["firewire_ohci" "firewire_core" "firewire_sbp2"];
@@ -600,6 +749,23 @@ in
         EOF
         chmod +x $out/bin/openssl-wrap
       ''}
+
+      ${optionalString luks.gpgSupport ''
+        copy_bin_and_libs ${pkgs.gnupg}/bin/gpg
+        copy_bin_and_libs ${pkgs.gnupg}/bin/gpg-agent
+        copy_bin_and_libs ${pkgs.gnupg}/libexec/scdaemon
+
+        ${concatMapStringsSep "\n" (x:
+          if x.gpgCard != null then
+            ''
+              mkdir -p $out/secrets/gpg-keys/${x.device}
+              cp -a ${x.gpgCard.encryptedPass} $out/secrets/gpg-keys/${x.device}/cryptkey.gpg
+              cp -a ${x.gpgCard.publicKey} $out/secrets/gpg-keys/${x.device}/pubkey.asc
+            ''
+          else ""
+          ) (attrValues luks.devices)
+        }
+      ''}
     '';
 
     boot.initrd.extraUtilsCommandsTest = ''
@@ -609,6 +775,11 @@ in
         $out/bin/ykinfo -V
         $out/bin/openssl-wrap version
       ''}
+      ${optionalString luks.gpgSupport ''
+        $out/bin/gpg --version
+        $out/bin/gpg-agent --version
+        $out/bin/scdaemon --version
+      ''}
     '';
 
     boot.initrd.preFailCommands = postCommands;
diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix
index 63a6f7fbe099..882db9130ea8 100644
--- a/nixos/modules/system/boot/networkd.nix
+++ b/nixos/modules/system/boot/networkd.nix
@@ -669,7 +669,7 @@ let
     };
   };
 
-  commonMatchText = def: ''
+  commonMatchText = def: optionalString (def.matchConfig != {}) ''
     [Match]
     ${attrsToSection def.matchConfig}
   '';
diff --git a/nixos/modules/system/boot/resolved.nix b/nixos/modules/system/boot/resolved.nix
index fc68904ae080..5c66cf4a6e6e 100644
--- a/nixos/modules/system/boot/resolved.nix
+++ b/nixos/modules/system/boot/resolved.nix
@@ -35,7 +35,7 @@ in
         when resolving single-label host names (domain names which
         contain no dot), in order to qualify them into fully-qualified
         domain names (FQDNs).
-        </para><para>
+
         For compatibility reasons, if this setting is not specified,
         the search domains listed in
         <filename>/etc/resolv.conf</filename> are used instead, if
@@ -50,8 +50,9 @@ in
       description = ''
         Controls Link-Local Multicast Name Resolution support
         (RFC 4795) on the local host.
-        </para><para>
+
         If set to
+
         <variablelist>
         <varlistentry>
           <term><literal>"true"</literal></term>
diff --git a/nixos/modules/system/boot/stage-1-init.sh b/nixos/modules/system/boot/stage-1-init.sh
index 6dafc6cddde7..67cbe720ddc3 100644
--- a/nixos/modules/system/boot/stage-1-init.sh
+++ b/nixos/modules/system/boot/stage-1-init.sh
@@ -555,7 +555,7 @@ echo /sbin/modprobe > /proc/sys/kernel/modprobe
 # Start stage 2.  `switch_root' deletes all files in the ramfs on the
 # current root.  Note that $stage2Init might be an absolute symlink,
 # in which case "-e" won't work because we're not in the chroot yet.
-if ! test -e "$targetRoot/$stage2Init" -o ! -L "$targetRoot/$stage2Init"; then
+if [ ! -e "$targetRoot/$stage2Init" ] && [ ! -L "$targetRoot/$stage2Init" ] ; then
     echo "stage 2 init script ($targetRoot/$stage2Init) not found"
     fail
 fi
diff --git a/nixos/modules/system/boot/stage-1.nix b/nixos/modules/system/boot/stage-1.nix
index 8702abd3df83..788e3f4a2ab1 100644
--- a/nixos/modules/system/boot/stage-1.nix
+++ b/nixos/modules/system/boot/stage-1.nix
@@ -32,7 +32,7 @@ let
   fileSystems = filter utils.fsNeededForBoot config.system.build.fileSystems;
 
   # A utility for enumerating the shared-library dependencies of a program
-  findLibs = pkgs.writeShellScriptBin "find-libs" ''
+  findLibs = pkgs.buildPackages.writeShellScriptBin "find-libs" ''
     set -euo pipefail
 
     declare -A seen
diff --git a/nixos/modules/tasks/filesystems/btrfs.nix b/nixos/modules/tasks/filesystems/btrfs.nix
index 1384873b6631..48be18c71021 100644
--- a/nixos/modules/tasks/filesystems/btrfs.nix
+++ b/nixos/modules/tasks/filesystems/btrfs.nix
@@ -19,7 +19,7 @@ in
     # One could also do regular btrfs balances, but that shouldn't be necessary
     # during normal usage and as long as the filesystems aren't filled near capacity
     services.btrfs.autoScrub = {
-      enable = mkEnableOption "Enable regular btrfs scrub";
+      enable = mkEnableOption "regular btrfs scrub";
 
       fileSystems = mkOption {
         type = types.listOf types.path;
diff --git a/nixos/modules/tasks/filesystems/zfs.nix b/nixos/modules/tasks/filesystems/zfs.nix
index 37a19fb9fc8c..93bbd141284d 100644
--- a/nixos/modules/tasks/filesystems/zfs.nix
+++ b/nixos/modules/tasks/filesystems/zfs.nix
@@ -24,11 +24,9 @@ let
   kernel = config.boot.kernelPackages;
 
   packages = if config.boot.zfs.enableUnstable then {
-    spl = null;
     zfs = kernel.zfsUnstable;
     zfsUser = pkgs.zfsUnstable;
   } else {
-    spl = kernel.spl;
     zfs = kernel.zfs;
     zfsUser = pkgs.zfs;
   };
@@ -325,8 +323,8 @@ in
       virtualisation.lxd.zfsSupport = true;
 
       boot = {
-        kernelModules = [ "zfs" ] ++ optional (!cfgZfs.enableUnstable) "spl";
-        extraModulePackages = with packages; [ zfs ] ++ optional (!cfgZfs.enableUnstable) spl;
+        kernelModules = [ "zfs" ];
+        extraModulePackages = with packages; [ zfs ];
       };
 
       boot.initrd = mkIf inInitrd {
diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix
index d10c4feecb43..2235eec9d95a 100644
--- a/nixos/modules/virtualisation/containers.nix
+++ b/nixos/modules/virtualisation/containers.nix
@@ -465,20 +465,24 @@ in
                 merge = loc: defs: (import ../../lib/eval-config.nix {
                   inherit system;
                   modules =
-                    let extraConfig =
-                      { boot.isContainer = true;
-                        networking.hostName = mkDefault name;
-                        networking.useDHCP = false;
-                        assertions = [
-                          {
-                            assertion =  config.privateNetwork -> stringLength name < 12;
-                            message = ''
-                              Container name `${name}` is too long: When `privateNetwork` is enabled, container names can
-                              not be longer than 11 characters, because the container's interface name is derived from it.
-                              This might be fixed in the future. See https://github.com/NixOS/nixpkgs/issues/38509
-                            '';
-                          }
-                        ];
+                    let
+                      extraConfig = {
+                        _file = "module at ${__curPos.file}:${toString __curPos.line}";
+                        config = {
+                          boot.isContainer = true;
+                          networking.hostName = mkDefault name;
+                          networking.useDHCP = false;
+                          assertions = [
+                            {
+                              assertion =  config.privateNetwork -> stringLength name < 12;
+                              message = ''
+                                Container name `${name}` is too long: When `privateNetwork` is enabled, container names can
+                                not be longer than 11 characters, because the container's interface name is derived from it.
+                                This might be fixed in the future. See https://github.com/NixOS/nixpkgs/issues/38509
+                              '';
+                            }
+                          ];
+                        };
                       };
                     in [ extraConfig ] ++ (map (x: x.value) defs);
                   prefix = [ "containers" name ];
@@ -689,7 +693,7 @@ in
       [{ name = "container@"; value = unit; }]
       # declarative containers
       ++ (mapAttrsToList (name: cfg: nameValuePair "container@${name}" (let
-          config = cfg // (
+          containerConfig = cfg // (
           if cfg.enableTun then
             {
               allowedDevices = cfg.allowedDevices
@@ -700,18 +704,21 @@ in
           else {});
         in
           unit // {
-            preStart = preStartScript config;
-            script = startScript config;
-            postStart = postStartScript config;
-            serviceConfig = serviceDirectives config;
+            preStart = preStartScript containerConfig;
+            script = startScript containerConfig;
+            postStart = postStartScript containerConfig;
+            serviceConfig = serviceDirectives containerConfig;
           } // (
-          if config.autoStart then
+          if containerConfig.autoStart then
             {
               wantedBy = [ "machines.target" ];
               wants = [ "network.target" ];
               after = [ "network.target" ];
-              restartTriggers = [ config.path ];
-              reloadIfChanged = true;
+              restartTriggers = [
+                containerConfig.path
+                config.environment.etc."containers/${name}.conf".source
+              ];
+              restartIfChanged = true;
             }
           else {})
       )) config.containers)
diff --git a/nixos/modules/virtualisation/docker-containers.nix b/nixos/modules/virtualisation/docker-containers.nix
index c4e47bfa477c..3e882a1383ff 100644
--- a/nixos/modules/virtualisation/docker-containers.nix
+++ b/nixos/modules/virtualisation/docker-containers.nix
@@ -65,10 +65,9 @@ let
           default = [];
           description = ''
             Network ports to publish from the container to the outer host.
-            </para>
-            <para>
+
             Valid formats:
-            </para>
+
             <itemizedlist>
               <listitem>
                 <para>
@@ -91,21 +90,19 @@ let
                 </para>
               </listitem>
             </itemizedlist>
-            <para>
+
             Both <literal>hostPort</literal> and
             <literal>containerPort</literal> can be specified as a range of
             ports.  When specifying ranges for both, the number of container
             ports in the range must match the number of host ports in the
             range.  Example: <literal>1234-1236:1234-1236/tcp</literal>
-            </para>
-            <para>
+
             When specifying a range for <literal>hostPort</literal> only, the
             <literal>containerPort</literal> must <emphasis>not</emphasis> be a
             range.  In this case, the container port is published somewhere
             within the specified <literal>hostPort</literal> range.  Example:
             <literal>1234-1236:1234/tcp</literal>
-            </para>
-            <para>
+
             Refer to the
             <link xlink:href="https://docs.docker.com/engine/reference/run/#expose-incoming-ports">
             Docker engine documentation</link> for full details.
@@ -177,13 +174,13 @@ let
         "--rm"
         "--name=%n"
         "--log-driver=${container.log-driver}"
-      ] ++ optional (! isNull container.entrypoint)
+      ] ++ optional (container.entrypoint != null)
         "--entrypoint=${escapeShellArg container.entrypoint}"
         ++ (mapAttrsToList (k: v: "-e ${escapeShellArg k}=${escapeShellArg v}") container.environment)
         ++ map (p: "-p ${escapeShellArg p}") container.ports
-        ++ optional (! isNull container.user) "-u ${escapeShellArg container.user}"
+        ++ optional (container.user != null) "-u ${escapeShellArg container.user}"
         ++ map (v: "-v ${escapeShellArg v}") container.volumes
-        ++ optional (! isNull container.workdir) "-w ${escapeShellArg container.workdir}"
+        ++ optional (container.workdir != null) "-w ${escapeShellArg container.workdir}"
         ++ map escapeShellArg container.extraDockerOptions
         ++ [container.image]
         ++ map escapeShellArg container.cmd
diff --git a/nixos/modules/virtualisation/docker.nix b/nixos/modules/virtualisation/docker.nix
index ba04dfd57942..7d196a46276a 100644
--- a/nixos/modules/virtualisation/docker.nix
+++ b/nixos/modules/virtualisation/docker.nix
@@ -46,8 +46,8 @@ in
         description =
           ''
             When enabled dockerd is started on boot. This is required for
-            container, which are created with the
-            <literal>--restart=always</literal> flag, to work. If this option is
+            containers which are created with the
+            <literal>--restart=always</literal> flag to work. If this option is
             disabled, docker might be started on demand by socket activation.
           '';
       };
diff --git a/nixos/modules/virtualisation/ec2-amis.nix b/nixos/modules/virtualisation/ec2-amis.nix
index aaea06bb9a63..f640bb21b133 100644
--- a/nixos/modules/virtualisation/ec2-amis.nix
+++ b/nixos/modules/virtualisation/ec2-amis.nix
@@ -274,5 +274,22 @@ let self = {
   "18.09".sa-east-1.hvm-ebs = "ami-0e4a8a47fd6db6112";
   "18.09".ap-south-1.hvm-ebs = "ami-0880a678d3f555313";
 
-  latest = self."18.09";
+  # 19.03.172286.8ea36d73256
+  "19.03".eu-west-1.hvm-ebs = "ami-0fe40176548ff0940";
+  "19.03".eu-west-2.hvm-ebs = "ami-03a40fd3a02fe95ba";
+  "19.03".eu-west-3.hvm-ebs = "ami-0436f9da0f20a638e";
+  "19.03".eu-central-1.hvm-ebs = "ami-0022b8ea9efde5de4";
+  "19.03".us-east-1.hvm-ebs = "ami-0efc58fb70ae9a217";
+  "19.03".us-east-2.hvm-ebs = "ami-0abf711b1b34da1af";
+  "19.03".us-west-1.hvm-ebs = "ami-07d126e8838c40ec5";
+  "19.03".us-west-2.hvm-ebs = "ami-03f8a737546e47fb0";
+  "19.03".ca-central-1.hvm-ebs = "ami-03f9fd0ef2e035ede";
+  "19.03".ap-southeast-1.hvm-ebs = "ami-0cff66114c652c262";
+  "19.03".ap-southeast-2.hvm-ebs = "ami-054c73a7f8d773ea9";
+  "19.03".ap-northeast-1.hvm-ebs = "ami-00db62688900456a4";
+  "19.03".ap-northeast-2.hvm-ebs = "ami-0485cdd1a5fdd2117";
+  "19.03".sa-east-1.hvm-ebs = "ami-0c6a43c6e0ad1f4e2";
+  "19.03".ap-south-1.hvm-ebs = "ami-0303deb1b5890f878";
+
+  latest = self."19.03";
 }; in self
diff --git a/nixos/modules/virtualisation/google-compute-config.nix b/nixos/modules/virtualisation/google-compute-config.nix
index 8c7331fe4d2b..2ee22b80f2f5 100644
--- a/nixos/modules/virtualisation/google-compute-config.nix
+++ b/nixos/modules/virtualisation/google-compute-config.nix
@@ -12,6 +12,7 @@ in
 
 
   fileSystems."/" = {
+    fsType = "ext4";
     device = "/dev/disk/by-label/nixos";
     autoResize = true;
   };
diff --git a/nixos/modules/virtualisation/google-compute-image.nix b/nixos/modules/virtualisation/google-compute-image.nix
index 0d2d25d30752..d172ae38fdcf 100644
--- a/nixos/modules/virtualisation/google-compute-image.nix
+++ b/nixos/modules/virtualisation/google-compute-image.nix
@@ -51,7 +51,7 @@ in
         popd
       '';
       format = "raw";
-      configFile = if isNull cfg.configFile then defaultConfigFile else cfg.configFile;
+      configFile = if cfg.configFile == null then defaultConfigFile else cfg.configFile;
       inherit (cfg) diskSize;
       inherit config lib pkgs;
     };
diff --git a/nixos/modules/virtualisation/virtualbox-host.nix b/nixos/modules/virtualisation/virtualbox-host.nix
index 6f7370181740..41bcb909fb5c 100644
--- a/nixos/modules/virtualisation/virtualbox-host.nix
+++ b/nixos/modules/virtualisation/virtualbox-host.nix
@@ -104,7 +104,7 @@ in
       "VBoxNetNAT"
       "VBoxSDL"
       "VBoxVolInfo"
-      "VirtualBox"
+      "VirtualBoxVM"
     ]));
 
     users.groups.vboxusers.gid = config.ids.gids.vboxusers;
diff --git a/nixos/modules/virtualisation/virtualbox-image.nix b/nixos/modules/virtualisation/virtualbox-image.nix
index 903411799d3e..ab65523592d7 100644
--- a/nixos/modules/virtualisation/virtualbox-image.nix
+++ b/nixos/modules/virtualisation/virtualbox-image.nix
@@ -100,6 +100,11 @@ in {
     boot.growPartition = true;
     boot.loader.grub.device = "/dev/sda";
 
+    swapDevices = [{
+      device = "/var/swap";
+      size = 2048;
+    }];
+
     virtualisation.virtualbox.guest.enable = true;
 
   };