about summary refs log tree commit diff
path: root/nixos/modules/services
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/services')
-rw-r--r--nixos/modules/services/audio/mopidy.nix10
-rw-r--r--nixos/modules/services/audio/roon-server.nix49
-rw-r--r--nixos/modules/services/audio/slimserver.nix6
-rw-r--r--nixos/modules/services/audio/snapserver.nix1
-rw-r--r--nixos/modules/services/audio/spotifyd.nix42
-rw-r--r--nixos/modules/services/backup/duplicati.nix16
-rw-r--r--nixos/modules/services/backup/tsm.nix106
-rw-r--r--nixos/modules/services/backup/zfs-replication.nix90
-rw-r--r--nixos/modules/services/cluster/kubernetes/default.nix7
-rw-r--r--nixos/modules/services/cluster/kubernetes/kubelet.nix7
-rw-r--r--nixos/modules/services/cluster/kubernetes/pki.nix1
-rw-r--r--nixos/modules/services/computing/boinc/client.nix9
-rw-r--r--nixos/modules/services/databases/cassandra.nix210
-rw-r--r--nixos/modules/services/databases/firebird.nix14
-rw-r--r--nixos/modules/services/databases/foundationdb.nix22
-rw-r--r--nixos/modules/services/databases/foundationdb.xml38
-rw-r--r--nixos/modules/services/databases/hbase.nix17
-rw-r--r--nixos/modules/services/databases/mysql.nix207
-rw-r--r--nixos/modules/services/databases/openldap.nix4
-rw-r--r--nixos/modules/services/databases/postgresql.nix41
-rw-r--r--nixos/modules/services/databases/postgresql.xml76
-rw-r--r--nixos/modules/services/desktops/deepin/deepin.nix4
-rw-r--r--nixos/modules/services/desktops/flatpak.nix28
-rw-r--r--nixos/modules/services/desktops/flatpak.xml24
-rw-r--r--nixos/modules/services/desktops/tumbler.nix2
-rw-r--r--nixos/modules/services/development/bloop.nix23
-rw-r--r--nixos/modules/services/editors/emacs.xml13
-rw-r--r--nixos/modules/services/hardware/80-net-setup-link.rules13
-rw-r--r--nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix4
-rw-r--r--nixos/modules/services/hardware/throttled.nix11
-rw-r--r--nixos/modules/services/hardware/triggerhappy.nix2
-rw-r--r--nixos/modules/services/hardware/udev.nix8
-rw-r--r--nixos/modules/services/logging/graylog.nix17
-rw-r--r--nixos/modules/services/logging/heartbeat.nix6
-rw-r--r--nixos/modules/services/mail/dspam.nix13
-rw-r--r--nixos/modules/services/mail/opendkim.nix7
-rw-r--r--nixos/modules/services/mail/postfix.nix19
-rw-r--r--nixos/modules/services/mail/rspamd.nix1
-rw-r--r--nixos/modules/services/misc/apache-kafka.nix9
-rw-r--r--nixos/modules/services/misc/couchpotato.nix7
-rw-r--r--nixos/modules/services/misc/gitea.nix125
-rw-r--r--nixos/modules/services/misc/gitlab.nix94
-rw-r--r--nixos/modules/services/misc/gitlab.xml12
-rw-r--r--nixos/modules/services/misc/gitolite.nix29
-rw-r--r--nixos/modules/services/misc/gollum.nix15
-rw-r--r--nixos/modules/services/misc/greenclip.nix31
-rw-r--r--nixos/modules/services/misc/jackett.nix9
-rw-r--r--nixos/modules/services/misc/lidarr.nix57
-rw-r--r--nixos/modules/services/misc/nix-daemon.nix7
-rw-r--r--nixos/modules/services/misc/octoprint.nix7
-rw-r--r--nixos/modules/services/misc/phd.nix52
-rw-r--r--nixos/modules/services/misc/redmine.nix75
-rw-r--r--nixos/modules/services/misc/taskserver/default.nix2
-rw-r--r--nixos/modules/services/misc/taskserver/doc.xml2
-rw-r--r--nixos/modules/services/misc/tiddlywiki.nix52
-rw-r--r--nixos/modules/services/misc/zoneminder.nix25
-rw-r--r--nixos/modules/services/monitoring/alerta.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/kapacitor.nix11
-rw-r--r--nixos/modules/services/monitoring/loki.nix112
-rw-r--r--nixos/modules/services/monitoring/nagios.nix23
-rw-r--r--nixos/modules/services/monitoring/netdata.nix27
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters.nix90
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters.xml152
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/bind.nix3
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix3
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/collectd.nix3
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix3
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix7
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix3
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/json.nix3
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/mail.nix157
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/minio.nix3
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/nginx.nix27
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/node.nix6
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/postfix.nix3
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/postgres.nix47
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/snmp.nix3
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/surfboard.nix3
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/tor.nix3
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/unifi.nix3
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/varnish.nix4
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix37
-rw-r--r--nixos/modules/services/monitoring/riemann-dash.nix8
-rw-r--r--nixos/modules/services/monitoring/riemann-tools.nix1
-rw-r--r--nixos/modules/services/monitoring/scollector.nix1
-rw-r--r--nixos/modules/services/monitoring/thanos.nix801
-rw-r--r--nixos/modules/services/monitoring/zabbix-agent.nix163
-rw-r--r--nixos/modules/services/monitoring/zabbix-proxy.nix298
-rw-r--r--nixos/modules/services/monitoring/zabbix-server.nix341
-rw-r--r--nixos/modules/services/network-filesystems/kbfs.nix1
-rw-r--r--nixos/modules/services/network-filesystems/samba.nix8
-rw-r--r--nixos/modules/services/networking/aria2.nix14
-rw-r--r--nixos/modules/services/networking/autossh.nix1
-rw-r--r--nixos/modules/services/networking/avahi-daemon.nix344
-rw-r--r--nixos/modules/services/networking/bind.nix8
-rw-r--r--nixos/modules/services/networking/bitcoind.nix2
-rw-r--r--nixos/modules/services/networking/charybdis.nix9
-rw-r--r--nixos/modules/services/networking/ddclient.nix2
-rw-r--r--nixos/modules/services/networking/dhcpcd.nix2
-rw-r--r--nixos/modules/services/networking/dnscrypt-proxy.xml8
-rw-r--r--nixos/modules/services/networking/dnsmasq.nix11
-rw-r--r--nixos/modules/services/networking/hostapd.nix14
-rw-r--r--nixos/modules/services/networking/iperf3.nix10
-rw-r--r--nixos/modules/services/networking/keybase.nix1
-rw-r--r--nixos/modules/services/networking/kresd.nix7
-rw-r--r--nixos/modules/services/networking/minidlna.nix8
-rw-r--r--nixos/modules/services/networking/networkmanager.nix159
-rw-r--r--nixos/modules/services/networking/quassel.nix10
-rw-r--r--nixos/modules/services/networking/rdnssd.nix5
-rw-r--r--nixos/modules/services/networking/smokeping.nix22
-rw-r--r--nixos/modules/services/networking/squid.nix3
-rw-r--r--nixos/modules/services/networking/ssh/sshd.nix10
-rw-r--r--nixos/modules/services/networking/syncthing.nix22
-rw-r--r--nixos/modules/services/networking/teamspeak3.nix10
-rw-r--r--nixos/modules/services/networking/thelounge.nix75
-rw-r--r--nixos/modules/services/networking/unbound.nix2
-rw-r--r--nixos/modules/services/networking/unifi.nix11
-rw-r--r--nixos/modules/services/networking/wpa_supplicant.nix1
-rw-r--r--nixos/modules/services/networking/zeronet.nix13
-rw-r--r--nixos/modules/services/security/sshguard.nix2
-rw-r--r--nixos/modules/services/security/tor.nix2
-rw-r--r--nixos/modules/services/system/kerberos/default.nix2
-rw-r--r--nixos/modules/services/system/kerberos/heimdal.nix2
-rw-r--r--nixos/modules/services/system/kerberos/mit.nix2
-rw-r--r--nixos/modules/services/system/localtime.nix31
-rw-r--r--nixos/modules/services/system/nscd.conf18
-rw-r--r--nixos/modules/services/system/nscd.nix31
-rw-r--r--nixos/modules/services/torrent/deluge.nix91
-rw-r--r--nixos/modules/services/web-apps/atlassian/confluence.nix18
-rw-r--r--nixos/modules/services/web-apps/atlassian/crowd.nix17
-rw-r--r--nixos/modules/services/web-apps/atlassian/jira.nix18
-rw-r--r--nixos/modules/services/web-apps/frab.nix10
-rw-r--r--nixos/modules/services/web-apps/limesurvey.nix20
-rw-r--r--nixos/modules/services/web-apps/matomo-doc.xml22
-rw-r--r--nixos/modules/services/web-apps/mediawiki.nix473
-rw-r--r--nixos/modules/services/web-apps/nextcloud.nix35
-rw-r--r--nixos/modules/services/web-apps/nextcloud.xml64
-rw-r--r--nixos/modules/services/web-apps/restya-board.nix2
-rw-r--r--nixos/modules/services/web-apps/tt-rss.nix63
-rw-r--r--nixos/modules/services/web-apps/wordpress.nix371
-rw-r--r--nixos/modules/services/web-apps/zabbix.nix225
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/default.nix11
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/foswiki.nix78
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/mediawiki.nix349
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/mercurial.nix75
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/per-server-options.nix8
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/phabricator.nix50
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/tomcat-connector.nix103
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/trac.nix121
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/wordpress.nix285
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/zabbix.nix84
-rw-r--r--nixos/modules/services/web-servers/lighttpd/cgit.nix4
-rw-r--r--nixos/modules/services/web-servers/lighttpd/collectd.nix2
-rw-r--r--nixos/modules/services/web-servers/nginx/default.nix11
-rw-r--r--nixos/modules/services/x11/compton.nix136
-rw-r--r--nixos/modules/services/x11/desktop-managers/default.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/enlightenment.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/gnome3.nix5
-rw-r--r--nixos/modules/services/x11/desktop-managers/pantheon.nix8
-rw-r--r--nixos/modules/services/x11/desktop-managers/plasma5.nix24
-rw-r--r--nixos/modules/services/x11/desktop-managers/surf-display.nix127
-rw-r--r--nixos/modules/services/x11/desktop-managers/xterm.nix4
-rw-r--r--nixos/modules/services/x11/display-managers/gdm.nix46
-rw-r--r--nixos/modules/services/x11/extra-layouts.nix165
-rw-r--r--nixos/modules/services/x11/gdk-pixbuf.nix8
-rw-r--r--nixos/modules/services/x11/hardware/libinput.nix2
-rw-r--r--nixos/modules/services/x11/xserver.nix14
169 files changed, 5486 insertions, 2692 deletions
diff --git a/nixos/modules/services/audio/mopidy.nix b/nixos/modules/services/audio/mopidy.nix
index e2f4ec39f94c..a534b692f177 100644
--- a/nixos/modules/services/audio/mopidy.nix
+++ b/nixos/modules/services/audio/mopidy.nix
@@ -70,25 +70,25 @@ in {
 
   config = mkIf cfg.enable {
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' - mopidy mopidy - -"
+    ];
+
     systemd.services.mopidy = {
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" "sound.target" ];
       description = "mopidy music player daemon";
-      preStart = "mkdir -p ${cfg.dataDir} && chown -R mopidy:mopidy  ${cfg.dataDir}";
       serviceConfig = {
         ExecStart = "${mopidyEnv}/bin/mopidy --config ${concatStringsSep ":" ([mopidyConf] ++ cfg.extraConfigFiles)}";
         User = "mopidy";
-        PermissionsStartOnly = true;
       };
     };
 
     systemd.services.mopidy-scan = {
       description = "mopidy local files scanner";
-      preStart = "mkdir -p ${cfg.dataDir} && chown -R mopidy:mopidy  ${cfg.dataDir}";
       serviceConfig = {
         ExecStart = "${mopidyEnv}/bin/mopidy --config ${concatStringsSep ":" ([mopidyConf] ++ cfg.extraConfigFiles)} local scan";
         User = "mopidy";
-        PermissionsStartOnly = true;
         Type = "oneshot";
       };
     };
@@ -98,7 +98,7 @@ in {
       group = "mopidy";
       extraGroups = [ "audio" ];
       description = "Mopidy daemon user";
-      home = "${cfg.dataDir}";
+      home = cfg.dataDir;
     };
 
     users.groups.mopidy.gid = gid;
diff --git a/nixos/modules/services/audio/roon-server.nix b/nixos/modules/services/audio/roon-server.nix
new file mode 100644
index 000000000000..9562ad1b164e
--- /dev/null
+++ b/nixos/modules/services/audio/roon-server.nix
@@ -0,0 +1,49 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  name = "roon-server";
+  cfg = config.services.roon-server;
+in {
+  options = {
+    services.roon-server = {
+      enable = mkEnableOption "Roon Server";
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Open ports in the firewall for the server.
+
+          UDP: 9003
+          TCP: 9100 - 9200
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.roon-server = {
+      after = [ "network.target" ];
+      description = "Roon Server";
+      wantedBy = [ "multi-user.target" ];
+
+      environment.ROON_DATAROOT = "/var/lib/${name}";
+
+      serviceConfig = {
+        ExecStart = "${pkgs.roon-server}/opt/start.sh";
+        LimitNOFILE = 8192;
+        DynamicUser = true;
+        SupplementaryGroups = "audio";
+        StateDirectory = name;
+      };
+    };
+    
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPortRanges = [
+        { from = 9100; to = 9200; }
+      ];
+      allowedUDPPorts = [ 9003 ];
+    };
+  };
+}
diff --git a/nixos/modules/services/audio/slimserver.nix b/nixos/modules/services/audio/slimserver.nix
index 640403d2c97d..8f94a2b49404 100644
--- a/nixos/modules/services/audio/slimserver.nix
+++ b/nixos/modules/services/audio/slimserver.nix
@@ -42,15 +42,17 @@ in {
 
   config = mkIf cfg.enable {
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' - slimserver slimserver - -"
+    ];
+
     systemd.services.slimserver = {
       after = [ "network.target" ];
       description = "Slim Server for Logitech Squeezebox Players";
       wantedBy = [ "multi-user.target" ];
 
-      preStart = "mkdir -p ${cfg.dataDir} && chown -R slimserver:slimserver ${cfg.dataDir}";
       serviceConfig = {
         User = "slimserver";
-        PermissionsStartOnly = true;
         # Issue 40589: Disable broken image/video support (audio still works!)
         ExecStart = "${cfg.package}/slimserver.pl --logdir ${cfg.dataDir}/logs --prefsdir ${cfg.dataDir}/prefs --cachedir ${cfg.dataDir}/cache --noimage --novideo";
       };
diff --git a/nixos/modules/services/audio/snapserver.nix b/nixos/modules/services/audio/snapserver.nix
index f709dd7fe16b..b0b9264e8166 100644
--- a/nixos/modules/services/audio/snapserver.nix
+++ b/nixos/modules/services/audio/snapserver.nix
@@ -4,7 +4,6 @@ with lib;
 
 let
 
-  package = "snapcast";
   name = "snapserver";
 
   cfg = config.services.snapserver;
diff --git a/nixos/modules/services/audio/spotifyd.nix b/nixos/modules/services/audio/spotifyd.nix
new file mode 100644
index 000000000000..e3556b2559c2
--- /dev/null
+++ b/nixos/modules/services/audio/spotifyd.nix
@@ -0,0 +1,42 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.spotifyd;
+  spotifydConf = pkgs.writeText "spotifyd.conf" cfg.config;
+in
+{
+  options = {
+    services.spotifyd = {
+      enable = mkEnableOption "spotifyd, a Spotify playing daemon";
+
+      config = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Configuration for Spotifyd. For syntax and directives, see
+          https://github.com/Spotifyd/spotifyd#Configuration.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.spotifyd = {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" "sound.target" ];
+      description = "spotifyd, a Spotify playing daemon";
+      serviceConfig = {
+        ExecStart = "${pkgs.spotifyd}/bin/spotifyd --no-daemon --cache_path /var/cache/spotifyd --config ${spotifydConf}";
+        Restart = "always";
+        RestartSec = 12;
+        DynamicUser = true;
+        CacheDirectory = "spotifyd";
+        SupplementaryGroups = ["audio"];
+      };
+    };
+  };
+
+  meta.maintainers = [ maintainers.anderslundstedt ];
+}
diff --git a/nixos/modules/services/backup/duplicati.nix b/nixos/modules/services/backup/duplicati.nix
index 80287f30b813..0ff720c5897d 100644
--- a/nixos/modules/services/backup/duplicati.nix
+++ b/nixos/modules/services/backup/duplicati.nix
@@ -19,13 +19,22 @@ in
       };
 
       interface = mkOption {
-        default = "lo";
+        default = "127.0.0.1";
         type = types.str;
         description = ''
           Listening interface for the web UI
           Set it to "any" to listen on all available interfaces
         '';
       };
+
+      user = mkOption {
+        default = "duplicati";
+        type = types.str;
+        description = ''
+          Duplicati runs as it's own user. It will only be able to backup world-readable files.
+          Run as root with special care.
+        '';
+      };
     };
   };
 
@@ -37,14 +46,15 @@ in
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       serviceConfig = {
-        User = "duplicati";
+        User = cfg.user;
         Group = "duplicati";
+        StateDirectory = "duplicati";
         ExecStart = "${pkgs.duplicati}/bin/duplicati-server --webservice-interface=${cfg.interface} --webservice-port=${toString cfg.port} --server-datafolder=/var/lib/duplicati";
         Restart = "on-failure";
       };
     };
 
-    users.users.duplicati = {
+    users.users.duplicati = lib.optionalAttrs (cfg.user == "duplicati") {
       uid = config.ids.uids.duplicati;
       home = "/var/lib/duplicati";
       createHome = true;
diff --git a/nixos/modules/services/backup/tsm.nix b/nixos/modules/services/backup/tsm.nix
new file mode 100644
index 000000000000..3b2bb37491b5
--- /dev/null
+++ b/nixos/modules/services/backup/tsm.nix
@@ -0,0 +1,106 @@
+{ config, lib, ... }:
+
+let
+
+  inherit (lib.attrsets) hasAttr;
+  inherit (lib.modules) mkDefault mkIf;
+  inherit (lib.options) mkEnableOption mkOption;
+  inherit (lib.types) nullOr strMatching;
+
+  options.services.tsmBackup = {
+    enable = mkEnableOption ''
+      automatic backups with the
+      IBM Spectrum Protect (Tivoli Storage Manager, TSM) client.
+      This also enables
+      <option>programs.tsmClient.enable</option>
+    '';
+    command = mkOption {
+      type = strMatching ".+";
+      default = "backup";
+      example = "incr";
+      description = ''
+        The actual command passed to the
+        <literal>dsmc</literal> executable to start the backup.
+      '';
+    };
+    servername = mkOption {
+      type = strMatching ".+";
+      example = "mainTsmServer";
+      description = ''
+        Create a systemd system service
+        <literal>tsm-backup.service</literal> that starts
+        a backup based on the given servername's stanza.
+        Note that this server's
+        <option>passwdDir</option> will default to
+        <filename>/var/lib/tsm-backup/password</filename>
+        (but may be overridden);
+        also, the service will use
+        <filename>/var/lib/tsm-backup</filename> as
+        <literal>HOME</literal> when calling
+        <literal>dsmc</literal>.
+      '';
+    };
+    autoTime = mkOption {
+      type = nullOr (strMatching ".+");
+      default = null;
+      example = "12:00";
+      description = ''
+        The backup service will be invoked
+        automatically at the given date/time,
+        which must be in the format described in
+        <citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+        The default <literal>null</literal>
+        disables automatic backups.
+      '';
+    };
+  };
+
+  cfg = config.services.tsmBackup;
+  cfgPrg = config.programs.tsmClient;
+
+  assertions = [
+    {
+      assertion = hasAttr cfg.servername cfgPrg.servers;
+      message = "TSM service servername not found in list of servers";
+    }
+    {
+      assertion = cfgPrg.servers.${cfg.servername}.genPasswd;
+      message = "TSM service requires automatic password generation";
+    }
+  ];
+
+in
+
+{
+
+  inherit options;
+
+  config = mkIf cfg.enable {
+    inherit assertions;
+    programs.tsmClient.enable = true;
+    programs.tsmClient.servers."${cfg.servername}".passwdDir =
+      mkDefault "/var/lib/tsm-backup/password";
+    systemd.services.tsm-backup = {
+      description = "IBM Spectrum Protect (Tivoli Storage Manager) Backup";
+      # DSM_LOG needs a trailing slash to have it treated as a directory.
+      # `/var/log` would be littered with TSM log files otherwise.
+      environment.DSM_LOG = "/var/log/tsm-backup/";
+      # TSM needs a HOME dir to store certificates.
+      environment.HOME = "/var/lib/tsm-backup";
+      # for exit status description see
+      # https://www.ibm.com/support/knowledgecenter/en/SSEQVQ_8.1.8/client/c_sched_rtncode.html
+      serviceConfig.SuccessExitStatus = "4 8";
+      # The `-se` option must come after the command.
+      # The `-optfile` option suppresses a `dsm.opt`-not-found warning.
+      serviceConfig.ExecStart =
+        "${cfgPrg.wrappedPackage}/bin/dsmc ${cfg.command} -se='${cfg.servername}' -optfile=/dev/null";
+      serviceConfig.LogsDirectory = "tsm-backup";
+      serviceConfig.StateDirectory = "tsm-backup";
+      serviceConfig.StateDirectoryMode = "0750";
+      startAt = mkIf (cfg.autoTime!=null) cfg.autoTime;
+    };
+  };
+
+  meta.maintainers = [ lib.maintainers.yarny ];
+
+}
diff --git a/nixos/modules/services/backup/zfs-replication.nix b/nixos/modules/services/backup/zfs-replication.nix
new file mode 100644
index 000000000000..785cedb98694
--- /dev/null
+++ b/nixos/modules/services/backup/zfs-replication.nix
@@ -0,0 +1,90 @@
+{ lib, pkgs, config, ... }:
+
+with lib;
+
+let
+  cfg = config.services.zfs.autoReplication;
+  recursive = optionalString cfg.recursive " --recursive";
+  followDelete = optionalString cfg.followDelete " --follow-delete";
+in {
+  options = {
+    services.zfs.autoReplication = {
+      enable = mkEnableOption "ZFS snapshot replication.";
+
+      followDelete = mkOption {
+        description = "Remove remote snapshots that don't have a local correspondant.";
+        default = true;
+        type = types.bool;
+      };
+
+      host = mkOption {
+        description = "Remote host where snapshots should be sent.";
+        example = "example.com";
+        type = types.str;
+      };
+
+      identityFilePath = mkOption {
+        description = "Path to SSH key used to login to host.";
+        example = "/home/username/.ssh/id_rsa";
+        type = types.path;
+      };
+
+      localFilesystem = mkOption {
+        description = "Local ZFS fileystem from which snapshots should be sent.  Defaults to the attribute name.";
+        example = "pool/file/path";
+        type = types.str;
+      };
+
+      remoteFilesystem = mkOption {
+        description = "Remote ZFS filesystem where snapshots should be sent.";
+        example = "pool/file/path";
+        type = types.str;
+      };
+
+      recursive = mkOption {
+        description = "Recursively discover snapshots to send.";
+        default = true;
+        type = types.bool;
+      };
+
+      username = mkOption {
+        description = "Username used by SSH to login to remote host.";
+        example = "username";
+        type = types.str;
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [
+      pkgs.lz4
+    ];
+
+    systemd.services."zfs-replication" = {
+      after = [
+        "zfs-snapshot-daily.service"
+        "zfs-snapshot-frequent.service"
+        "zfs-snapshot-hourly.service"
+        "zfs-snapshot-monthly.service"
+        "zfs-snapshot-weekly.service"
+      ];
+      description = "ZFS Snapshot Replication";
+      documentation = [
+        "https://github.com/alunduil/zfs-replicate"
+      ];
+      restartIfChanged = false;
+      serviceConfig.ExecStart = "${pkgs.zfs-replicate}/bin/zfs-replicate${recursive} -l ${escapeShellArg cfg.username} -i ${escapeShellArg cfg.identityFilePath}${followDelete} ${escapeShellArg cfg.host} ${escapeShellArg cfg.remoteFilesystem} ${escapeShellArg cfg.localFilesystem}";
+      wantedBy = [
+        "zfs-snapshot-daily.service"
+        "zfs-snapshot-frequent.service"
+        "zfs-snapshot-hourly.service"
+        "zfs-snapshot-monthly.service"
+        "zfs-snapshot-weekly.service"
+      ];
+    };
+  };
+
+  meta = {
+    maintainers = with lib.maintainers; [ alunduil ];
+  };
+}
diff --git a/nixos/modules/services/cluster/kubernetes/default.nix b/nixos/modules/services/cluster/kubernetes/default.nix
index 5e46bfc4240f..143b41f57f6a 100644
--- a/nixos/modules/services/cluster/kubernetes/default.nix
+++ b/nixos/modules/services/cluster/kubernetes/default.nix
@@ -72,13 +72,6 @@ let
       default = null;
     };
   };
-
-  kubeConfigDefaults = {
-    server = mkDefault cfg.kubeconfig.server;
-    caFile = mkDefault cfg.kubeconfig.caFile;
-    certFile = mkDefault cfg.kubeconfig.certFile;
-    keyFile = mkDefault cfg.kubeconfig.keyFile;
-  };
 in {
 
   ###### interface
diff --git a/nixos/modules/services/cluster/kubernetes/kubelet.nix b/nixos/modules/services/cluster/kubernetes/kubelet.nix
index ccc8a16e788a..4c5df96bcc6a 100644
--- a/nixos/modules/services/cluster/kubernetes/kubelet.nix
+++ b/nixos/modules/services/cluster/kubernetes/kubelet.nix
@@ -28,13 +28,6 @@ let
 
   kubeconfig = top.lib.mkKubeConfig "kubelet" cfg.kubeconfig;
 
-  manifests = pkgs.buildEnv {
-    name = "kubernetes-manifests";
-    paths = mapAttrsToList (name: manifest:
-      pkgs.writeTextDir "${name}.json" (builtins.toJSON manifest)
-    ) cfg.manifests;
-  };
-
   manifestPath = "kubernetes/manifests";
 
   taintOptions = with lib.types; { name, ... }: {
diff --git a/nixos/modules/services/cluster/kubernetes/pki.nix b/nixos/modules/services/cluster/kubernetes/pki.nix
index e68660e8bdd4..47384ae50a07 100644
--- a/nixos/modules/services/cluster/kubernetes/pki.nix
+++ b/nixos/modules/services/cluster/kubernetes/pki.nix
@@ -118,7 +118,6 @@ in
     cfsslCertPathPrefix = "${config.services.cfssl.dataDir}/cfssl";
     cfsslCert = "${cfsslCertPathPrefix}.pem";
     cfsslKey = "${cfsslCertPathPrefix}-key.pem";
-    cfsslPort = toString config.services.cfssl.port;
 
     certmgrPaths = [
       top.caFile
diff --git a/nixos/modules/services/computing/boinc/client.nix b/nixos/modules/services/computing/boinc/client.nix
index 8abe3c5b8c9b..7022751b3f01 100644
--- a/nixos/modules/services/computing/boinc/client.nix
+++ b/nixos/modules/services/computing/boinc/client.nix
@@ -105,19 +105,18 @@ in
         isSystemUser = true;
       };
 
+      systemd.tmpfiles.rules = [
+        "d '${cfg.dataDir}' - boinc - - -"
+      ];
+
       systemd.services.boinc = {
         description = "BOINC Client";
         after = ["network.target" "local-fs.target"];
         wantedBy = ["multi-user.target"];
-        preStart = ''
-          mkdir -p ${cfg.dataDir}
-          chown boinc ${cfg.dataDir}
-        '';
         script = ''
           ${fhsEnvExecutable} --dir ${cfg.dataDir} --redirectio ${allowRemoteGuiRpcFlag}
         '';
         serviceConfig = {
-          PermissionsStartOnly = true; # preStart must be run as root
           User = "boinc";
           Nice = 10;
         };
diff --git a/nixos/modules/services/databases/cassandra.nix b/nixos/modules/services/databases/cassandra.nix
index 688938868020..a9da3a3c5620 100644
--- a/nixos/modules/services/databases/cassandra.nix
+++ b/nixos/modules/services/databases/cassandra.nix
@@ -8,18 +8,21 @@ let
   cassandraConfig = flip recursiveUpdate cfg.extraConfig
     ({ commitlog_sync = "batch";
        commitlog_sync_batch_window_in_ms = 2;
+       start_native_transport = cfg.allowClients;
+       cluster_name = cfg.clusterName;
        partitioner = "org.apache.cassandra.dht.Murmur3Partitioner";
        endpoint_snitch = "SimpleSnitch";
-       seed_provider =
-         [{ class_name = "org.apache.cassandra.locator.SimpleSeedProvider";
-            parameters = [ { seeds = "127.0.0.1"; } ];
-         }];
        data_file_directories = [ "${cfg.homeDir}/data" ];
        commitlog_directory = "${cfg.homeDir}/commitlog";
        saved_caches_directory = "${cfg.homeDir}/saved_caches";
-     } // (if builtins.compareVersions cfg.package.version "3" >= 0
-             then { hints_directory = "${cfg.homeDir}/hints"; }
-             else {})
+     } // (lib.optionalAttrs (cfg.seedAddresses != []) {
+       seed_provider = [{
+         class_name = "org.apache.cassandra.locator.SimpleSeedProvider";
+         parameters = [ { seeds = concatStringsSep "," cfg.seedAddresses; } ];
+       }];
+     }) // (lib.optionalAttrs (lib.versionAtLeast cfg.package.version "3") {
+       hints_directory = "${cfg.homeDir}/hints";
+     })
     );
   cassandraConfigWithAddresses = cassandraConfig //
     ( if cfg.listenAddress == null
@@ -39,15 +42,42 @@ let
         mkdir -p "$out"
 
         echo "$cassandraYaml" > "$out/cassandra.yaml"
-        ln -s "$cassandraEnvPkg" "$out/cassandra-env.sh"
         ln -s "$cassandraLogbackConfig" "$out/logback.xml"
+
+        cp "$cassandraEnvPkg" "$out/cassandra-env.sh"
+
+        # Delete default JMX Port, otherwise we can't set it using env variable
+        sed -i '/JMX_PORT="7199"/d' "$out/cassandra-env.sh"
+
+        # Delete default password file
+        sed -i '/-Dcom.sun.management.jmxremote.password.file=\/etc\/cassandra\/jmxremote.password/d' "$out/cassandra-env.sh"
       '';
     };
+  defaultJmxRolesFile = builtins.foldl'
+     (left: right: left + right) ""
+     (map (role: "${role.username} ${role.password}") cfg.jmxRoles);
+  fullJvmOptions = cfg.jvmOpts
+    ++ lib.optionals (cfg.jmxRoles != []) [
+      "-Dcom.sun.management.jmxremote.authenticate=true"
+      "-Dcom.sun.management.jmxremote.password.file=${cfg.jmxRolesFile}"
+    ]
+    ++ lib.optionals cfg.remoteJmx [
+      "-Djava.rmi.server.hostname=${cfg.rpcAddress}"
+    ];
 in {
   options.services.cassandra = {
     enable = mkEnableOption ''
       Apache Cassandra – Scalable and highly available database.
     '';
+    clusterName = mkOption {
+      type = types.str;
+      default = "Test Cluster";
+      description = ''
+        The name of the cluster.
+        This setting prevents nodes in one logical cluster from joining
+        another. All nodes in a cluster must have the same value.
+      '';
+    };
     user = mkOption {
       type = types.str;
       default = defaultUser;
@@ -162,6 +192,28 @@ in {
         XML logback configuration for cassandra
       '';
     };
+    seedAddresses = mkOption {
+      type = types.listOf types.str;
+      default = [ "127.0.0.1" ];
+      description = ''
+        The addresses of hosts designated as contact points in the cluster. A
+        joining node contacts one of the nodes in the seeds list to learn the
+        topology of the ring.
+        Set to 127.0.0.1 for a single node cluster.
+      '';
+    };
+    allowClients = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Enables or disables the native transport server (CQL binary protocol).
+        This server uses the same address as the <literal>rpcAddress</literal>,
+        but the port it uses is not <literal>rpc_port</literal> but
+        <literal>native_transport_port</literal>. See the official Cassandra
+        docs for more information on these variables and set them using
+        <literal>extraConfig</literal>.
+      '';
+    };
     extraConfig = mkOption {
       type = types.attrs;
       default = {};
@@ -178,11 +230,11 @@ in {
       example = literalExample "null";
       description = ''
           Set the interval how often full repairs are run, i.e.
-          `nodetool repair --full` is executed. See
+          <literal>nodetool repair --full</literal> is executed. See
           https://cassandra.apache.org/doc/latest/operating/repair.html
           for more information.
 
-          Set to `null` to disable full repairs.
+          Set to <literal>null</literal> to disable full repairs.
         '';
     };
     fullRepairOptions = mkOption {
@@ -199,11 +251,11 @@ in {
       example = literalExample "null";
       description = ''
           Set the interval how often incremental repairs are run, i.e.
-          `nodetool repair` is executed. See
+          <literal>nodetool repair</literal> is executed. See
           https://cassandra.apache.org/doc/latest/operating/repair.html
           for more information.
 
-          Set to `null` to disable incremental repairs.
+          Set to <literal>null</literal> to disable incremental repairs.
         '';
     };
     incrementalRepairOptions = mkOption {
@@ -214,20 +266,135 @@ in {
           Options passed through to the incremental repair command.
         '';
     };
+    maxHeapSize = mkOption {
+      type = types.nullOr types.string;
+      default = null;
+      example = "4G";
+      description = ''
+        Must be left blank or set together with heapNewSize.
+        If left blank a sensible value for the available amount of RAM and CPU
+        cores is calculated.
+
+        Override to set the amount of memory to allocate to the JVM at
+        start-up. For production use you may wish to adjust this for your
+        environment. MAX_HEAP_SIZE is the total amount of memory dedicated
+        to the Java heap. HEAP_NEWSIZE refers to the size of the young
+        generation.
+
+        The main trade-off for the young generation is that the larger it
+        is, the longer GC pause times will be. The shorter it is, the more
+        expensive GC will be (usually).
+      '';
+    };
+    heapNewSize = mkOption {
+      type = types.nullOr types.string;
+      default = null;
+      example = "800M";
+      description = ''
+        Must be left blank or set together with heapNewSize.
+        If left blank a sensible value for the available amount of RAM and CPU
+        cores is calculated.
+
+        Override to set the amount of memory to allocate to the JVM at
+        start-up. For production use you may wish to adjust this for your
+        environment. HEAP_NEWSIZE refers to the size of the young
+        generation.
+
+        The main trade-off for the young generation is that the larger it
+        is, the longer GC pause times will be. The shorter it is, the more
+        expensive GC will be (usually).
+
+        The example HEAP_NEWSIZE assumes a modern 8-core+ machine for decent pause
+        times. If in doubt, and if you do not particularly want to tweak, go with
+        100 MB per physical CPU core.
+      '';
+    };
+    mallocArenaMax = mkOption {
+      type = types.nullOr types.int;
+      default = null;
+      example = 4;
+      description = ''
+        Set this to control the amount of arenas per-thread in glibc.
+      '';
+    };
+    remoteJmx = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Cassandra ships with JMX accessible *only* from localhost.
+        To enable remote JMX connections set to true.
+
+        Be sure to also enable authentication and/or TLS.
+        See: https://wiki.apache.org/cassandra/JmxSecurity
+      '';
+    };
+    jmxPort = mkOption {
+      type = types.int;
+      default = 7199;
+      description = ''
+        Specifies the default port over which Cassandra will be available for
+        JMX connections.
+        For security reasons, you should not expose this port to the internet.
+        Firewall it if needed.
+      '';
+    };
+    jmxRoles = mkOption {
+      default = [];
+      description = ''
+        Roles that are allowed to access the JMX (e.g. nodetool)
+        BEWARE: The passwords will be stored world readable in the nix-store.
+                It's recommended to use your own protected file using
+                <literal>jmxRolesFile</literal>
+
+        Doesn't work in versions older than 3.11 because they don't like that
+        it's world readable.
+      '';
+      type = types.listOf (types.submodule {
+        options = {
+          username = mkOption {
+            type = types.string;
+            description = "Username for JMX";
+          };
+          password = mkOption {
+            type = types.string;
+            description = "Password for JMX";
+          };
+        };
+      });
+    };
+    jmxRolesFile = mkOption {
+      type = types.nullOr types.path;
+      default = if (lib.versionAtLeast cfg.package.version "3.11")
+                then pkgs.writeText "jmx-roles-file" defaultJmxRolesFile
+                else null;
+      example = "/var/lib/cassandra/jmx.password";
+      description = ''
+        Specify your own jmx roles file.
+
+        Make sure the permissions forbid "others" from reading the file if
+        you're using Cassandra below version 3.11.
+      '';
+    };
   };
 
   config = mkIf cfg.enable {
     assertions =
-      [ { assertion =
-          (cfg.listenAddress == null || cfg.listenInterface == null)
-          && !(cfg.listenAddress == null && cfg.listenInterface == null);
+      [ { assertion = (cfg.listenAddress == null) != (cfg.listenInterface == null);
           message = "You have to set either listenAddress or listenInterface";
         }
-        { assertion =
-          (cfg.rpcAddress == null || cfg.rpcInterface == null)
-          && !(cfg.rpcAddress == null && cfg.rpcInterface == null);
+        { assertion = (cfg.rpcAddress == null) != (cfg.rpcInterface == null);
           message = "You have to set either rpcAddress or rpcInterface";
         }
+        { assertion = (cfg.maxHeapSize == null) == (cfg.heapNewSize == null);
+          message = "If you set either of maxHeapSize or heapNewSize you have to set both";
+        }
+        { assertion = cfg.remoteJmx -> cfg.jmxRolesFile != null;
+          message = ''
+            If you want JMX available remotely you need to set a password using
+            <literal>jmxRoles</literal> or <literal>jmxRolesFile</literal> if
+            using Cassandra older than v3.11.
+          '';
+        }
       ];
     users = mkIf (cfg.user == defaultUser) {
       extraUsers."${defaultUser}" =
@@ -245,7 +412,12 @@ in {
         after = [ "network.target" ];
         environment =
           { CASSANDRA_CONF = "${cassandraEtc}";
-            JVM_OPTS = builtins.concatStringsSep " " cfg.jvmOpts;
+            JVM_OPTS = builtins.concatStringsSep " " fullJvmOptions;
+            MAX_HEAP_SIZE = toString cfg.maxHeapSize;
+            HEAP_NEWSIZE = toString cfg.heapNewSize;
+            MALLOC_ARENA_MAX = toString cfg.mallocArenaMax;
+            LOCAL_JMX = if cfg.remoteJmx then "no" else "yes";
+            JMX_PORT = toString cfg.jmxPort;
           };
         wantedBy = [ "multi-user.target" ];
         serviceConfig =
diff --git a/nixos/modules/services/databases/firebird.nix b/nixos/modules/services/databases/firebird.nix
index cc81b440450b..042c9841df54 100644
--- a/nixos/modules/services/databases/firebird.nix
+++ b/nixos/modules/services/databases/firebird.nix
@@ -95,6 +95,11 @@ in
 
     environment.systemPackages = [cfg.package];
 
+    systemd.tmpfiles.rules = [
+      "d '${dataDir}' 0700 ${cfg.user} - - -"
+      "d '${systemDir}' 0700 ${cfg.user} - - -"
+    ];
+
     systemd.services.firebird =
       { description = "Firebird Super-Server";
 
@@ -104,21 +109,16 @@ in
         # is a better way
         preStart =
           ''
-            mkdir -m 0700 -p \
-              "${dataDir}" \
-              "${systemDir}" \
-              /var/log/firebird
-
             if ! test -e "${systemDir}/security2.fdb"; then
                 cp ${firebird}/security2.fdb "${systemDir}"
             fi
 
-            chown -R ${cfg.user} "${dataDir}" "${systemDir}" /var/log/firebird
             chmod -R 700         "${dataDir}" "${systemDir}" /var/log/firebird
           '';
 
-        serviceConfig.PermissionsStartOnly = true; # preStart must be run as root
         serviceConfig.User = cfg.user;
+        serviceConfig.LogsDirectory = "firebird";
+        serviceConfig.LogsDirectoryMode = "0700";
         serviceConfig.ExecStart = ''${firebird}/bin/fbserver -d'';
 
         # TODO think about shutdown
diff --git a/nixos/modules/services/databases/foundationdb.nix b/nixos/modules/services/databases/foundationdb.nix
index 490c5e9d005a..3746b875c7f2 100644
--- a/nixos/modules/services/databases/foundationdb.nix
+++ b/nixos/modules/services/databases/foundationdb.nix
@@ -359,6 +359,13 @@ in
         }
       ];
 
+    systemd.tmpfiles.rules = [
+      "d /etc/foundationdb 0755 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.dataDir}' 0770 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.logDir}' 0770 ${cfg.user} ${cfg.group} - -"
+      "F '${cfg.pidfile}' - ${cfg.user} ${cfg.group} - -"
+    ];
+
     systemd.services.foundationdb = {
       description             = "FoundationDB Service";
 
@@ -396,25 +403,12 @@ in
       path = [ pkg pkgs.coreutils ];
 
       preStart = ''
-        rm -f ${cfg.pidfile}   && \
-          touch ${cfg.pidfile} && \
-          chown -R ${cfg.user}:${cfg.group} ${cfg.pidfile}
-
-        for x in "${cfg.logDir}" "${cfg.dataDir}"; do
-          [ ! -d "$x" ] && mkdir -m 0770 -vp "$x";
-          chown -R ${cfg.user}:${cfg.group} "$x";
-        done
-
-        [ ! -d /etc/foundationdb ] && \
-          mkdir -m 0775 -vp /etc/foundationdb && \
-          chown -R ${cfg.user}:${cfg.group} "/etc/foundationdb"
-
         if [ ! -f /etc/foundationdb/fdb.cluster ]; then
             cf=/etc/foundationdb/fdb.cluster
             desc=$(tr -dc A-Za-z0-9 </dev/urandom 2>/dev/null | head -c8)
             rand=$(tr -dc A-Za-z0-9 </dev/urandom 2>/dev/null | head -c8)
             echo ''${desc}:''${rand}@${initialIpAddr}:${builtins.toString cfg.listenPortStart} > $cf
-            chmod 0664 $cf && chown -R ${cfg.user}:${cfg.group} $cf
+            chmod 0664 $cf
             touch "${cfg.dataDir}/.first_startup"
         fi
       '';
diff --git a/nixos/modules/services/databases/foundationdb.xml b/nixos/modules/services/databases/foundationdb.xml
index bf4b644c9b86..b0b1ebeab45f 100644
--- a/nixos/modules/services/databases/foundationdb.xml
+++ b/nixos/modules/services/databases/foundationdb.xml
@@ -47,14 +47,14 @@ services.foundationdb.package = pkgs.foundationdb52; # FoundationDB 5.2.x
    After running <command>nixos-rebuild</command>, you can verify whether
    FoundationDB is running by executing <command>fdbcli</command> (which is
    added to <option>environment.systemPackages</option>):
-<programlisting>
-$ sudo -u foundationdb fdbcli
+<screen>
+<prompt>$ </prompt>sudo -u foundationdb fdbcli
 Using cluster file `/etc/foundationdb/fdb.cluster'.
 
 The database is available.
 
 Welcome to the fdbcli. For help, type `help'.
-fdb> status
+<prompt>fdb> </prompt>status
 
 Using cluster file `/etc/foundationdb/fdb.cluster'.
 
@@ -72,8 +72,8 @@ Cluster:
 
 ...
 
-fdb>
-</programlisting>
+<prompt>fdb></prompt>
+</screen>
   </para>
 
   <para>
@@ -82,8 +82,8 @@ fdb>
    cluster status, as a quick example. (This example uses
    <command>nix-shell</command> shebang support to automatically supply the
    necessary Python modules).
-<programlisting>
-a@link> cat fdb-status.py
+<screen>
+<prompt>a@link> </prompt>cat fdb-status.py
 #! /usr/bin/env nix-shell
 #! nix-shell -i python -p python pythonPackages.foundationdb52
 
@@ -103,11 +103,11 @@ def main():
 
 if __name__ == "__main__":
     main()
-a@link> chmod +x fdb-status.py
-a@link> ./fdb-status.py
+<prompt>a@link> </prompt>chmod +x fdb-status.py
+<prompt>a@link> </prompt>./fdb-status.py
 FoundationDB available: True
-a@link>
-</programlisting>
+<prompt>a@link></prompt>
+</screen>
   </para>
 
   <para>
@@ -266,10 +266,10 @@ services.foundationdb.dataDir = "/data/fdb";
    <emphasis>every</emphasis> node a coordinator automatically:
   </para>
 
-<programlisting>
-fdbcli> configure double ssd
-fdbcli> coordinators auto
-</programlisting>
+<screen>
+<prompt>fdbcli> </prompt>configure double ssd
+<prompt>fdbcli> </prompt>coordinators auto
+</screen>
 
   <para>
    This will transparently update all the servers within seconds, and
@@ -386,10 +386,10 @@ services.foundationdb.extraReadWritePaths = [ "/opt/fdb-backups" ];
    You can now perform a backup:
   </para>
 
-<programlisting>
-$ sudo -u foundationdb fdbbackup start  -t default -d file:///opt/fdb-backups
-$ sudo -u foundationdb fdbbackup status -t default
-</programlisting>
+<screen>
+<prompt>$ </prompt>sudo -u foundationdb fdbbackup start  -t default -d file:///opt/fdb-backups
+<prompt>$ </prompt>sudo -u foundationdb fdbbackup status -t default
+</screen>
  </section>
  <section xml:id="module-services-foundationdb-limitations">
   <title>Known limitations</title>
diff --git a/nixos/modules/services/databases/hbase.nix b/nixos/modules/services/databases/hbase.nix
index 52f2d95b4e00..589c8cf5ec80 100644
--- a/nixos/modules/services/databases/hbase.nix
+++ b/nixos/modules/services/databases/hbase.nix
@@ -94,6 +94,11 @@ in {
 
   config = mkIf config.services.hbase.enable {
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' - ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.logDir}' - ${cfg.user} ${cfg.group} - -"
+    ];
+
     systemd.services.hbase = {
       description = "HBase Server";
       wantedBy = [ "multi-user.target" ];
@@ -103,19 +108,7 @@ in {
         HBASE_LOG_DIR = cfg.logDir;
       };
 
-      preStart =
-        ''
-        mkdir -p ${cfg.dataDir};
-        mkdir -p ${cfg.logDir};
-
-        if [ "$(id -u)" = 0 ]; then
-          chown ${cfg.user}:${cfg.group} ${cfg.dataDir}
-          chown ${cfg.user}:${cfg.group} ${cfg.logDir}
-        fi
-        '';
-
       serviceConfig = {
-        PermissionsStartOnly = true;
         User = cfg.user;
         Group = cfg.group;
         ExecStart = "${cfg.package}/bin/hbase --config ${configDir} master start";
diff --git a/nixos/modules/services/databases/mysql.nix b/nixos/modules/services/databases/mysql.nix
index 66d55b650a45..df74cfc9a26b 100644
--- a/nixos/modules/services/databases/mysql.nix
+++ b/nixos/modules/services/databases/mysql.nix
@@ -204,12 +204,6 @@ in
         '';
       };
 
-      # FIXME: remove this option; it's a really bad idea.
-      rootPassword = mkOption {
-        default = null;
-        description = "Path to a file containing the root password, modified on the first startup. Not specifying a root password will leave the root password empty.";
-      };
-
       replication = {
         role = mkOption {
           type = types.enum [ "master" "slave" "none" ];
@@ -323,114 +317,107 @@ in
           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 =
-          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
-                    ''}
+          ExecStartPost =
+            let
+              setupScript = pkgs.writeScript "mysql-setup" ''
+                #!${pkgs.runtimeShell} -e
+
+                ${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
+                ''}
+
+                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 != 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}
+
+                    ${optionalString (cfg.replication.role == "master")
+                      ''
+                        # Set up the replication master
+
+                        ( echo "use mysql;"
+                          echo "CREATE USER '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}' IDENTIFIED WITH mysql_native_password;"
+                          echo "SET PASSWORD FOR '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}' = PASSWORD('${cfg.replication.masterPassword}');"
+                          echo "GRANT REPLICATION SLAVE ON *.* TO '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}';"
+                        ) | ${mysql}/bin/mysql -u root -N
+                      ''}
+
+                    ${optionalString (cfg.replication.role == "slave")
+                      ''
+                        # Set up the replication slave
+
+                        ( echo "stop slave;"
+                          echo "change master to master_host='${cfg.replication.masterHost}', master_user='${cfg.replication.masterUser}', master_password='${cfg.replication.masterPassword}';"
+                          echo "start slave;"
+                        ) | ${mysql}/bin/mysql -u root -N
+                      ''}
+
+                    ${optionalString (cfg.initialScript != null)
+                      ''
+                        # Execute initial script
+                        # 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
+                      ''}
+
+                    rm /tmp/mysql_init
+                fi
+
+                ${optionalString (cfg.ensureDatabases != []) ''
+                  (
+                  ${concatMapStrings (database: ''
+                    echo "CREATE DATABASE IF NOT EXISTS \`${database}\`;"
+                  '') cfg.ensureDatabases}
                   ) | ${mysql}/bin/mysql -u root -N
-              fi
-            '') cfg.initialDatabases;
-          in
-
-          lib.optionalString (!hasNotify) cmdWatchForMysqlSocket + ''
-            if [ -f /tmp/mysql_init ]
-            then
-                ${cmdInitialDatabases}
-                ${optionalString (cfg.replication.role == "master")
-                  ''
-                    # Set up the replication master
-
-                    ( echo "use mysql;"
-                      echo "CREATE USER '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}' IDENTIFIED WITH mysql_native_password;"
-                      echo "SET PASSWORD FOR '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}' = PASSWORD('${cfg.replication.masterPassword}');"
-                      echo "GRANT REPLICATION SLAVE ON *.* TO '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}';"
-                    ) | ${mysql}/bin/mysql -u root -N
-                  ''}
+                ''}
 
-                ${optionalString (cfg.replication.role == "slave")
+                ${concatMapStrings (user:
                   ''
-                    # Set up the replication slave
-
-                    ( echo "stop slave;"
-                      echo "change master to master_host='${cfg.replication.masterHost}', master_user='${cfg.replication.masterUser}', master_password='${cfg.replication.masterPassword}';"
-                      echo "start slave;"
+                    ( echo "CREATE USER IF NOT EXISTS '${user.name}'@'localhost' IDENTIFIED WITH ${if isMariaDB then "unix_socket" else "auth_socket"};"
+                      ${concatStringsSep "\n" (mapAttrsToList (database: permission: ''
+                        echo "GRANT ${permission} ON ${database} TO '${user.name}'@'localhost';"
+                      '') user.ensurePermissions)}
                     ) | ${mysql}/bin/mysql -u root -N
-                  ''}
-
-                ${optionalString (cfg.initialScript != null)
-                  ''
-                    # Execute initial script
-                    # 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)
-                  ''
-                    # Change root password
-
-                    ( echo "use mysql;"
-                      echo "update user set Password=password('$(cat ${cfg.rootPassword})') where User='root';"
-                      echo "flush privileges;"
-                    ) | ${mysql}/bin/mysql -u root -N
-                  ''}
-
-              rm /tmp/mysql_init
-            fi
-
-            ${optionalString (cfg.ensureDatabases != []) ''
-              (
-              ${concatMapStrings (database: ''
-                echo "CREATE DATABASE IF NOT EXISTS \`${database}\`;"
-              '') cfg.ensureDatabases}
-              ) | ${mysql}/bin/mysql -u root -N
-            ''}
-
-            ${concatMapStrings (user:
-              ''
-                ( echo "CREATE USER IF NOT EXISTS '${user.name}'@'localhost' IDENTIFIED WITH ${if isMariaDB then "unix_socket" else "auth_socket"};"
-                  ${concatStringsSep "\n" (mapAttrsToList (database: permission: ''
-                  echo "GRANT ${permission} ON ${database} TO '${user.name}'@'localhost';"
-                  '') user.ensurePermissions)}
-                ) | ${mysql}/bin/mysql -u root -N
-              '') cfg.ensureUsers}
-
-          ''; # */
+                  '') cfg.ensureUsers}
+              '';
+            in
+              # ensureDatbases & ensureUsers depends on this script being run as root
+              # when the user has secured their mysql install
+              "+${setupScript}";
+        };
       };
 
   };
diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix
index c2f458c03794..d8e2c715afb9 100644
--- a/nixos/modules/services/databases/openldap.nix
+++ b/nixos/modules/services/databases/openldap.nix
@@ -237,8 +237,8 @@ in
   config = mkIf cfg.enable {
     assertions = [
       {
-        assertion = cfg.rootpwFile != null || cfg.rootpw != null;
-        message = "Either services.openldap.rootpw or services.openldap.rootpwFile must be set";
+        assertion = cfg.configDir != null || cfg.rootpwFile != null || cfg.rootpw != null;
+        message = "services.openldap: Unless configDir is set, either rootpw or rootpwFile must be set";
       }
     ];
 
diff --git a/nixos/modules/services/databases/postgresql.nix b/nixos/modules/services/databases/postgresql.nix
index 5661edbee2db..7ff899970ccd 100644
--- a/nixos/modules/services/databases/postgresql.nix
+++ b/nixos/modules/services/databases/postgresql.nix
@@ -6,23 +6,10 @@ let
 
   cfg = config.services.postgresql;
 
-  # see description of extraPlugins
-  postgresqlAndPlugins = pg:
-    if cfg.extraPlugins == [] then pg
-    else pkgs.buildEnv {
-      name = "postgresql-and-plugins-${(builtins.parseDrvName pg.name).version}";
-      paths = [ pg pg.lib ] ++ cfg.extraPlugins;
-      buildInputs = [ pkgs.makeWrapper ];
-      postBuild =
-        ''
-          mkdir -p $out/bin
-          rm $out/bin/{pg_config,postgres,pg_ctl}
-          cp --target-directory=$out/bin ${pg}/bin/{postgres,pg_config,pg_ctl}
-          wrapProgram $out/bin/postgres --set NIX_PGLIBDIR $out/lib
-        '';
-    };
-
-  postgresql = postgresqlAndPlugins cfg.package;
+  postgresql =
+    if cfg.extraPlugins == []
+      then cfg.package
+      else cfg.package.withPackages (_: cfg.extraPlugins);
 
   # The main PostgreSQL configuration file.
   configFile = pkgs.writeText "postgresql.conf"
@@ -55,7 +42,7 @@ in
 
       package = mkOption {
         type = types.package;
-        example = literalExample "pkgs.postgresql_9_6";
+        example = literalExample "pkgs.postgresql_11";
         description = ''
           PostgreSQL package to use.
         '';
@@ -71,7 +58,7 @@ in
 
       dataDir = mkOption {
         type = types.path;
-        example = "/var/lib/postgresql/9.6";
+        example = "/var/lib/postgresql/11";
         description = ''
           Data directory for PostgreSQL.
         '';
@@ -192,17 +179,11 @@ in
       extraPlugins = mkOption {
         type = types.listOf types.path;
         default = [];
-        example = literalExample "[ (pkgs.postgis.override { postgresql = pkgs.postgresql_9_4; }) ]";
+        example = literalExample "with pkgs.postgresql_11.pkgs; [ postgis pg_repack ]";
         description = ''
-          When this list contains elements a new store path is created.
-          PostgreSQL and the elements are symlinked into it. Then pg_config,
-          postgres and pg_ctl are copied to make them use the new
-          $out/lib directory as pkglibdir. This makes it possible to use postgis
-          without patching the .sql files which reference $libdir/postgis-1.5.
+          List of PostgreSQL plugins. PostgreSQL version for each plugin should
+          match version for <literal>services.postgresql.package</literal> value.
         '';
-        # Note: the duplication of executables is about 4MB size.
-        # So a nicer solution was patching postgresql to allow setting the
-        # libdir explicitely.
       };
 
       extraConfig = mkOption {
@@ -270,6 +251,10 @@ in
 
     environment.systemPackages = [ postgresql ];
 
+    environment.pathsToLink = [
+     "/share/postgresql"
+    ];
+
     systemd.services.postgresql =
       { description = "PostgreSQL Server";
 
diff --git a/nixos/modules/services/databases/postgresql.xml b/nixos/modules/services/databases/postgresql.xml
index 14f4d4909bc0..72d4a8249a32 100644
--- a/nixos/modules/services/databases/postgresql.xml
+++ b/nixos/modules/services/databases/postgresql.xml
@@ -27,10 +27,10 @@
    <filename>configuration.nix</filename>:
 <programlisting>
 <xref linkend="opt-services.postgresql.enable"/> = true;
-<xref linkend="opt-services.postgresql.package"/> = pkgs.postgresql_9_4;
+<xref linkend="opt-services.postgresql.package"/> = pkgs.postgresql_11;
 </programlisting>
    Note that you are required to specify the desired version of PostgreSQL
-   (e.g. <literal>pkgs.postgresql_9_4</literal>). Since upgrading your
+   (e.g. <literal>pkgs.postgresql_11</literal>). Since upgrading your
    PostgreSQL version requires a database dump and reload (see below), NixOS
    cannot provide a default value for
    <xref linkend="opt-services.postgresql.package"/> such as the most recent
@@ -42,17 +42,17 @@
 whether PostgreSQL works by running <command>psql</command>:
 
 <screen>
-$ psql
+<prompt>$ </prompt>psql
 psql (9.2.9)
 Type "help" for help.
 
-alice=>
+<prompt>alice=></prompt>
 </screen>
 -->
 
   <para>
    By default, PostgreSQL stores its databases in
-   <filename>/var/db/postgresql</filename>. You can override this using
+   <filename>/var/lib/postgresql/$psqlSchema</filename>. You can override this using
    <xref linkend="opt-services.postgresql.dataDir"/>, e.g.
 <programlisting>
 <xref linkend="opt-services.postgresql.dataDir"/> = "/data/postgresql";
@@ -74,4 +74,70 @@ alice=>
    <link linkend="opt-services.postgresql.enable">here</link>.
   </para>
  </section>
+ <section xml:id="module-services-postgres-plugins">
+  <title>Plugins</title>
+
+  <para>
+   Plugins collection for each PostgreSQL version can be accessed with
+   <literal>.pkgs</literal>. For example, for
+   <literal>pkgs.postgresql_11</literal> package, its plugin collection is
+   accessed by <literal>pkgs.postgresql_11.pkgs</literal>:
+<screen>
+<prompt>$ </prompt>nix repl '&lt;nixpkgs&gt;'
+
+Loading '&lt;nixpkgs&gt;'...
+Added 10574 variables.
+
+<prompt>nix-repl&gt; </prompt>postgresql_11.pkgs.&lt;TAB&gt;&lt;TAB&gt;
+postgresql_11.pkgs.cstore_fdw        postgresql_11.pkgs.pg_repack
+postgresql_11.pkgs.pg_auto_failover  postgresql_11.pkgs.pg_safeupdate
+postgresql_11.pkgs.pg_bigm           postgresql_11.pkgs.pg_similarity
+postgresql_11.pkgs.pg_cron           postgresql_11.pkgs.pg_topn
+postgresql_11.pkgs.pg_hll            postgresql_11.pkgs.pgjwt
+postgresql_11.pkgs.pg_partman        postgresql_11.pkgs.pgroonga
+...
+</screen>
+  </para>
+  <para>
+    To add plugins via NixOS configuration, set <literal>services.postgresql.extraPlugins</literal>:
+<programlisting>
+<xref linkend="opt-services.postgresql.package"/> = pkgs.postgresql_11;
+<xref linkend="opt-services.postgresql.extraPlugins"/> = with pkgs.postgresql_11.pkgs; [
+  pg_repack
+  postgis
+];
+</programlisting>
+  </para>
+  <para>
+   You can build custom PostgreSQL-with-plugins (to be used outside of NixOS) using
+   function <literal>.withPackages</literal>. For example, creating a custom
+   PostgreSQL package in an overlay can look like:
+<programlisting>
+self: super: {
+  postgresql_custom = self.postgresql_11.withPackages (ps: [
+    ps.pg_repack
+    ps.postgis
+  ]);
+}
+</programlisting>
+  </para>
+  <para>
+    Here's a recipe on how to override a particular plugin through an overlay:
+<programlisting>
+self: super: {
+  postgresql_11 = super.postgresql_11.override { this = self.postgresql_11; } // {
+    pkgs = super.postgresql_11.pkgs // {
+      pg_repack = super.postgresql_11.pkgs.pg_repack.overrideAttrs (_: {
+        name = "pg_repack-v20181024";
+        src = self.fetchzip {
+          url = "https://github.com/reorg/pg_repack/archive/923fa2f3c709a506e111cc963034bf2fd127aa00.tar.gz";
+          sha256 = "17k6hq9xaax87yz79j773qyigm4fwk8z4zh5cyp6z0sxnwfqxxw5";
+        };
+      });
+    };
+  };
+}
+</programlisting>
+  </para>
+ </section>
 </chapter>
diff --git a/nixos/modules/services/desktops/deepin/deepin.nix b/nixos/modules/services/desktops/deepin/deepin.nix
index d9d5d539e422..931bac58aceb 100644
--- a/nixos/modules/services/desktops/deepin/deepin.nix
+++ b/nixos/modules/services/desktops/deepin/deepin.nix
@@ -33,8 +33,10 @@
       environment.systemPackages = [
         pkgs.deepin.dde-api
         pkgs.deepin.dde-calendar
+        pkgs.deepin.dde-control-center
         pkgs.deepin.dde-daemon
         pkgs.deepin.dde-dock
+        pkgs.deepin.dde-launcher
         pkgs.deepin.dde-file-manager
         pkgs.deepin.dde-session-ui
         pkgs.deepin.deepin-anything
@@ -45,8 +47,10 @@
       services.dbus.packages = [
         pkgs.deepin.dde-api
         pkgs.deepin.dde-calendar
+        pkgs.deepin.dde-control-center
         pkgs.deepin.dde-daemon
         pkgs.deepin.dde-dock
+        pkgs.deepin.dde-launcher
         pkgs.deepin.dde-file-manager
         pkgs.deepin.dde-session-ui
         pkgs.deepin.deepin-anything
diff --git a/nixos/modules/services/desktops/flatpak.nix b/nixos/modules/services/desktops/flatpak.nix
index cfca1893bd82..1492d855aa03 100644
--- a/nixos/modules/services/desktops/flatpak.nix
+++ b/nixos/modules/services/desktops/flatpak.nix
@@ -15,38 +15,28 @@ in {
   options = {
     services.flatpak = {
       enable = mkEnableOption "flatpak";
-
-      extraPortals = mkOption {
-        type = types.listOf types.package;
-        default = [];
-        description = ''
-          List of additional portals to add to path. Portals allow interaction
-          with system, like choosing files or taking screenshots. At minimum,
-          a desktop portal implementation should be listed. GNOME already
-          adds <package>xdg-desktop-portal-gtk</package>; for KDE, there
-          is <package>xdg-desktop-portal-kde</package>. Other desktop
-          environments will probably want to do the same.
-        '';
-      };
     };
   };
 
 
   ###### implementation
   config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion = (config.xdg.portal.enable == true);
+        message = "To use Flatpak you must enable XDG Desktop Portals with xdg.portal.enable.";
+      }
+    ];
+
     environment.systemPackages = [ pkgs.flatpak ];
 
-    services.dbus.packages = [ pkgs.flatpak pkgs.xdg-desktop-portal ] ++ cfg.extraPortals;
+    services.dbus.packages = [ pkgs.flatpak ];
 
-    systemd.packages = [ pkgs.flatpak pkgs.xdg-desktop-portal ] ++ cfg.extraPortals;
+    systemd.packages = [ pkgs.flatpak ];
 
     environment.profiles = [
       "$HOME/.local/share/flatpak/exports"
       "/var/lib/flatpak/exports"
     ];
-
-    environment.variables = {
-      XDG_DESKTOP_PORTAL_PATH = map (p: "${p}/share/xdg-desktop-portal/portals") cfg.extraPortals;
-    };
   };
 }
diff --git a/nixos/modules/services/desktops/flatpak.xml b/nixos/modules/services/desktops/flatpak.xml
index 8045d5fa14f8..8f080b250228 100644
--- a/nixos/modules/services/desktops/flatpak.xml
+++ b/nixos/modules/services/desktops/flatpak.xml
@@ -21,7 +21,7 @@
   <filename>configuration.nix</filename>:
 <programlisting>
   <xref linkend="opt-services.flatpak.enable"/> = true;
-  </programlisting>
+</programlisting>
  </para>
  <para>
   For the sandboxed apps to work correctly, desktop integration portals need to
@@ -29,28 +29,28 @@
   in other cases, you will need to add something like the following to your
   <filename>configuration.nix</filename>:
 <programlisting>
-  <xref linkend="opt-services.flatpak.extraPortals"/> = [ pkgs.xdg-desktop-portal-gtk ];
-  </programlisting>
+  <xref linkend="opt-xdg.portal.extraPortals"/> = [ pkgs.xdg-desktop-portal-gtk ];
+</programlisting>
  </para>
  <para>
   Then, you will need to add a repository, for example,
   <link xlink:href="https://github.com/flatpak/flatpak/wiki">Flathub</link>,
   either using the following commands:
-<programlisting>
-  flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
-  flatpak update
-  </programlisting>
+<screen>
+<prompt>$ </prompt>flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
+<prompt>$ </prompt>flatpak update
+</screen>
   or by opening the
   <link xlink:href="https://flathub.org/repo/flathub.flatpakrepo">repository
   file</link> in GNOME Software.
  </para>
  <para>
   Finally, you can search and install programs:
-<programlisting>
-  flatpak search bustle
-  flatpak install flathub org.freedesktop.Bustle
-  flatpak run org.freedesktop.Bustle
-  </programlisting>
+<screen>
+<prompt>$ </prompt>flatpak search bustle
+<prompt>$ </prompt>flatpak install flathub org.freedesktop.Bustle
+<prompt>$ </prompt>flatpak run org.freedesktop.Bustle
+</screen>
   Again, GNOME Software offers graphical interface for these tasks.
  </para>
 </chapter>
diff --git a/nixos/modules/services/desktops/tumbler.nix b/nixos/modules/services/desktops/tumbler.nix
index ccbb6d1434d9..d18088d4634b 100644
--- a/nixos/modules/services/desktops/tumbler.nix
+++ b/nixos/modules/services/desktops/tumbler.nix
@@ -23,7 +23,7 @@ in
 
       package = mkOption {
         type = types.package;
-        default = pkgs.xfce4-13.tumbler;
+        default = pkgs.xfce4-14.tumbler;
         description = "Which tumbler package to use";
         example = pkgs.xfce4-12.tumbler;
       };
diff --git a/nixos/modules/services/development/bloop.nix b/nixos/modules/services/development/bloop.nix
index 56904b7c40e6..226718a9e80a 100644
--- a/nixos/modules/services/development/bloop.nix
+++ b/nixos/modules/services/development/bloop.nix
@@ -9,6 +9,20 @@ let
 in {
 
   options.services.bloop = {
+    extraOptions = mkOption {
+      type = types.listOf types.str;
+      default = [ ];
+      example = [
+        "-J-Xmx2G"
+        "-J-XX:MaxInlineLevel=20"
+        "-J-XX:+UseParallelGC"
+      ];
+      description = ''
+        Specifies additional command line argument to pass to bloop
+        java process.
+      '';
+    };
+
     install = mkOption {
       type = types.bool;
       default = false;
@@ -25,10 +39,13 @@ in {
     systemd.user.services.bloop = {
       description = "Bloop Scala build server";
 
+      environment = {
+        PATH = mkForce "${makeBinPath [ config.programs.java.package ]}";
+      };
       serviceConfig = {
-        Type      = "simple";
-        ExecStart = ''${pkgs.bloop}/bin/blp-server'';
-        Restart   = "always";
+        Type        = "simple";
+        ExecStart   = ''${pkgs.bloop}/bin/bloop server'';
+        Restart     = "always";
       };
     };
 
diff --git a/nixos/modules/services/editors/emacs.xml b/nixos/modules/services/editors/emacs.xml
index 1ac53c818a7f..a3041ae22e78 100644
--- a/nixos/modules/services/editors/emacs.xml
+++ b/nixos/modules/services/editors/emacs.xml
@@ -95,7 +95,8 @@
     also available in Nixpkgs:
     <link xlink:href="https://www.gnu.org/software/zile/">Zile</link>,
     <link xlink:href="http://homepage.boetes.org/software/mg/">mg</link>,
-    <link xlink:href="http://yi-editor.github.io/">Yi</link>.
+    <link xlink:href="http://yi-editor.github.io/">Yi</link>,
+    <link xlink:href="https://joe-editor.sourceforge.io/">jmacs</link>.
    </para>
   </section>
 
@@ -238,8 +239,8 @@ in
    <para>
     You can check that it works by executing this in a terminal:
 <screen>
-$ nix-build emacs.nix
-$ ./result/bin/emacs -q
+<prompt>$ </prompt>nix-build emacs.nix
+<prompt>$ </prompt>./result/bin/emacs -q
 </screen>
     and then typing <literal>M-x package-initialize</literal>. Check that you
     can use all the packages you want in this Emacs instance. For example, try
@@ -403,9 +404,9 @@ in [...]
    <para>
     To start the daemon, execute the following:
 <screen>
-$ nixos-rebuild switch  # to activate the new configuration.nix
-$ systemctl --user daemon-reload        # to force systemd reload
-$ systemctl --user start emacs.service  # to start the Emacs daemon
+<prompt>$ </prompt>nixos-rebuild switch  # to activate the new configuration.nix
+<prompt>$ </prompt>systemctl --user daemon-reload        # to force systemd reload
+<prompt>$ </prompt>systemctl --user start emacs.service  # to start the Emacs daemon
 </screen>
     The server should now be ready to serve Emacs clients.
    </para>
diff --git a/nixos/modules/services/hardware/80-net-setup-link.rules b/nixos/modules/services/hardware/80-net-setup-link.rules
deleted file mode 100644
index 18547f170a3f..000000000000
--- a/nixos/modules/services/hardware/80-net-setup-link.rules
+++ /dev/null
@@ -1,13 +0,0 @@
-# Copied from systemd 203.
-ACTION=="remove", GOTO="net_name_slot_end"
-SUBSYSTEM!="net", GOTO="net_name_slot_end"
-NAME!="", GOTO="net_name_slot_end"
-
-IMPORT{cmdline}="net.ifnames"
-ENV{net.ifnames}=="0", GOTO="net_name_slot_end"
-
-NAME=="", ENV{ID_NET_NAME_ONBOARD}!="", NAME="$env{ID_NET_NAME_ONBOARD}"
-NAME=="", ENV{ID_NET_NAME_SLOT}!="", NAME="$env{ID_NET_NAME_SLOT}"
-NAME=="", ENV{ID_NET_NAME_PATH}!="", NAME="$env{ID_NET_NAME_PATH}"
-
-LABEL="net_name_slot_end"
diff --git a/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix b/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix
index bd114f0d2cca..fd19d8020fb8 100644
--- a/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix
+++ b/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix
@@ -19,7 +19,7 @@ nix-shell -E 'with import <nixpkgs> { }; brscan4-etc-files.override{netDevices=[
 
 */
 
-with lib; 
+with lib;
 
 let
 
@@ -40,7 +40,7 @@ stdenv.mkDerivation rec {
 
   nativeBuildInputs = [ brscan4 ];
 
-  configurePhase = ":";
+  dontConfigure = true;
 
   buildPhase = ''
     TARGET_DIR="$out/etc/opt/brother/scanner/brscan4"
diff --git a/nixos/modules/services/hardware/throttled.nix b/nixos/modules/services/hardware/throttled.nix
index cd5b01450e44..13fc5e4792e6 100644
--- a/nixos/modules/services/hardware/throttled.nix
+++ b/nixos/modules/services/hardware/throttled.nix
@@ -8,6 +8,12 @@ in {
   options = {
     services.throttled = {
       enable = mkEnableOption "fix for Intel CPU throttling";
+
+      extraConfig = mkOption {
+        type = types.str;
+        default = "";
+        description = "Alternative configuration";
+      };
     };
   };
 
@@ -16,6 +22,9 @@ in {
     # 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";
+    environment.etc."lenovo_fix.conf".source =
+      if cfg.extraConfig != ""
+      then pkgs.writeText "lenovo_fix.conf" cfg.extraConfig
+      else "${pkgs.throttled}/etc/lenovo_fix.conf";
   };
 }
diff --git a/nixos/modules/services/hardware/triggerhappy.nix b/nixos/modules/services/hardware/triggerhappy.nix
index bffe7353b10e..a500cb4fc367 100644
--- a/nixos/modules/services/hardware/triggerhappy.nix
+++ b/nixos/modules/services/hardware/triggerhappy.nix
@@ -17,7 +17,7 @@ let
     ${cfg.extraConfig}
   '';
 
-  bindingCfg = { config, ... }: {
+  bindingCfg = { ... }: {
     options = {
 
       keys = mkOption {
diff --git a/nixos/modules/services/hardware/udev.nix b/nixos/modules/services/hardware/udev.nix
index 0266286aaacf..83ab93bd7cfc 100644
--- a/nixos/modules/services/hardware/udev.nix
+++ b/nixos/modules/services/hardware/udev.nix
@@ -85,7 +85,7 @@ let
       for i in $import_progs $run_progs; do
         if [[ ! -x $i ]]; then
           echo "FAIL"
-          echo "$i is called in udev rules but not installed by udev"
+          echo "$i is called in udev rules but is not executable or does not exist"
           exit 1
         fi
       done
@@ -116,10 +116,6 @@ let
         exit 1
       fi
 
-      ${optionalString config.networking.usePredictableInterfaceNames ''
-        cp ${./80-net-setup-link.rules} $out/80-net-setup-link.rules
-      ''}
-
       # If auto-configuration is disabled, then remove
       # udev's 80-drivers.rules file, which contains rules for
       # automatically calling modprobe.
@@ -282,6 +278,8 @@ in
 
     services.udev.path = [ pkgs.coreutils pkgs.gnused pkgs.gnugrep pkgs.utillinux udev ];
 
+    boot.kernelParams = mkIf (!config.networking.usePredictableInterfaceNames) [ "net.ifnames=0" ];
+
     environment.etc =
       [ { source = udevRules;
           target = "udev/rules.d";
diff --git a/nixos/modules/services/logging/graylog.nix b/nixos/modules/services/logging/graylog.nix
index 74a7b3c9b470..a889a44d4b2b 100644
--- a/nixos/modules/services/logging/graylog.nix
+++ b/nixos/modules/services/logging/graylog.nix
@@ -108,7 +108,7 @@ in
       };
 
       extraConfig = mkOption {
-        type = types.str;
+        type = types.lines;
         default = "";
         description = "Any other configuration options you might want to add";
       };
@@ -134,6 +134,10 @@ in
       };
     };
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.messageJournalDir}' - ${cfg.user} - - -"
+    ];
+
     systemd.services.graylog = with pkgs; {
       description = "Graylog Server";
       wantedBy = [ "multi-user.target" ];
@@ -143,25 +147,22 @@ in
       };
       path = [ pkgs.jre_headless pkgs.which pkgs.procps ];
       preStart = ''
-        mkdir -p /var/lib/graylog -m 755
-
         rm -rf /var/lib/graylog/plugins || true
         mkdir -p /var/lib/graylog/plugins -m 755
 
+        mkdir -p "$(dirname ${cfg.nodeIdFile})"
+        chown -R ${cfg.user} "$(dirname ${cfg.nodeIdFile})"
+
         for declarativeplugin in `ls ${glPlugins}/bin/`; do
           ln -sf ${glPlugins}/bin/$declarativeplugin /var/lib/graylog/plugins/$declarativeplugin
         done
         for includedplugin in `ls ${cfg.package}/plugin/`; do
           ln -s ${cfg.package}/plugin/$includedplugin /var/lib/graylog/plugins/$includedplugin || true
         done
-        chown -R ${cfg.user} /var/lib/graylog
-
-        mkdir -p ${cfg.messageJournalDir} -m 755
-        chown -R ${cfg.user} ${cfg.messageJournalDir}
       '';
       serviceConfig = {
         User="${cfg.user}";
-        PermissionsStartOnly=true;
+        StateDirectory = "graylog";
         ExecStart = "${cfg.package}/bin/graylogctl run";
       };
     };
diff --git a/nixos/modules/services/logging/heartbeat.nix b/nixos/modules/services/logging/heartbeat.nix
index b595ac07bf5e..56fb4deabda5 100644
--- a/nixos/modules/services/logging/heartbeat.nix
+++ b/nixos/modules/services/logging/heartbeat.nix
@@ -54,16 +54,18 @@ in
 
   config = mkIf cfg.enable {
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.stateDir}' - nobody nogroup - -"
+    ];
+
     systemd.services.heartbeat = with pkgs; {
       description = "heartbeat log shipper";
       wantedBy = [ "multi-user.target" ];
       preStart = ''
         mkdir -p "${cfg.stateDir}"/{data,logs}
-        chown nobody:nogroup "${cfg.stateDir}"/{data,logs}
       '';
       serviceConfig = {
         User = "nobody";
-        PermissionsStartOnly = true;
         AmbientCapabilities = "cap_net_raw";
         ExecStart = "${pkgs.heartbeat}/bin/heartbeat -c \"${heartbeatYml}\" -path.data \"${cfg.stateDir}/data\" -path.logs \"${cfg.stateDir}/logs\"";
       };
diff --git a/nixos/modules/services/mail/dspam.nix b/nixos/modules/services/mail/dspam.nix
index 167b5aeccc84..72b8c4c08b92 100644
--- a/nixos/modules/services/mail/dspam.nix
+++ b/nixos/modules/services/mail/dspam.nix
@@ -113,19 +113,14 @@ in {
           Group = cfg.group;
           RuntimeDirectory = optional (cfg.domainSocket == defaultSock) "dspam";
           RuntimeDirectoryMode = optional (cfg.domainSocket == defaultSock) "0750";
-          PermissionsStartOnly = true;
+          StateDirectory = "dspam";
+          StateDirectoryMode = "0750";
+          LogsDirectory = "dspam";
+          LogsDirectoryMode = "0750";
           # DSPAM segfaults on just about every error
           Restart = "on-abort";
           RestartSec = "1s";
         };
-
-        preStart = ''
-          mkdir -m750 -p /var/lib/dspam
-          chown -R "${cfg.user}:${cfg.group}" /var/lib/dspam
-
-          mkdir -m750 -p /var/log/dspam
-          chown -R "${cfg.user}:${cfg.group}" /var/log/dspam
-        '';
       };
     }
 
diff --git a/nixos/modules/services/mail/opendkim.nix b/nixos/modules/services/mail/opendkim.nix
index 7855efb46c73..253823cbaf9c 100644
--- a/nixos/modules/services/mail/opendkim.nix
+++ b/nixos/modules/services/mail/opendkim.nix
@@ -101,13 +101,16 @@ in {
 
     environment.systemPackages = [ pkgs.opendkim ];
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.keyPath}' - ${cfg.user} ${cfg.group} - -"
+    ];
+
     systemd.services.opendkim = {
       description = "OpenDKIM signing and verification daemon";
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
 
       preStart = ''
-        mkdir -p "${cfg.keyPath}"
         cd "${cfg.keyPath}"
         if ! test -f ${cfg.selector}.private; then
           ${pkgs.opendkim}/bin/opendkim-genkey -s ${cfg.selector} -d all-domains-generic-key
@@ -116,7 +119,6 @@ in {
           cat ${cfg.selector}.txt
           echo "-------------------------------------------------------------"
         fi
-        chown ${cfg.user}:${cfg.group} ${cfg.selector}.private
       '';
 
       serviceConfig = {
@@ -124,7 +126,6 @@ in {
         User = cfg.user;
         Group = cfg.group;
         RuntimeDirectory = optional (cfg.socket == defaultSock) "opendkim";
-        PermissionsStartOnly = true;
       };
     };
 
diff --git a/nixos/modules/services/mail/postfix.nix b/nixos/modules/services/mail/postfix.nix
index d43733484ffa..dab1b29aa4be 100644
--- a/nixos/modules/services/mail/postfix.nix
+++ b/nixos/modules/services/mail/postfix.nix
@@ -13,6 +13,7 @@ let
                       || cfg.extraAliases != "";
   haveTransport = cfg.transport != "";
   haveVirtual = cfg.virtual != "";
+  haveLocalRecipients = cfg.localRecipients != null;
 
   clientAccess =
     optional (cfg.dnsBlacklistOverrides != "")
@@ -244,6 +245,7 @@ let
 
   aliasesFile = pkgs.writeText "postfix-aliases" aliases;
   virtualFile = pkgs.writeText "postfix-virtual" cfg.virtual;
+  localRecipientMapFile = pkgs.writeText "postfix-local-recipient-map" (concatMapStrings (x: x + " ACCEPT\n") cfg.localRecipients);
   checkClientAccessFile = pkgs.writeText "postfix-check-client-access" cfg.dnsBlacklistOverrides;
   mainCfFile = pkgs.writeText "postfix-main.cf" mainCf;
   masterCfFile = pkgs.writeText "postfix-master.cf" masterCfContent;
@@ -506,6 +508,19 @@ in
         '';
       };
 
+      localRecipients = mkOption {
+        type = with types; nullOr (listOf string);
+        default = null;
+        description = ''
+          List of accepted local users. Specify a bare username, an
+          <literal>"@domain.tld"</literal> wild-card, or a complete
+          <literal>"user@domain.tld"</literal> address. If set, these names end
+          up in the local recipient map -- see the local(8) man-page -- and
+          effectively replace the system user database lookup that's otherwise
+          used by default.
+        '';
+      };
+
       transport = mkOption {
         default = "";
         description = "
@@ -742,6 +757,7 @@ in
       // optionalAttrs haveAliases { alias_maps = [ "${cfg.aliasMapType}:/etc/postfix/aliases" ]; }
       // optionalAttrs haveTransport { transport_maps = [ "hash:/etc/postfix/transport" ]; }
       // optionalAttrs haveVirtual { virtual_alias_maps = [ "${cfg.virtualMapType}:/etc/postfix/virtual" ]; }
+      // optionalAttrs haveLocalRecipients { local_recipient_maps = [ "hash:/etc/postfix/local_recipients" ] ++ optional haveAliases "$alias_maps"; }
       // optionalAttrs (cfg.dnsBlacklists != []) { smtpd_client_restrictions = clientRestrictions; }
       // optionalAttrs cfg.useSrs {
         sender_canonical_maps = [ "tcp:127.0.0.1:10001" ];
@@ -869,6 +885,9 @@ in
     (mkIf haveVirtual {
       services.postfix.mapFiles."virtual" = virtualFile;
     })
+    (mkIf haveLocalRecipients {
+      services.postfix.mapFiles."local_recipients" = localRecipientMapFile;
+    })
     (mkIf cfg.enableHeaderChecks {
       services.postfix.mapFiles."header_checks" = headerChecksFile;
     })
diff --git a/nixos/modules/services/mail/rspamd.nix b/nixos/modules/services/mail/rspamd.nix
index c9ba86780213..5541b8b79b7e 100644
--- a/nixos/modules/services/mail/rspamd.nix
+++ b/nixos/modules/services/mail/rspamd.nix
@@ -5,7 +5,6 @@ with lib;
 let
 
   cfg = config.services.rspamd;
-  opts = options.services.rspamd;
   postfixCfg = config.services.postfix;
 
   bindSocketOpts = {options, config, ... }: {
diff --git a/nixos/modules/services/misc/apache-kafka.nix b/nixos/modules/services/misc/apache-kafka.nix
index 363ac4411e11..9eeae9556992 100644
--- a/nixos/modules/services/misc/apache-kafka.nix
+++ b/nixos/modules/services/misc/apache-kafka.nix
@@ -131,6 +131,8 @@ in {
       home = head cfg.logDirs;
     };
 
+    systemd.tmpfiles.rules = map (logDir: "d '${logDir} 0700 apache-kafka - - -") cfg.logDirs;
+
     systemd.services.apache-kafka = {
       description = "Apache Kafka Daemon";
       wantedBy = [ "multi-user.target" ];
@@ -145,15 +147,8 @@ in {
             ${serverConfig}
         '';
         User = "apache-kafka";
-        PermissionsStartOnly = true;
         SuccessExitStatus = "0 143";
       };
-      preStart = ''
-        mkdir -m 0700 -p ${concatStringsSep " " cfg.logDirs}
-        if [ "$(id -u)" = 0 ]; then
-           chown apache-kafka ${concatStringsSep " " cfg.logDirs};
-        fi
-      '';
     };
 
   };
diff --git a/nixos/modules/services/misc/couchpotato.nix b/nixos/modules/services/misc/couchpotato.nix
index 70aa895f76d8..528af486b414 100644
--- a/nixos/modules/services/misc/couchpotato.nix
+++ b/nixos/modules/services/misc/couchpotato.nix
@@ -19,16 +19,11 @@ in
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
 
-      preStart = ''
-        mkdir -p /var/lib/couchpotato
-        chown -R couchpotato:couchpotato /var/lib/couchpotato
-      '';
-
       serviceConfig = {
         Type = "simple";
         User = "couchpotato";
         Group = "couchpotato";
-        PermissionsStartOnly = "true";
+        StateDirectory = "couchpotato";
         ExecStart = "${pkgs.couchpotato}/bin/couchpotato";
         Restart = "on-failure";
       };
diff --git a/nixos/modules/services/misc/gitea.nix b/nixos/modules/services/misc/gitea.nix
index 6fd4183bd6b4..59c1c104b9b9 100644
--- a/nixos/modules/services/misc/gitea.nix
+++ b/nixos/modules/services/misc/gitea.nix
@@ -38,6 +38,7 @@ let
     HTTP_PORT = ${toString cfg.httpPort}
     ROOT_URL = ${cfg.rootUrl}
     STATIC_ROOT_PATH = ${cfg.staticRootPath}
+    LFS_JWT_SECRET = #jwtsecret#
 
     [session]
     COOKIE_NAME = session
@@ -54,6 +55,11 @@ let
     [service]
     DISABLE_REGISTRATION = ${boolToString cfg.disableRegistration}
 
+    ${optionalString (cfg.mailerPasswordFile != null) ''
+      [mailer]
+      PASSWD = #mailerpass#
+    ''}
+
     ${cfg.extraConfig}
   '';
 in
@@ -159,7 +165,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.";
         };
@@ -173,10 +180,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.";
         };
       };
 
@@ -256,6 +260,13 @@ in
         description = "Upper level of template and static files path.";
       };
 
+      mailerPasswordFile = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/var/lib/secrets/gitea/mailpw";
+        description = "Path to a file containing the SMTP password.";
+      };
+
       disableRegistration = mkEnableOption "the registration lock" // {
         description = ''
           By default any user can create an account on this <literal>gitea</literal> instance.
@@ -277,7 +288,47 @@ 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' - ${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";
@@ -288,28 +339,36 @@ in
       preStart = let
         runConfig = "${cfg.stateDir}/custom/conf/app.ini";
         secretKey = "${cfg.stateDir}/custom/conf/secret_key";
+        jwtSecret = "${cfg.stateDir}/custom/conf/jwt_secret";
       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
-              head -c 16 /dev/urandom | base64 > ${secretKey}
+              ${gitea.bin}/bin/gitea generate secret SECRET_KEY > ${secretKey}
+          fi
+
+          if [ ! -e ${jwtSecret} ]; then
+              ${gitea.bin}/bin/gitea generate secret LFS_JWT_SECRET > ${jwtSecret}
           fi
 
-          KEY=$(head -n1 ${secretKey})
-          DBPASS=$(head -n1 ${cfg.database.passwordFile})
+          KEY="$(head -n1 ${secretKey})"
+          DBPASS="$(head -n1 ${cfg.database.passwordFile})"
+          JWTSECRET="$(head -n1 ${jwtSecret})"
+          ${if (cfg.mailerPasswordFile == null) then ''
+            MAILERPASSWORD="#mailerpass#"
+          '' else ''
+            MAILERPASSWORD="$(head -n1 ${cfg.mailerPasswordFile} || :)"
+          ''}
           sed -e "s,#secretkey#,$KEY,g" \
               -e "s,#dbpass#,$DBPASS,g" \
+              -e "s,#jwtsecet#,$JWTSECET,g" \
+              -e "s,#mailerpass#,$MAILERPASSWORD,g" \
               -i ${runConfig}
-          chmod 640 ${runConfig} ${secretKey}
+          chmod 640 ${runConfig} ${secretKey} ${jwtSecret}
         ''}
 
-        mkdir -p ${cfg.repositoryRoot}
         # update all hooks' binary paths
         HOOKS=$(find ${cfg.repositoryRoot} -mindepth 4 -maxdepth 6 -type f -wholename "*git/hooks/*")
         if [ "$HOOKS" ]
@@ -319,43 +378,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";
       };
@@ -367,15 +402,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/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index 71277b48ecd9..2f3f76d79ff3 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -52,7 +52,7 @@ let
     gitlab_url = "http+unix://${pathUrlQuote gitlabSocket}";
     http_settings.self_signed_cert = false;
     repos_path = "${cfg.statePath}/repositories";
-    secret_file = "${cfg.statePath}/config/gitlab_shell_secret";
+    secret_file = "${cfg.statePath}/gitlab_shell_secret";
     log_file = "${cfg.statePath}/log/gitlab-shell.log";
     custom_hooks_dir = "${cfg.statePath}/custom_hooks";
     redis = {
@@ -109,7 +109,7 @@ let
       gitlab_shell = {
         path = "${cfg.packages.gitlab-shell}";
         hooks_path = "${cfg.statePath}/shell/hooks";
-        secret_file = "${cfg.statePath}/config/gitlab_shell_secret";
+        secret_file = "${cfg.statePath}/gitlab_shell_secret";
         upload_pack = true;
         receive_pack = true;
       };
@@ -132,14 +132,9 @@ let
     HOME = "${cfg.statePath}/home";
     UNICORN_PATH = "${cfg.statePath}/";
     GITLAB_PATH = "${cfg.packages.gitlab}/share/gitlab/";
-    GITLAB_STATE_PATH = cfg.statePath;
-    GITLAB_UPLOADS_PATH = "${cfg.statePath}/uploads";
     SCHEMA = "${cfg.statePath}/db/schema.rb";
+    GITLAB_UPLOADS_PATH = "${cfg.statePath}/uploads";
     GITLAB_LOG_PATH = "${cfg.statePath}/log";
-    GITLAB_SHELL_PATH = "${cfg.packages.gitlab-shell}";
-    GITLAB_SHELL_CONFIG_PATH = "${cfg.statePath}/shell/config.yml";
-    GITLAB_SHELL_SECRET_PATH = "${cfg.statePath}/config/gitlab_shell_secret";
-    GITLAB_SHELL_HOOKS_PATH = "${cfg.statePath}/shell/hooks";
     GITLAB_REDIS_CONFIG_FILE = pkgs.writeText "redis.yml" (builtins.toJSON redisConfig);
     prometheus_multiproc_dir = "/run/gitlab";
     RAILS_ENV = "production";
@@ -149,7 +144,7 @@ let
     name = "gitlab-rake";
     buildInputs = [ pkgs.makeWrapper ];
     dontBuild = true;
-    unpackPhase = ":";
+    dontUnpack = true;
     installPhase = ''
       mkdir -p $out/bin
       makeWrapper ${cfg.packages.gitlab.rubyEnv}/bin/rake $out/bin/gitlab-rake \
@@ -164,7 +159,7 @@ let
     name = "gitlab-rails";
     buildInputs = [ pkgs.makeWrapper ];
     dontBuild = true;
-    unpackPhase = ":";
+    dontUnpack = true;
     installPhase = ''
       mkdir -p $out/bin
       makeWrapper ${cfg.packages.gitlab.rubyEnv}/bin/rails $out/bin/gitlab-rails \
@@ -502,23 +497,44 @@ in {
     systemd.tmpfiles.rules = [
       "d /run/gitlab 0755 ${cfg.user} ${cfg.group} -"
       "d ${gitlabEnv.HOME} 0750 ${cfg.user} ${cfg.group} -"
+      "z ${gitlabEnv.HOME}/.ssh/authorized_keys 0600 ${cfg.user} ${cfg.group} -"
       "d ${cfg.backupPath} 0750 ${cfg.user} ${cfg.group} -"
+      "d ${cfg.statePath} 0750 ${cfg.user} ${cfg.group} -"
       "d ${cfg.statePath}/builds 0750 ${cfg.user} ${cfg.group} -"
       "d ${cfg.statePath}/config 0750 ${cfg.user} ${cfg.group} -"
+      "d ${cfg.statePath}/config/initializers 0750 ${cfg.user} ${cfg.group} -"
       "d ${cfg.statePath}/db 0750 ${cfg.user} ${cfg.group} -"
       "d ${cfg.statePath}/log 0750 ${cfg.user} ${cfg.group} -"
       "d ${cfg.statePath}/repositories 2770 ${cfg.user} ${cfg.group} -"
       "d ${cfg.statePath}/shell 0750 ${cfg.user} ${cfg.group} -"
+      "d ${cfg.statePath}/tmp 0750 ${cfg.user} ${cfg.group} -"
       "d ${cfg.statePath}/tmp/pids 0750 ${cfg.user} ${cfg.group} -"
       "d ${cfg.statePath}/tmp/sockets 0750 ${cfg.user} ${cfg.group} -"
       "d ${cfg.statePath}/uploads 0700 ${cfg.user} ${cfg.group} -"
+      "d ${cfg.statePath}/custom_hooks 0700 ${cfg.user} ${cfg.group} -"
       "d ${cfg.statePath}/custom_hooks/pre-receive.d 0700 ${cfg.user} ${cfg.group} -"
       "d ${cfg.statePath}/custom_hooks/post-receive.d 0700 ${cfg.user} ${cfg.group} -"
       "d ${cfg.statePath}/custom_hooks/update.d 0700 ${cfg.user} ${cfg.group} -"
+      "d ${gitlabConfig.production.shared.path} 0750 ${cfg.user} ${cfg.group} -"
       "d ${gitlabConfig.production.shared.path}/artifacts 0750 ${cfg.user} ${cfg.group} -"
       "d ${gitlabConfig.production.shared.path}/lfs-objects 0750 ${cfg.user} ${cfg.group} -"
       "d ${gitlabConfig.production.shared.path}/pages 0750 ${cfg.user} ${cfg.group} -"
-    ];
+      "L+ ${cfg.statePath}/lib - - - - ${cfg.packages.gitlab}/share/gitlab/lib"
+      "L+ /run/gitlab/config - - - - ${cfg.statePath}/config"
+      "L+ /run/gitlab/log - - - - ${cfg.statePath}/log"
+      "L+ /run/gitlab/tmp - - - - ${cfg.statePath}/tmp"
+      "L+ /run/gitlab/uploads - - - - ${cfg.statePath}/uploads"
+
+      "L+ /run/gitlab/shell-config.yml - - - - ${pkgs.writeText "config.yml" (builtins.toJSON gitlabShellConfig)}"
+
+      "L+ ${cfg.statePath}/config/gitlab.yml - - - - ${pkgs.writeText "gitlab.yml" (builtins.toJSON gitlabConfig)}"
+      "L+ ${cfg.statePath}/config/database.yml - - - - ${pkgs.writeText "database.yml" (builtins.toJSON databaseConfig)}"
+      "L+ ${cfg.statePath}/config/secrets.yml - - - - ${pkgs.writeText "secrets.yml" (builtins.toJSON secretsConfig)}"
+      "L+ ${cfg.statePath}/config/unicorn.rb - - - - ${./defaultUnicornConfig.rb}"
+
+      "L+ ${cfg.statePath}/config/initializers/extra-gitlab.rb - - - - ${extraGitlabRb}"
+    ] ++ optional cfg.smtp.enable
+      "L+ ${cfg.statePath}/config/initializers/smtp_settings.rb - - - - ${smtpSettings}" ;
 
     systemd.services.gitlab-sidekiq = {
       after = [ "network.target" "redis.service" "gitlab.service" ];
@@ -570,6 +586,7 @@ in {
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       path = with pkgs; [
+        exiftool
         gitAndTools.git
         gnutar
         gzip
@@ -609,40 +626,14 @@ in {
         gnupg
       ];
       preStart = ''
-        cp -rf ${cfg.packages.gitlab}/share/gitlab/db/* ${cfg.statePath}/db
-        rm -rf ${cfg.statePath}/config
-        mkdir ${cfg.statePath}/config
-        if [ -e ${cfg.statePath}/lib ]; then
-          rm ${cfg.statePath}/lib
-        fi
+        ${pkgs.sudo}/bin/sudo -u ${cfg.user} cp -f ${cfg.packages.gitlab}/share/gitlab/VERSION ${cfg.statePath}/VERSION
+        ${pkgs.sudo}/bin/sudo -u ${cfg.user} rm -rf ${cfg.statePath}/db/*
+        ${pkgs.sudo}/bin/sudo -u ${cfg.user} cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/config.dist/* ${cfg.statePath}/config
+        ${pkgs.sudo}/bin/sudo -u ${cfg.user} cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/db/* ${cfg.statePath}/db
 
-        ln -sf ${cfg.packages.gitlab}/share/gitlab/lib ${cfg.statePath}/lib
-        [ -L /run/gitlab/config ] || ln -sf ${cfg.statePath}/config /run/gitlab/config
-        [ -L /run/gitlab/log ] || ln -sf ${cfg.statePath}/log /run/gitlab/log
-        [ -L /run/gitlab/tmp ] || ln -sf ${cfg.statePath}/tmp /run/gitlab/tmp
-        [ -L /run/gitlab/uploads ] || ln -sf ${cfg.statePath}/uploads /run/gitlab/uploads
-        cp ${cfg.packages.gitlab}/share/gitlab/VERSION ${cfg.statePath}/VERSION
-        cp -rf ${cfg.packages.gitlab}/share/gitlab/config.dist/* ${cfg.statePath}/config
-        ln -sf ${extraGitlabRb} ${cfg.statePath}/config/initializers/extra-gitlab.rb
-        ${optionalString cfg.smtp.enable ''
-          ln -sf ${smtpSettings} ${cfg.statePath}/config/initializers/smtp_settings.rb
-        ''}
-        ${pkgs.openssl}/bin/openssl rand -hex 32 > ${cfg.statePath}/config/gitlab_shell_secret
-
-        # JSON is a subset of YAML
-        ln -sf ${pkgs.writeText "gitlab.yml" (builtins.toJSON gitlabConfig)} ${cfg.statePath}/config/gitlab.yml
-        ln -sf ${pkgs.writeText "database.yml" (builtins.toJSON databaseConfig)} ${cfg.statePath}/config/database.yml
-        ln -sf ${pkgs.writeText "secrets.yml" (builtins.toJSON secretsConfig)} ${cfg.statePath}/config/secrets.yml
-        ln -sf ${./defaultUnicornConfig.rb} ${cfg.statePath}/config/unicorn.rb
-
-        # Install the shell required to push repositories
-        ln -sf ${pkgs.writeText "config.yml" (builtins.toJSON gitlabShellConfig)} /run/gitlab/shell-config.yml
-        [ -L ${cfg.statePath}/shell/hooks ] ||  ln -sf ${cfg.packages.gitlab-shell}/hooks ${cfg.statePath}/shell/hooks
-        ${cfg.packages.gitlab-shell}/bin/install
-
-        chown -R ${cfg.user}:${cfg.group} ${cfg.statePath}/
-        chmod -R ug+rwX,o-rwx+X ${cfg.statePath}/
-        chown -R ${cfg.user}:${cfg.group} /run/gitlab
+        ${pkgs.openssl}/bin/openssl rand -hex 32 > ${cfg.statePath}/gitlab_shell_secret
+
+        ${pkgs.sudo}/bin/sudo -u ${cfg.user} ${cfg.packages.gitlab-shell}/bin/install
 
         if ! test -e "${cfg.statePath}/db-created"; then
           if [ "${cfg.databaseHost}" = "127.0.0.1" ]; then
@@ -655,7 +646,7 @@ in {
 
           ${pkgs.sudo}/bin/sudo -u ${cfg.user} -H ${gitlab-rake}/bin/gitlab-rake db:schema:load
 
-          touch "${cfg.statePath}/db-created"
+          ${pkgs.sudo}/bin/sudo -u ${cfg.user} touch "${cfg.statePath}/db-created"
         fi
 
         # Always do the db migrations just to be sure the database is up-to-date
@@ -664,22 +655,13 @@ in {
         if ! test -e "${cfg.statePath}/db-seeded"; then
           ${pkgs.sudo}/bin/sudo -u ${cfg.user} ${gitlab-rake}/bin/gitlab-rake db:seed_fu \
             GITLAB_ROOT_PASSWORD='${cfg.initialRootPassword}' GITLAB_ROOT_EMAIL='${cfg.initialRootEmail}'
-          touch "${cfg.statePath}/db-seeded"
+          ${pkgs.sudo}/bin/sudo -u ${cfg.user} touch "${cfg.statePath}/db-seeded"
         fi
 
-        # The gitlab:shell:create_hooks task seems broken for fixing links
-        # so we instead delete all the hooks and create them anew
+        # We remove potentially broken links to old gitlab-shell versions
         rm -f ${cfg.statePath}/repositories/**/*.git/hooks
-        ${pkgs.sudo}/bin/sudo -u ${cfg.user} -H ${gitlab-rake}/bin/gitlab-rake gitlab:shell:create_hooks
 
         ${pkgs.sudo}/bin/sudo -u ${cfg.user} -H ${pkgs.git}/bin/git config --global core.autocrlf "input"
-
-        # Change permissions in the last step because some of the
-        # intermediary scripts like to create directories as root.
-        chmod -R u+rwX,go-rwx+X ${gitlabEnv.HOME}
-        chmod -R ug+rwX,o-rwx ${cfg.statePath}/repositories
-        chmod -R ug-s ${cfg.statePath}/repositories
-        find ${cfg.statePath}/repositories -type d -print0 | xargs -0 chmod g+s
       '';
 
       serviceConfig = {
diff --git a/nixos/modules/services/misc/gitlab.xml b/nixos/modules/services/misc/gitlab.xml
index ab99d7bd3a60..5ff570a442f6 100644
--- a/nixos/modules/services/misc/gitlab.xml
+++ b/nixos/modules/services/misc/gitlab.xml
@@ -138,13 +138,13 @@ services.gitlab = {
 
   <para>
    For example, to backup a Gitlab instance:
-<programlisting>
-$ sudo -u git -H gitlab-rake gitlab:backup:create
-</programlisting>
+<screen>
+<prompt>$ </prompt>sudo -u git -H gitlab-rake gitlab:backup:create
+</screen>
    A list of all availabe rake tasks can be obtained by running:
-<programlisting>
-$ sudo -u git -H gitlab-rake -T
-</programlisting>
+<screen>
+<prompt>$ </prompt>sudo -u git -H gitlab-rake -T
+</screen>
   </para>
  </section>
 </chapter>
diff --git a/nixos/modules/services/misc/gitolite.nix b/nixos/modules/services/misc/gitolite.nix
index c7f2a168f8ab..cbe2c06ab651 100644
--- a/nixos/modules/services/misc/gitolite.nix
+++ b/nixos/modules/services/misc/gitolite.nix
@@ -143,21 +143,37 @@ in
     users.users.${cfg.user} = {
       description     = "Gitolite user";
       home            = cfg.dataDir;
-      createHome      = true;
       uid             = config.ids.uids.gitolite;
       group           = cfg.group;
       useDefaultShell = true;
     };
     users.groups."${cfg.group}".gid = config.ids.gids.gitolite;
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' 0750 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.dataDir}'/.gitolite - ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.dataDir}'/.gitolite/logs - ${cfg.user} ${cfg.group} - -"
+
+      "Z ${cfg.dataDir} 0750 ${cfg.user} ${cfg.group} - -"
+    ];
+
     systemd.services."gitolite-init" = {
       description = "Gitolite initialization";
       wantedBy    = [ "multi-user.target" ];
       unitConfig.RequiresMountsFor = cfg.dataDir;
 
-      serviceConfig.User = "${cfg.user}";
-      serviceConfig.Type = "oneshot";
-      serviceConfig.RemainAfterExit = true;
+      environment = {
+        GITOLITE_RC = ".gitolite.rc";
+        GITOLITE_RC_DEFAULT = "${rcDir}/gitolite.rc.default";
+      };
+
+      serviceConfig = {
+        Type = "oneshot";
+        User = cfg.user;
+        Group = cfg.group;
+        WorkingDirectory = "~";
+        RemainAfterExit = true;
+      };
 
       path = [ pkgs.gitolite pkgs.git pkgs.perl pkgs.bash pkgs.diffutils config.programs.ssh.package ];
       script =
@@ -187,11 +203,6 @@ in
           '';
       in
         ''
-          cd ${cfg.dataDir}
-          mkdir -p .gitolite/logs
-
-          GITOLITE_RC=.gitolite.rc
-          GITOLITE_RC_DEFAULT=${rcDir}/gitolite.rc.default
           if ( [[ ! -e "$GITOLITE_RC" ]] && [[ ! -L "$GITOLITE_RC" ]] ) ||
              ( [[ -f "$GITOLITE_RC" ]] && diff -q "$GITOLITE_RC" "$GITOLITE_RC_DEFAULT" >/dev/null ) ||
              ( [[ -L "$GITOLITE_RC" ]] && [[ "$(readlink "$GITOLITE_RC")" =~ ^/nix/store/ ]] )
diff --git a/nixos/modules/services/misc/gollum.nix b/nixos/modules/services/misc/gollum.nix
index d1823bc6d4df..7653b415bf09 100644
--- a/nixos/modules/services/misc/gollum.nix
+++ b/nixos/modules/services/misc/gollum.nix
@@ -75,27 +75,24 @@ in
 
     users.groups.gollum = { };
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.stateDir}' - ${config.users.users.gollum.name} ${config.users.groups.gollum.name} - -"
+    ];
+
     systemd.services.gollum = {
       description = "Gollum wiki";
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       path = [ pkgs.git ];
 
-      preStart = let
-          userName = config.users.users.gollum.name;
-          groupName = config.users.groups.gollum.name;
-        in ''
-        # All of this is safe to be run on an existing repo
-        mkdir -p ${cfg.stateDir}
+      preStart = ''
+        # This is safe to be run on an existing repo
         git init ${cfg.stateDir}
-        chmod 755 ${cfg.stateDir}
-        chown -R ${userName}:${groupName} ${cfg.stateDir}
       '';
 
       serviceConfig = {
         User = config.users.users.gollum.name;
         Group = config.users.groups.gollum.name;
-        PermissionsStartOnly = true;
         ExecStart = ''
           ${pkgs.gollum}/bin/gollum \
             --port ${toString cfg.port} \
diff --git a/nixos/modules/services/misc/greenclip.nix b/nixos/modules/services/misc/greenclip.nix
new file mode 100644
index 000000000000..9152a782d7f0
--- /dev/null
+++ b/nixos/modules/services/misc/greenclip.nix
@@ -0,0 +1,31 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.greenclip;
+in {
+
+  options.services.greenclip = {
+    enable = mkEnableOption "Greenclip daemon";
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.haskellPackages.greenclip;
+      defaultText = "pkgs.haskellPackages.greenclip";
+      description = "greenclip derivation to use.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.user.services.greenclip = {
+      enable      = true;
+      description = "greenclip daemon";
+      wantedBy = [ "graphical-session.target" ];
+      after    = [ "graphical-session.target" ];
+      serviceConfig.ExecStart = "${cfg.package}/bin/greenclip daemon";
+    };
+
+    environment.systemPackages = [ cfg.package ];
+  };
+}
diff --git a/nixos/modules/services/misc/jackett.nix b/nixos/modules/services/misc/jackett.nix
index a07f20e5c24b..f2dc6635df93 100644
--- a/nixos/modules/services/misc/jackett.nix
+++ b/nixos/modules/services/misc/jackett.nix
@@ -34,6 +34,13 @@ in
         default = "jackett";
         description = "Group under which Jackett runs.";
       };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.jackett;
+        defaultText = "pkgs.jackett";
+        description = "Jackett package to use.";
+      };
     };
   };
 
@@ -51,7 +58,7 @@ in
         Type = "simple";
         User = cfg.user;
         Group = cfg.group;
-        ExecStart = "${pkgs.jackett}/bin/Jackett --NoUpdates --DataFolder '${cfg.dataDir}'";
+        ExecStart = "${cfg.package}/bin/Jackett --NoUpdates --DataFolder '${cfg.dataDir}'";
         Restart = "on-failure";
       };
     };
diff --git a/nixos/modules/services/misc/lidarr.nix b/nixos/modules/services/misc/lidarr.nix
index f466402abfc7..40755c162171 100644
--- a/nixos/modules/services/misc/lidarr.nix
+++ b/nixos/modules/services/misc/lidarr.nix
@@ -9,6 +9,37 @@ in
   options = {
     services.lidarr = {
       enable = mkEnableOption "Lidarr";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.lidarr;
+        defaultText = "pkgs.lidarr";
+        description = "The Lidarr package to use";
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Open ports in the firewall for Lidarr
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "lidarr";
+        description = ''
+          User account under which Lidarr runs.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "lidarr";
+        description = ''
+          Group under which Lidarr runs.
+        '';
+      };
     };
   };
 
@@ -20,9 +51,9 @@ in
 
       serviceConfig = {
         Type = "simple";
-        User = "lidarr";
-        Group = "lidarr";
-        ExecStart = "${pkgs.lidarr}/bin/Lidarr";
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart = "${cfg.package}/bin/Lidarr";
         Restart = "on-failure";
 
         StateDirectory = "lidarr";
@@ -30,12 +61,22 @@ in
       };
     };
 
-    users.users.lidarr = {
-      uid = config.ids.uids.lidarr;
-      home = "/var/lib/lidarr";
-      group = "lidarr";
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ 8686 ];
+    };
+
+    users.users = mkIf (cfg.user == "lidarr") {
+      lidarr = {
+        group = cfg.group;
+        home = "/var/lib/lidarr";
+        uid = config.ids.uids.lidarr;
+      };
     };
 
-    users.groups.lidarr.gid = config.ids.gids.lidarr;
+    users.groups = mkIf (cfg.group == "lidarr") {
+      lidarr = {
+        gid = config.ids.gids.lidarr;
+      };
+    };
   };
 }
diff --git a/nixos/modules/services/misc/nix-daemon.nix b/nixos/modules/services/misc/nix-daemon.nix
index 8db3c44246f3..6bc88c66dc19 100644
--- a/nixos/modules/services/misc/nix-daemon.nix
+++ b/nixos/modules/services/misc/nix-daemon.nix
@@ -272,10 +272,12 @@ in
 
       binaryCaches = mkOption {
         type = types.listOf types.str;
-        default = [ https://cache.nixos.org/ ];
         description = ''
           List of binary cache URLs used to obtain pre-built binaries
           of Nix packages.
+
+          By default https://cache.nixos.org/ is added,
+          to override it use <literal>lib.mkForce []</literal>.
         '';
       };
 
@@ -386,6 +388,7 @@ in
   config = {
 
     nix.binaryCachePublicKeys = [ "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" ];
+    nix.binaryCaches = [ "https://cache.nixos.org/" ];
 
     environment.etc."nix/nix.conf".source = nixConf;
 
@@ -464,7 +467,7 @@ in
         fi
       '';
 
-    nix.nrBuildUsers = mkDefault (lib.max 32 cfg.maxJobs);
+    nix.nrBuildUsers = mkDefault (lib.max 32 (if cfg.maxJobs == "auto" then 0 else cfg.maxJobs));
 
     users.users = nixbldUsers;
 
diff --git a/nixos/modules/services/misc/octoprint.nix b/nixos/modules/services/misc/octoprint.nix
index 52ae25201c35..8950010773cf 100644
--- a/nixos/modules/services/misc/octoprint.nix
+++ b/nixos/modules/services/misc/octoprint.nix
@@ -97,6 +97,10 @@ in
         gid = config.ids.gids.octoprint;
       });
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -"
+    ];
+
     systemd.services.octoprint = {
       description = "OctoPrint, web interface for 3D printers";
       wantedBy = [ "multi-user.target" ];
@@ -105,7 +109,6 @@ in
       environment.PYTHONPATH = makeSearchPathOutput "lib" pkgs.python.sitePackages [ pluginsEnv ];
 
       preStart = ''
-        mkdir -p "${cfg.stateDir}"
         if [ -e "${cfg.stateDir}/config.yaml" ]; then
           ${pkgs.yaml-merge}/bin/yaml-merge "${cfg.stateDir}/config.yaml" "${cfgUpdate}" > "${cfg.stateDir}/config.yaml.tmp"
           mv "${cfg.stateDir}/config.yaml.tmp" "${cfg.stateDir}/config.yaml"
@@ -113,14 +116,12 @@ in
           cp "${cfgUpdate}" "${cfg.stateDir}/config.yaml"
           chmod 600 "${cfg.stateDir}/config.yaml"
         fi
-        chown -R ${cfg.user}:${cfg.group} "${cfg.stateDir}"
       '';
 
       serviceConfig = {
         ExecStart = "${pkgs.octoprint}/bin/octoprint serve -b ${cfg.stateDir}";
         User = cfg.user;
         Group = cfg.group;
-        PermissionsStartOnly = true;
       };
     };
 
diff --git a/nixos/modules/services/misc/phd.nix b/nixos/modules/services/misc/phd.nix
deleted file mode 100644
index e605ce5de16e..000000000000
--- a/nixos/modules/services/misc/phd.nix
+++ /dev/null
@@ -1,52 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  cfg = config.services.phd;
-
-in
-
-{
-
-  ###### interface
-
-  options = {
-
-    services.phd = {
-
-      enable = mkOption {
-        default = false;
-        description = "
-          Enable daemons for phabricator.
-        ";
-      };
-
-    };
-
-  };
-
-  ###### implementation
-
-  config = mkIf cfg.enable {
-
-    systemd.services.phd = {
-      path = [ pkgs.phabricator pkgs.php pkgs.mercurial pkgs.git pkgs.subversion ];
-
-      after = [ "httpd.service" ];
-      wantedBy = [ "multi-user.target" ];
-
-      serviceConfig = {
-        ExecStart = "${pkgs.phabricator}/phabricator/bin/phd start";
-        ExecStop = "${pkgs.phabricator}/phabricator/bin/phd stop";
-        User = "wwwrun";
-        RestartSec = "30s";
-        Restart = "always";
-        StartLimitInterval = "1m";
-      };
-    };
-
-  };
-
-}
diff --git a/nixos/modules/services/misc/redmine.nix b/nixos/modules/services/misc/redmine.nix
index 91ddf2c3edf3..24b9e27ac2da 100644
--- a/nixos/modules/services/misc/redmine.nix
+++ b/nixos/modules/services/misc/redmine.nix
@@ -1,8 +1,10 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
+  inherit (lib) mkDefault mkEnableOption mkIf mkOption types;
+  inherit (lib) concatStringsSep literalExample mapAttrsToList;
+  inherit (lib) optional optionalAttrs optionalString singleton versionAtLeast;
+
   cfg = config.services.redmine;
 
   bundle = "${cfg.package}/share/redmine/bin/bundle";
@@ -11,11 +13,11 @@ let
     production:
       adapter: ${cfg.database.type}
       database: ${cfg.database.name}
-      host: ${cfg.database.host}
+      host: ${if (cfg.database.type == "postgresql" && cfg.database.socket != null) then cfg.database.socket else cfg.database.host}
       port: ${toString cfg.database.port}
       username: ${cfg.database.user}
       password: #dbpass#
-      ${optionalString (cfg.database.socket != null) "socket: ${cfg.database.socket}"}
+      ${optionalString (cfg.database.type == "mysql2" && cfg.database.socket != null) "socket: ${cfg.database.socket}"}
   '';
 
   configurationYml = pkgs.writeText "configuration.yml" ''
@@ -50,16 +52,15 @@ let
       '';
   });
 
+  mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql2";
+  pgsqlLocal = cfg.database.createLocally && cfg.database.type == "postgresql";
+
 in
 
 {
   options = {
     services.redmine = {
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Enable the Redmine service.";
-      };
+      enable = mkEnableOption "Redmine";
 
       # default to the 4.x series not forcing major version upgrade of those on the 3.x series
       package = mkOption {
@@ -107,7 +108,8 @@ in
         description = ''
           Extra configuration in configuration.yml.
 
-          See https://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration
+          See <link xlink:href="https://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration"/>
+          for details.
         '';
         example = literalExample ''
           email_delivery:
@@ -124,7 +126,8 @@ in
         description = ''
           Extra configuration in additional_environment.rb.
 
-          See https://svn.redmine.org/redmine/trunk/config/additional_environment.rb.example
+          See <link xlink:href="https://svn.redmine.org/redmine/trunk/config/additional_environment.rb.example"/>
+          for details.
         '';
         example = literalExample ''
           config.logger.level = Logger::DEBUG
@@ -169,13 +172,14 @@ in
 
         host = mkOption {
           type = types.str;
-          default = (if cfg.database.socket != null then "localhost" else "127.0.0.1");
+          default = "localhost";
           description = "Database host address.";
         };
 
         port = mkOption {
           type = types.int;
-          default = 3306;
+          default = if cfg.database.type == "postgresql" then 5432 else 3306;
+          defaultText = "3306";
           description = "Database host port.";
         };
 
@@ -213,10 +217,20 @@ in
 
         socket = mkOption {
           type = types.nullOr types.path;
-          default = null;
+          default =
+            if mysqlLocal then "/run/mysqld/mysqld.sock"
+            else if pgsqlLocal then "/run/postgresql"
+            else null;
+          defaultText = "/run/mysqld/mysqld.sock";
           example = "/run/mysqld/mysqld.sock";
           description = "Path to the unix socket file to use for authentication.";
         };
+
+        createLocally = mkOption {
+          type = types.bool;
+          default = true;
+          description = "Create the database and database user locally.";
+        };
       };
     };
   };
@@ -227,12 +241,37 @@ in
       { assertion = cfg.database.passwordFile != null || cfg.database.password != "" || cfg.database.socket != null;
         message = "one of services.redmine.database.socket, services.redmine.database.passwordFile, or services.redmine.database.password must be set";
       }
-      { assertion = cfg.database.socket != null -> (cfg.database.type == "mysql2");
-        message = "Socket authentication is only available for the mysql2 database type";
+      { assertion = cfg.database.createLocally -> cfg.database.user == cfg.user;
+        message = "services.redmine.database.user must be set to ${cfg.user} if services.redmine.database.createLocally is set true";
+      }
+      { assertion = cfg.database.createLocally -> cfg.database.socket != null;
+        message = "services.redmine.database.socket must be set if services.redmine.database.createLocally is set to true";
+      }
+      { assertion = cfg.database.createLocally -> cfg.database.host == "localhost";
+        message = "services.redmine.database.host must be set to localhost if services.redmine.database.createLocally is set to true";
       }
     ];
 
-    environment.systemPackages = [ cfg.package ];
+    services.mysql = mkIf mysqlLocal {
+      enable = true;
+      package = mkDefault pkgs.mariadb;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.database.user;
+          ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
+
+    services.postgresql = mkIf pgsqlLocal {
+      enable = true;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.database.user;
+          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
 
     # create symlinks for the basic directory layout the redmine package expects
     systemd.tmpfiles.rules = [
@@ -259,7 +298,7 @@ in
     ];
 
     systemd.services.redmine = {
-      after = [ "network.target" (if cfg.database.type == "mysql2" then "mysql.service" else "postgresql.service") ];
+      after = [ "network.target" ] ++ optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
       wantedBy = [ "multi-user.target" ];
       environment.RAILS_ENV = "production";
       environment.RAILS_CACHE = "${cfg.stateDir}/cache";
diff --git a/nixos/modules/services/misc/taskserver/default.nix b/nixos/modules/services/misc/taskserver/default.nix
index 07dbee69db0c..8a57277fafe7 100644
--- a/nixos/modules/services/misc/taskserver/default.nix
+++ b/nixos/modules/services/misc/taskserver/default.nix
@@ -411,7 +411,7 @@ in {
         } else {
           cert = "${cfg.pki.manual.server.cert}";
           key = "${cfg.pki.manual.server.key}";
-          crl = "${cfg.pki.manual.server.crl}";
+          ${mapNullable (_: "crl") cfg.pki.manual.server.crl} = "${cfg.pki.manual.server.crl}";
         });
 
         ca.cert = if needToCreateCA then "${cfg.dataDir}/keys/ca.cert"
diff --git a/nixos/modules/services/misc/taskserver/doc.xml b/nixos/modules/services/misc/taskserver/doc.xml
index 5eac8d9ef784..5656bb85b373 100644
--- a/nixos/modules/services/misc/taskserver/doc.xml
+++ b/nixos/modules/services/misc/taskserver/doc.xml
@@ -105,7 +105,7 @@
    Now in order to import the <literal>alice</literal> user to another machine
    <literal>alicebox</literal>, all we need to do is something like this:
 <screen>
-$ ssh server nixos-taskserver user export my-company alice | sh
+<prompt>$ </prompt>ssh server nixos-taskserver user export my-company alice | sh
 </screen>
    Of course, if no SSH daemon is available on the server you can also copy
    &amp; paste it directly into a shell.
diff --git a/nixos/modules/services/misc/tiddlywiki.nix b/nixos/modules/services/misc/tiddlywiki.nix
new file mode 100644
index 000000000000..2adc08f6cfed
--- /dev/null
+++ b/nixos/modules/services/misc/tiddlywiki.nix
@@ -0,0 +1,52 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.tiddlywiki;
+  listenParams = concatStrings (mapAttrsToList (n: v: " '${n}=${toString v}' ") cfg.listenOptions);
+  exe = "${pkgs.nodePackages.tiddlywiki}/lib/node_modules/.bin/tiddlywiki";
+  name = "tiddlywiki";
+  dataDir = "/var/lib/" + name;
+
+in {
+
+  options.services.tiddlywiki = {
+
+    enable = mkEnableOption "TiddlyWiki nodejs server";
+
+    listenOptions = mkOption {
+      type = types.attrs;
+      default = {};
+      example = {
+        credentials = "../credentials.csv";
+        readers="(authenticated)";
+        port = 3456;
+      };
+      description = ''
+        Parameters passed to <literal>--listen</literal> command.
+        Refer to <link xlink:href="https://tiddlywiki.com/#WebServer"/>
+        for details on supported values.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd = {
+      services.tiddlywiki = {
+        description = "TiddlyWiki nodejs server";
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          Type = "simple";
+          Restart = "on-failure";
+          DynamicUser = true;
+          StateDirectory = name;
+          ExecStartPre = "-${exe} ${dataDir} --init server";
+          ExecStart = "${exe} ${dataDir} --listen ${listenParams}";
+        };
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/misc/zoneminder.nix b/nixos/modules/services/misc/zoneminder.nix
index 9c555e8031c4..cf56ae89b399 100644
--- a/nixos/modules/services/misc/zoneminder.nix
+++ b/nixos/modules/services/misc/zoneminder.nix
@@ -50,7 +50,7 @@ let
     ZM_DB_TYPE=mysql
     ZM_DB_HOST=${cfg.database.host}
     ZM_DB_NAME=${cfg.database.name}
-    ZM_DB_USER=${if cfg.database.createLocally then user else cfg.database.username}
+    ZM_DB_USER=${cfg.database.username}
     ZM_DB_PASS=${cfg.database.password}
 
     # Web
@@ -155,6 +155,7 @@ in {
           default = "zmpass";
           description = ''
             Username for accessing the database.
+            Not used if <literal>createLocally</literal> is set.
           '';
         };
       };
@@ -189,6 +190,12 @@ in {
 
   config = lib.mkIf cfg.enable {
 
+    assertions = [
+      { assertion = cfg.database.createLocally -> cfg.database.username == user;
+        message = "services.zoneminder.database.username must be set to ${user} if services.zoneminder.database.createLocally is set true";
+      }
+    ];
+
     environment.etc = {
       "zoneminder/60-defaults.conf".source = defaultsFile;
       "zoneminder/80-nixos.conf".source    = configFile;
@@ -204,10 +211,9 @@ in {
       };
 
       mysql = lib.mkIf cfg.database.createLocally {
+        enable = true;
+        package = lib.mkDefault pkgs.mariadb;
         ensureDatabases = [ cfg.database.name ];
-        initialDatabases = [{
-          inherit (cfg.database) name; schema = "${pkg}/share/zoneminder/db/zm_create.sql";
-        }];
         ensureUsers = [{
           name = cfg.database.username;
           ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
@@ -256,7 +262,7 @@ in {
                   fastcgi_pass ${fcgi.socketType}:${fcgi.socketAddress};
                 }
 
-                location /cache {
+                location /cache/ {
                   alias /var/cache/${dirName};
                 }
 
@@ -315,11 +321,16 @@ in {
           procps
           psmisc
         ];
-        after = [ "mysql.service" "nginx.service" ];
+        after = [ "nginx.service" ] ++ lib.optional cfg.database.createLocally "mysql.service";
         wantedBy = [ "multi-user.target" ];
         restartTriggers = [ defaultsFile configFile ];
-        preStart = lib.mkIf useCustomDir ''
+        preStart = lib.optionalString useCustomDir ''
           install -dm775 -o ${user} -g ${group} ${cfg.storageDir}/{${lib.concatStringsSep "," libDirs}}
+        '' + lib.optionalString cfg.database.createLocally ''
+          if ! test -e "/var/lib/${dirName}/db-created"; then
+            ${config.services.mysql.package}/bin/mysql < ${pkg}/share/zoneminder/db/zm_create.sql
+            touch "/var/lib/${dirName}/db-created"
+          fi
         '';
         serviceConfig = {
           User = user;
diff --git a/nixos/modules/services/monitoring/alerta.nix b/nixos/modules/services/monitoring/alerta.nix
index 8f4258e26ded..34f2d41706a5 100644
--- a/nixos/modules/services/monitoring/alerta.nix
+++ b/nixos/modules/services/monitoring/alerta.nix
@@ -1,4 +1,4 @@
-{ options, config, lib, pkgs, ... }:
+{ config, lib, pkgs, ... }:
 
 with lib;
 
@@ -83,6 +83,10 @@ in
   };
 
   config = mkIf cfg.enable {
+    systemd.tmpfiles.rules = [
+      "d '${cfg.logDir}' - alerta alerta - -"
+    ];
+
     systemd.services.alerta = {
       description = "Alerta Monitoring System";
       wantedBy = [ "multi-user.target" ];
@@ -94,12 +98,7 @@ in
         ExecStart = "${pkgs.python36Packages.alerta-server}/bin/alertad run --port ${toString cfg.port} --host ${cfg.bind}";
         User = "alerta";
         Group = "alerta";
-        PermissionsStartOnly = true;
       };
-      preStart = ''
-        mkdir -p ${cfg.logDir}
-        chown alerta:alerta ${cfg.logDir}
-      '';
     };
 
     environment.systemPackages = [ pkgs.python36Packages.alerta ];
diff --git a/nixos/modules/services/monitoring/grafana-reporter.nix b/nixos/modules/services/monitoring/grafana-reporter.nix
index 827cf6322cfd..b5a78e4583e1 100644
--- a/nixos/modules/services/monitoring/grafana-reporter.nix
+++ b/nixos/modules/services/monitoring/grafana-reporter.nix
@@ -1,4 +1,4 @@
-{ options, config, lib, pkgs, ... }:
+{ config, lib, pkgs, ... }:
 
 with lib;
 
diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix
index 5d3f2e6ac28f..c2f6b585d493 100644
--- a/nixos/modules/services/monitoring/grafana.nix
+++ b/nixos/modules/services/monitoring/grafana.nix
@@ -552,6 +552,8 @@ in {
       description = "Grafana user";
       home = cfg.dataDir;
       createHome = true;
+      group = "grafana";
     };
+    users.groups.grafana = {};
   };
 }
diff --git a/nixos/modules/services/monitoring/kapacitor.nix b/nixos/modules/services/monitoring/kapacitor.nix
index a4bdfa8f8053..0f236d25c9ed 100644
--- a/nixos/modules/services/monitoring/kapacitor.nix
+++ b/nixos/modules/services/monitoring/kapacitor.nix
@@ -1,4 +1,4 @@
-{ options, config, lib, pkgs, ... }:
+{ config, lib, pkgs, ... }:
 
 with lib;
 
@@ -163,6 +163,10 @@ in
   config = mkIf cfg.enable {
     environment.systemPackages = [ pkgs.kapacitor ];
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' - ${cfg.user} ${cfg.group} - -"
+    ];
+
     systemd.services.kapacitor = {
       description = "Kapacitor Real-Time Stream Processing Engine";
       wantedBy = [ "multi-user.target" ];
@@ -171,12 +175,7 @@ in
         ExecStart = "${pkgs.kapacitor}/bin/kapacitord -config ${kapacitorConf}";
         User = "kapacitor";
         Group = "kapacitor";
-        PermissionsStartOnly = true;
       };
-      preStart = ''
-        mkdir -p ${cfg.dataDir}
-        chown ${cfg.user}:${cfg.group} ${cfg.dataDir}
-      '';
     };
 
     users.users.kapacitor = {
diff --git a/nixos/modules/services/monitoring/loki.nix b/nixos/modules/services/monitoring/loki.nix
new file mode 100644
index 000000000000..4d11360d07e9
--- /dev/null
+++ b/nixos/modules/services/monitoring/loki.nix
@@ -0,0 +1,112 @@
+{ config, lib, pkgs, ... }:
+
+let
+  inherit (lib) escapeShellArgs literalExample mkEnableOption mkIf mkOption types;
+
+  cfg = config.services.loki;
+
+  prettyJSON = conf:
+    pkgs.runCommand "loki-config.json" { } ''
+      echo '${builtins.toJSON conf}' | ${pkgs.jq}/bin/jq 'del(._module)' > $out
+    '';
+
+in {
+  options.services.loki = {
+    enable = mkEnableOption "loki";
+
+    user = mkOption {
+      type = types.str;
+      default = "loki";
+      description = ''
+        User under which the Loki service runs.
+      '';
+    };
+
+    group = mkOption {
+      type = types.str;
+      default = "loki";
+      description = ''
+        Group under which the Loki service runs.
+      '';
+    };
+
+    dataDir = mkOption {
+      type = types.path;
+      default = "/var/lib/loki";
+      description = ''
+        Specify the directory for Loki.
+      '';
+    };
+
+    configuration = mkOption {
+      type = types.attrs;
+      default = {};
+      description = ''
+        Specify the configuration for Loki in Nix.
+      '';
+    };
+
+    configFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = ''
+        Specify a configuration file that Loki should use.
+      '';
+    };
+
+    extraFlags = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = literalExample [ "--server.http-listen-port=3101" ];
+      description = ''
+        Specify a list of additional command line flags,
+        which get escaped and are then passed to Loki.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [{
+      assertion = (
+        (cfg.configuration == {} -> cfg.configFile != null) &&
+        (cfg.configFile != null -> cfg.configuration == {})
+      );
+      message  = ''
+        Please specify either
+        'services.loki.configuration' or
+        'services.loki.configFile'.
+      '';
+    }];
+
+    users.groups.${cfg.group} = { };
+    users.users.${cfg.user} = {
+      description = "Loki Service User";
+      group = cfg.group;
+      home = cfg.dataDir;
+      createHome = true;
+      isSystemUser = true;
+    };
+
+    systemd.services.loki = {
+      description = "Loki Service Daemon";
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = let
+        conf = if cfg.configFile == null
+               then prettyJSON cfg.configuration
+               else cfg.configFile;
+      in
+      {
+        ExecStart = "${pkgs.grafana-loki}/bin/loki --config.file=${conf} ${escapeShellArgs cfg.extraFlags}";
+        User = cfg.user;
+        Restart = "always";
+        PrivateTmp = true;
+        ProtectHome = true;
+        ProtectSystem = "full";
+        DecvicePolicy = "closed";
+        NoNewPrivileges = true;
+        WorkingDirectory = cfg.dataDir;
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/nagios.nix b/nixos/modules/services/monitoring/nagios.nix
index 7f65236ed3d3..6a3b97769462 100644
--- a/nixos/modules/services/monitoring/nagios.nix
+++ b/nixos/modules/services/monitoring/nagios.nix
@@ -36,7 +36,7 @@ let
 
       # Uid/gid that the daemon runs under.
       nagios_user=nagios
-      nagios_group=nogroup
+      nagios_group=nagios
 
       # Misc. options.
       illegal_macro_output_chars=`~$&|'"<>
@@ -58,9 +58,7 @@ let
 
       <Directory "${pkgs.nagios}/sbin">
         Options ExecCGI
-        AllowOverride None
-        Order allow,deny
-        Allow from all
+        Require all granted
         SetEnv NAGIOS_CGI_CONFIG ${cfg.cgiConfigFile}
       </Directory>
 
@@ -68,9 +66,7 @@ let
 
       <Directory "${pkgs.nagios}/share">
         Options None
-        AllowOverride None
-        Order allow,deny
-        Allow from all
+        Require all granted
       </Directory>
     '';
 
@@ -149,9 +145,11 @@ in
       description = "Nagios user ";
       uid         = config.ids.uids.nagios;
       home        = nagiosState;
-      createHome  = true;
+      group       = "nagios";
     };
 
+    users.groups.nagios = { };
+
     # This isn't needed, it's just so that the user can type "nagiostats
     # -c /etc/nagios.cfg".
     environment.etc = [
@@ -169,16 +167,13 @@ in
 
       serviceConfig = {
         User = "nagios";
+        Group = "nagios";
         Restart = "always";
         RestartSec = 2;
-        PermissionsStartOnly = true;
+        LogsDirectory = "nagios";
+        StateDirectory = "nagios";
       };
 
-      preStart = ''
-        mkdir -m 0755 -p ${nagiosState} ${nagiosLogDir}
-        chown nagios ${nagiosState} ${nagiosLogDir}
-      '';
-
       script = ''
         for i in ${toString cfg.plugins}; do
           export PATH=$i/bin:$i/sbin:$i/libexec:$PATH
diff --git a/nixos/modules/services/monitoring/netdata.nix b/nixos/modules/services/monitoring/netdata.nix
index a49555cf677f..f9b7550af23a 100644
--- a/nixos/modules/services/monitoring/netdata.nix
+++ b/nixos/modules/services/monitoring/netdata.nix
@@ -8,6 +8,7 @@ let
   wrappedPlugins = pkgs.runCommand "wrapped-plugins" { preferLocalBuild = true; } ''
     mkdir -p $out/libexec/netdata/plugins.d
     ln -s /run/wrappers/bin/apps.plugin $out/libexec/netdata/plugins.d/apps.plugin
+    ln -s /run/wrappers/bin/freeipmi.plugin $out/libexec/netdata/plugins.d/freeipmi.plugin
   '';
 
   plugins = [
@@ -140,12 +141,18 @@ in {
       path = (with pkgs; [ gawk curl ]) ++ lib.optional cfg.python.enable
         (pkgs.python3.withPackages cfg.python.extraPackages);
       serviceConfig = {
-        User = cfg.user;
-        Group = cfg.group;
         Environment="PYTHONPATH=${pkgs.netdata}/libexec/netdata/python.d/python_modules";
-        PermissionsStartOnly = true;
-        ExecStart = "${pkgs.netdata}/bin/netdata -D -c ${configFile}";
+        ExecStart = "${pkgs.netdata}/bin/netdata -P /run/netdata/netdata.pid -D -c ${configFile}";
+        ExecReload = "${pkgs.utillinux}/bin/kill -s HUP -s USR1 -s USR2 $MAINPID";
         TimeoutStopSec = 60;
+        # User and group
+        User = cfg.user;
+        Group = cfg.group;
+        # Runtime directory and mode
+        RuntimeDirectory = "netdata";
+        RuntimeDirectoryMode = "0755";
+        # Performance
+        LimitNOFILE = "30000";
       };
     };
 
@@ -157,6 +164,18 @@ in {
       permissions = "u+rx,g+rx,o-rwx";
     };
 
+    security.wrappers."freeipmi.plugin" = {
+      source = "${pkgs.netdata}/libexec/netdata/plugins.d/freeipmi.plugin.org";
+      capabilities = "cap_dac_override,cap_fowner+ep";
+      owner = cfg.user;
+      group = cfg.group;
+      permissions = "u+rx,g+rx,o-rwx";
+    };
+
+    security.pam.loginLimits = [
+      { domain = "netdata"; type = "soft"; item = "nofile"; value = "10000"; }
+      { domain = "netdata"; type = "hard"; item = "nofile"; value = "30000"; }
+    ];
 
     users.users = optional (cfg.user == defaultUser) {
       name = defaultUser;
diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix
index 20e7eba43412..2ab8910ff9db 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters.nix
@@ -1,8 +1,10 @@
-{ config, pkgs, lib, ... }:
-
-with lib;
+{ config, pkgs, lib, options, ... }:
 
 let
+  inherit (lib) concatStrings foldl foldl' genAttrs literalExample maintainers
+                mapAttrsToList mkDefault mkEnableOption mkIf mkMerge mkOption
+                optional types;
+
   cfg = config.services.prometheus.exporters;
 
   # each attribute in `exporterOpts` is expected to have specified:
@@ -17,25 +19,30 @@ let
   #  Note that `extraOpts` is optional, but a script for the exporter's
   #  systemd service must be provided by specifying either
   #  `serviceOpts.script` or `serviceOpts.serviceConfig.ExecStart`
-  exporterOpts = {
-    blackbox  = import ./exporters/blackbox.nix  { inherit config lib pkgs; };
-    collectd  = import ./exporters/collectd.nix  { inherit config lib pkgs; };
-    dnsmasq   = import ./exporters/dnsmasq.nix   { inherit config lib pkgs; };
-    dovecot   = import ./exporters/dovecot.nix   { inherit config lib pkgs; };
-    fritzbox  = import ./exporters/fritzbox.nix  { inherit config lib pkgs; };
-    json      = import ./exporters/json.nix      { inherit config lib pkgs; };
-    minio     = import ./exporters/minio.nix     { inherit config lib pkgs; };
-    nginx     = import ./exporters/nginx.nix     { inherit config lib pkgs; };
-    node      = import ./exporters/node.nix      { inherit config lib pkgs; };
-    postfix   = import ./exporters/postfix.nix   { inherit config lib pkgs; };
-    snmp      = import ./exporters/snmp.nix      { inherit config lib pkgs; };
-    surfboard = import ./exporters/surfboard.nix { inherit config lib pkgs; };
-    tor       = import ./exporters/tor.nix       { inherit config lib pkgs; };
-    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; };
-  };
+
+  exporterOpts = genAttrs [
+    "bind"
+    "blackbox"
+    "collectd"
+    "dnsmasq"
+    "dovecot"
+    "fritzbox"
+    "json"
+    "mail"
+    "minio"
+    "nginx"
+    "node"
+    "postfix"
+    "postgres"
+    "snmp"
+    "surfboard"
+    "tor"
+    "unifi"
+    "varnish"
+    "wireguard"
+  ] (name:
+    import (./. + "/exporters/${name}.nix") { inherit config lib pkgs options; }
+  );
 
   mkExporterOpts = ({ name, port }: {
     enable = mkEnableOption "the prometheus ${name} exporter";
@@ -81,7 +88,7 @@ let
     };
     user = mkOption {
       type = types.str;
-      default = "nobody";
+      default = "${name}-exporter";
       description = ''
         User name under which the ${name} exporter shall be run.
         Has no effect when <option>systemd.services.prometheus-${name}-exporter.serviceConfig.DynamicUser</option> is true.
@@ -89,7 +96,7 @@ let
     };
     group = mkOption {
       type = types.str;
-      default = "nobody";
+      default = "${name}-exporter";
       description = ''
         Group under which the ${name} exporter shall be run.
         Has no effect when <option>systemd.services.prometheus-${name}-exporter.serviceConfig.DynamicUser</option> is true.
@@ -97,9 +104,10 @@ let
     };
   });
 
-  mkSubModule = { name, port, extraOpts, ... }: {
+  mkSubModule = { name, port, extraOpts, imports }: {
     ${name} = mkOption {
       type = types.submodule {
+        inherit imports;
         options = (mkExporterOpts {
           inherit name port;
         } // extraOpts);
@@ -112,13 +120,30 @@ let
   mkSubModules = (foldl' (a: b: a//b) {}
     (mapAttrsToList (name: opts: mkSubModule {
       inherit name;
-      inherit (opts) port serviceOpts;
+      inherit (opts) port;
       extraOpts = opts.extraOpts or {};
+      imports = opts.imports or [];
     }) exporterOpts)
   );
 
   mkExporterConf = { name, conf, serviceOpts }:
+    let
+      enableDynamicUser = serviceOpts.serviceConfig.DynamicUser or true;
+    in
     mkIf conf.enable {
+      warnings = conf.warnings or [];
+      users.users = (mkIf (conf.user == "${name}-exporter" && !enableDynamicUser) {
+        "${name}-exporter" = {
+          description = ''
+            Prometheus ${name} exporter service user
+          '';
+          isSystemUser = true;
+          inherit (conf) group;
+        };
+      });
+      users.groups = (mkIf (conf.group == "${name}-exporter" && !enableDynamicUser) {
+        "${name}-exporter" = {};
+      });
       networking.firewall.extraCommands = mkIf conf.openFirewall (concatStrings [
         "ip46tables -A nixos-fw ${conf.firewallFilter} "
         "-m comment --comment ${name}-exporter -j nixos-fw-accept"
@@ -129,7 +154,8 @@ let
         serviceConfig.Restart = mkDefault "always";
         serviceConfig.PrivateTmp = mkDefault true;
         serviceConfig.WorkingDirectory = mkDefault /tmp;
-      } serviceOpts ] ++ optional (!(serviceOpts.serviceConfig.DynamicUser or false)) {
+        serviceConfig.DynamicUser = mkDefault enableDynamicUser;
+      } serviceOpts ] ++ optional (!enableDynamicUser) {
         serviceConfig.User = conf.user;
         serviceConfig.Group = conf.group;
       });
@@ -154,13 +180,19 @@ in
   };
 
   config = mkMerge ([{
-    assertions = [{
+    assertions = [ {
       assertion = (cfg.snmp.configurationPath == null) != (cfg.snmp.configuration == null);
       message = ''
         Please ensure you have either `services.prometheus.exporters.snmp.configuration'
           or `services.prometheus.exporters.snmp.configurationPath' set!
       '';
-    }];
+    } {
+      assertion = (cfg.mail.configFile == null) != (cfg.mail.configuration == {});
+      message = ''
+        Please specify either 'services.prometheus.exporters.mail.configuration'
+          or 'services.prometheus.exporters.mail.configFile'.
+      '';
+    } ];
   }] ++ [(mkIf config.services.minio.enable {
     services.prometheus.exporters.minio.minioAddress  = mkDefault "http://localhost:9000";
     services.prometheus.exporters.minio.minioAccessKey = mkDefault config.services.minio.accessKey;
diff --git a/nixos/modules/services/monitoring/prometheus/exporters.xml b/nixos/modules/services/monitoring/prometheus/exporters.xml
index 7a0a1bdf2c14..c2d4b05996a4 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters.xml
+++ b/nixos/modules/services/monitoring/prometheus/exporters.xml
@@ -17,7 +17,7 @@
    exporter</link>, it provides hardware and OS metrics from the host it's
    running on. The exporter could be configured as follows:
 <programlisting>
-  services.promtheus.exporters.node = {
+  services.prometheus.exporters.node = {
     enable = true;
     enabledCollectors = [
       "logind"
@@ -112,65 +112,67 @@
      directory, which will be called postfix.nix and contains all exporter
      specific options and configuration:
 <programlisting>
-        # nixpgs/nixos/modules/services/prometheus/exporters/postfix.nix
-        { config, lib, pkgs }:
+# nixpgs/nixos/modules/services/prometheus/exporters/postfix.nix
+{ config, lib, pkgs, options }:
 
-        with lib;
+with lib;
 
-        let
-          # for convenience we define cfg here
-          cfg = config.services.prometheus.exporters.postfix;
-        in
-        {
-          port = 9154; # The postfix exporter listens on this port by default
+let
+  # for convenience we define cfg here
+  cfg = config.services.prometheus.exporters.postfix;
+in
+{
+  port = 9154; # The postfix exporter listens on this port by default
 
-          # `extraOpts` is an attribute set which contains additional options
-          # (and optional overrides for default options).
-          # Note that this attribute is optional.
-          extraOpts = {
-            telemetryPath = mkOption {
-              type = types.str;
-              default = "/metrics";
-              description = ''
-                Path under which to expose metrics.
-              '';
-            };
-            logfilePath = mkOption {
-              type = types.path;
-              default = /var/log/postfix_exporter_input.log;
-              example = /var/log/mail.log;
-              description = ''
-                Path where Postfix writes log entries.
-                This file will be truncated by this exporter!
-              '';
-            };
-            showqPath = mkOption {
-              type = types.path;
-              default = /var/spool/postfix/public/showq;
-              example = /var/lib/postfix/queue/public/showq;
-              description = ''
-                Path at which Postfix places its showq socket.
-              '';
-            };
-          };
+  # `extraOpts` is an attribute set which contains additional options
+  # (and optional overrides for default options).
+  # Note that this attribute is optional.
+  extraOpts = {
+    telemetryPath = mkOption {
+      type = types.str;
+      default = "/metrics";
+      description = ''
+        Path under which to expose metrics.
+      '';
+    };
+    logfilePath = mkOption {
+      type = types.path;
+      default = /var/log/postfix_exporter_input.log;
+      example = /var/log/mail.log;
+      description = ''
+        Path where Postfix writes log entries.
+        This file will be truncated by this exporter!
+      '';
+    };
+    showqPath = mkOption {
+      type = types.path;
+      default = /var/spool/postfix/public/showq;
+      example = /var/lib/postfix/queue/public/showq;
+      description = ''
+        Path at which Postfix places its showq socket.
+      '';
+    };
+  };
 
-          # `serviceOpts` is an attribute set which contains configuration
-          # for the exporter's systemd service. One of
-          # `serviceOpts.script` and `serviceOpts.serviceConfig.ExecStart`
-          # has to be specified here. This will be merged with the default
-          # service confiuration.
-          serviceOpts = {
-            serviceConfig = {
-              ExecStart = ''
-                ${pkgs.prometheus-postfix-exporter}/bin/postfix_exporter \
-                  --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
-                  --web.telemetry-path ${cfg.telemetryPath} \
-                  ${concatStringsSep " \\\n  " cfg.extraFlags}
-              '';
-            };
-          };
-        }
-      </programlisting>
+  # `serviceOpts` is an attribute set which contains configuration
+  # for the exporter's systemd service. One of
+  # `serviceOpts.script` and `serviceOpts.serviceConfig.ExecStart`
+  # has to be specified here. This will be merged with the default
+  # service confiuration.
+  # Note that by default 'DynamicUser' is 'true'.
+  serviceOpts = {
+    serviceConfig = {
+      DynamicUser = false;
+      ExecStart = ''
+        ${pkgs.prometheus-postfix-exporter}/bin/postfix_exporter \
+          --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+          --web.telemetry-path ${cfg.telemetryPath} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
+</programlisting>
     </para>
    </listitem>
    <listitem>
@@ -184,4 +186,42 @@
    </listitem>
   </itemizedlist>
  </section>
+ <section xml:id="module-services-prometheus-exporters-update-exporter-module">
+  <title>Updating an exporter module</title>
+   <para>
+     Should an exporter option change at some point, it is possible to add
+     information about the change to the exporter definition similar to
+     <literal>nixpkgs/nixos/modules/rename.nix</literal>:
+<programlisting>
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.nginx;
+in
+{
+  port = 9113;
+  extraOpts = {
+    # additional module options
+    # ...
+  };
+  serviceOpts = {
+    # service configuration
+    # ...
+  };
+  imports = [
+    # 'services.prometheus.exporters.nginx.telemetryEndpoint' -> 'services.prometheus.exporters.nginx.telemetryPath'
+    (mkRenamedOptionModule [ "telemetryEndpoint" ] [ "telemetryPath" ])
+
+    # removed option 'services.prometheus.exporters.nginx.insecure'
+    (mkRemovedOptionModule [ "insecure" ] ''
+      This option was replaced by 'prometheus.exporters.nginx.sslVerify' which defaults to true.
+    '')
+    ({ options.warnings = options.warnings; })
+  ];
+}
+</programlisting>
+    </para>
+  </section>
 </chapter>
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/bind.nix b/nixos/modules/services/monitoring/prometheus/exporters/bind.nix
index a9746c4d65d5..972632b5a24a 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/bind.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/bind.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -39,7 +39,6 @@ in
   };
   serviceOpts = {
     serviceConfig = {
-      DynamicUser = true;
       ExecStart = ''
         ${pkgs.prometheus-bind-exporter}/bin/bind_exporter \
           -web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix b/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix
index d09d1c4f3663..f69b389760f7 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -18,7 +18,6 @@ in
   serviceOpts = {
     serviceConfig = {
       AmbientCapabilities = [ "CAP_NET_RAW" ]; # for ping probes
-      DynamicUser = true;
       ExecStart = ''
         ${pkgs.prometheus-blackbox-exporter}/bin/blackbox_exporter \
           --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix b/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix
index 0eba3527162d..1cc346418091 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -64,7 +64,6 @@ in
     '' else "";
   in {
     serviceConfig = {
-      DynamicUser = true;
       ExecStart = ''
         ${pkgs.prometheus-collectd-exporter}/bin/collectd_exporter \
           -log.format ${cfg.logFormat} \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix b/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix
index b1fab85109af..e9fa26cb1f5a 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -26,7 +26,6 @@ in
   };
   serviceOpts = {
     serviceConfig = {
-      DynamicUser = true;
       ExecStart = ''
         ${pkgs.prometheus-dnsmasq-exporter}/bin/dnsmasq_exporter \
           --listen ${cfg.listenAddress}:${toString cfg.port} \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix b/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix
index c47e87a3dc35..a01074758ff8 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -39,8 +39,8 @@ in
             mail_plugins = $mail_plugins old_stats
             service old-stats {
               unix_listener old-stats {
-                user = nobody
-                group = nobody
+                user = dovecot-exporter
+                group = dovecot-exporter
               }
             }
           ''';
@@ -59,6 +59,7 @@ in
   };
   serviceOpts = {
     serviceConfig = {
+      DynamicUser = false;
       ExecStart = ''
         ${pkgs.prometheus-dovecot-exporter}/bin/dovecot_exporter \
           --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix b/nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix
index 530206681d36..9526597b8c96 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -26,7 +26,6 @@ in
   };
   serviceOpts = {
     serviceConfig = {
-      DynamicUser = true;
       ExecStart = ''
         ${pkgs.prometheus-fritzbox-exporter}/bin/exporter \
           -listen-address ${cfg.listenAddress}:${toString cfg.port} \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/json.nix b/nixos/modules/services/monitoring/prometheus/exporters/json.nix
index a5494e85e016..82a55bafc982 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/json.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/json.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -24,7 +24,6 @@ in
   };
   serviceOpts = {
     serviceConfig = {
-      DynamicUser = true;
       ExecStart = ''
         ${pkgs.prometheus-json-exporter}/bin/prometheus-json-exporter \
           --port ${toString cfg.port} \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/mail.nix b/nixos/modules/services/monitoring/prometheus/exporters/mail.nix
new file mode 100644
index 000000000000..7d8c6fb61404
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/mail.nix
@@ -0,0 +1,157 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.mail;
+
+  configurationFile = pkgs.writeText "prometheus-mail-exporter.conf" (builtins.toJSON (
+    # removes the _module attribute, null values and converts attrNames to lowercase
+    mapAttrs' (name: value:
+      if name == "servers"
+      then nameValuePair (toLower name)
+        ((map (srv: (mapAttrs' (n: v: nameValuePair (toLower n) v)
+          (filterAttrs (n: v: !(n == "_module" || v == null)) srv)
+        ))) value)
+      else nameValuePair (toLower name) value
+    ) (filterAttrs (n: _: !(n == "_module")) cfg.configuration)
+  ));
+
+  serverOptions.options = {
+    name = mkOption {
+      type = types.str;
+      description = ''
+        Value for label 'configname' which will be added to all metrics.
+      '';
+    };
+    server = mkOption {
+      type = types.str;
+      description = ''
+        Hostname of the server that should be probed.
+      '';
+    };
+    port = mkOption {
+      type = types.int;
+      example = 587;
+      description = ''
+        Port to use for SMTP.
+      '';
+    };
+    from = mkOption {
+      type = types.str;
+      example = "exporteruser@domain.tld";
+      description = ''
+        Content of 'From' Header for probing mails.
+      '';
+    };
+    to = mkOption {
+      type = types.str;
+      example = "exporteruser@domain.tld";
+      description = ''
+        Content of 'To' Header for probing mails.
+      '';
+    };
+    detectionDir = mkOption {
+      type = types.path;
+      example = "/var/spool/mail/exporteruser/new";
+      description = ''
+        Directory in which new mails for the exporter user are placed.
+        Note that this needs to exist when the exporter starts.
+      '';
+    };
+    login = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "exporteruser@domain.tld";
+      description = ''
+        Username to use for SMTP authentication.
+      '';
+    };
+    passphrase = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        Password to use for SMTP authentication.
+      '';
+    };
+  };
+
+  exporterOptions.options = {
+    monitoringInterval = mkOption {
+      type = types.str;
+      example = "10s";
+      description = ''
+        Time interval between two probe attempts.
+      '';
+    };
+    mailCheckTimeout = mkOption {
+      type = types.str;
+      description = ''
+        Timeout until mails are considered "didn't make it".
+      '';
+    };
+    disableFileDelition = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Disables the exporter's function to delete probing mails.
+      '';
+    };
+    servers = mkOption {
+      type = types.listOf (types.submodule serverOptions);
+      default = [];
+      example = literalExample ''
+        [ {
+          name = "testserver";
+          server = "smtp.domain.tld";
+          port = 587;
+          from = "exporteruser@domain.tld";
+          to = "exporteruser@domain.tld";
+          detectionDir = "/path/to/Maildir/new";
+        } ]
+      '';
+      description = ''
+        List of servers that should be probed.
+      '';
+    };
+  };
+in
+{
+  port = 9225;
+  extraOpts = {
+    configFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = ''
+        Specify the mailexporter configuration file to use.
+      '';
+    };
+    configuration = mkOption {
+      type = types.submodule exporterOptions;
+      default = {};
+      description = ''
+        Specify the mailexporter configuration file to use.
+      '';
+    };
+    telemetryPath = mkOption {
+      type = types.str;
+      default = "/metrics";
+      description = ''
+        Path under which to expose metrics.
+      '';
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      DynamicUser = false;
+      ExecStart = ''
+        ${pkgs.prometheus-mail-exporter}/bin/mailexporter \
+          --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+          --config.file ${
+            if cfg.configuration != {} then configurationFile else cfg.configFile
+          } \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/minio.nix b/nixos/modules/services/monitoring/prometheus/exporters/minio.nix
index 3cc4ffdbc8fd..ab3e3d7d5d50 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/minio.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/minio.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -50,7 +50,6 @@ in
   };
   serviceOpts = {
     serviceConfig = {
-      DynamicUser = true;
       ExecStart = ''
         ${pkgs.prometheus-minio-exporter}/bin/minio-exporter \
           -web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix b/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix
index 431dd8b4ead7..554377df37ba 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -16,32 +16,39 @@ in
         Can be enabled with services.nginx.statusPage = true.
       '';
     };
-    telemetryEndpoint = mkOption {
+    telemetryPath = mkOption {
       type = types.str;
       default = "/metrics";
       description = ''
         Path under which to expose metrics.
       '';
     };
-    insecure = mkOption {
+    sslVerify = mkOption {
       type = types.bool;
       default = true;
       description = ''
-        Ignore server certificate if using https.
+        Whether to perform certificate verification for https.
       '';
     };
+
   };
   serviceOpts = {
     serviceConfig = {
-      DynamicUser = true;
       ExecStart = ''
-        ${pkgs.prometheus-nginx-exporter}/bin/nginx_exporter \
-          --nginx.scrape_uri '${cfg.scrapeUri}' \
-          --telemetry.address ${cfg.listenAddress}:${toString cfg.port} \
-          --telemetry.endpoint ${cfg.telemetryEndpoint} \
-          --insecure ${toString cfg.insecure} \
+        ${pkgs.prometheus-nginx-exporter}/bin/nginx-prometheus-exporter \
+          --nginx.scrape-uri '${cfg.scrapeUri}' \
+          --nginx.ssl-verify ${toString cfg.sslVerify} \
+          --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+          --web.telemetry-path ${cfg.telemetryPath} \
           ${concatStringsSep " \\\n  " cfg.extraFlags}
       '';
     };
   };
+  imports = [
+    (mkRenamedOptionModule [ "telemetryEndpoint" ] [ "telemetryPath" ])
+    (mkRemovedOptionModule [ "insecure" ] ''
+      This option was replaced by 'prometheus.exporters.nginx.sslVerify'.
+    '')
+    ({ options.warnings = options.warnings; })
+  ];
 }
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/node.nix b/nixos/modules/services/monitoring/prometheus/exporters/node.nix
index ee7bf39f199a..7e394e8463e0 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/node.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/node.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -27,13 +27,13 @@ in
   };
   serviceOpts = {
     serviceConfig = {
+      DynamicUser = false;
       RuntimeDirectory = "prometheus-node-exporter";
       ExecStart = ''
         ${pkgs.prometheus-node-exporter}/bin/node_exporter \
           ${concatMapStringsSep " " (x: "--collector." + x) cfg.enabledCollectors} \
           ${concatMapStringsSep " " (x: "--no-collector." + x) cfg.disabledCollectors} \
-          --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
-          ${concatStringsSep " \\\n  " cfg.extraFlags}
+          --web.listen-address ${cfg.listenAddress}:${toString cfg.port} ${concatStringsSep " " cfg.extraFlags}
       '';
     };
   };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix b/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix
index efe78ebcba86..f40819e826b0 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -62,6 +62,7 @@ in
   };
   serviceOpts = {
     serviceConfig = {
+      DynamicUser = false;
       ExecStart = ''
         ${pkgs.prometheus-postfix-exporter}/bin/postfix_exporter \
           --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix b/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix
new file mode 100644
index 000000000000..1ece73a1159a
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix
@@ -0,0 +1,47 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.postgres;
+in
+{
+  port = 9187;
+  extraOpts = {
+    telemetryPath = mkOption {
+      type = types.str;
+      default = "/metrics";
+      description = ''
+        Path under which to expose metrics.
+      '';
+    };
+    dataSourceName = mkOption {
+      type = types.str;
+      default = "user=postgres database=postgres host=/run/postgresql sslmode=disable";
+      example = "postgresql://username:password@localhost:5432/postgres?sslmode=disable";
+      description = ''
+        Accepts PostgreSQL URI form and key=value form arguments.
+      '';
+    };
+    runAsLocalSuperUser = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to run the exporter as the local 'postgres' super user.
+      '';
+    };
+  };
+  serviceOpts = {
+    environment.DATA_SOURCE_NAME = cfg.dataSourceName;
+    serviceConfig = {
+      DynamicUser = false;
+      User = mkIf cfg.runAsLocalSuperUser (mkForce "postgres");
+      ExecStart = ''
+        ${pkgs.prometheus-postgres-exporter}/bin/postgres_exporter \
+          --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+          --web.telemetry-path ${cfg.telemetryPath} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix b/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
index 0d9194124325..fe7ae8a8ac90 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -57,7 +57,6 @@ in
                  else "${pkgs.writeText "snmp-eporter-conf.yml" (builtins.toJSON cfg.configuration)}";
     in {
     serviceConfig = {
-      DynamicUser = true;
       ExecStart = ''
         ${pkgs.prometheus-snmp-exporter.bin}/bin/snmp_exporter \
           --config.file=${configFile} \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/surfboard.nix b/nixos/modules/services/monitoring/prometheus/exporters/surfboard.nix
index 715dba06a3dc..81c5c70ed93f 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/surfboard.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/surfboard.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -20,7 +20,6 @@ in
     description = "Prometheus exporter for surfboard cable modem";
     unitConfig.Documentation = "https://github.com/ipstatic/surfboard_exporter";
     serviceConfig = {
-      DynamicUser = true;
       ExecStart = ''
         ${pkgs.prometheus-surfboard-exporter}/bin/surfboard_exporter \
           --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/tor.nix b/nixos/modules/services/monitoring/prometheus/exporters/tor.nix
index e0ae83802425..36c473677efa 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/tor.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/tor.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -26,7 +26,6 @@ in
   };
   serviceOpts = {
     serviceConfig = {
-      DynamicUser = true;
       ExecStart = ''
         ${pkgs.prometheus-tor-exporter}/bin/prometheus-tor-exporter \
           -b ${cfg.listenAddress} \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/unifi.nix b/nixos/modules/services/monitoring/prometheus/exporters/unifi.nix
index 011dcbe208e4..9aa0f1b85aac 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/unifi.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/unifi.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -51,7 +51,6 @@ in
   };
   serviceOpts = {
     serviceConfig = {
-      DynamicUser = true;
       ExecStart = ''
         ${pkgs.prometheus-unifi-exporter}/bin/unifi_exporter \
           -telemetry.addr ${cfg.listenAddress}:${toString cfg.port} \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix b/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix
index aaed76175b84..12153fa021ec 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -68,8 +68,8 @@ in
   serviceOpts = {
     path = [ pkgs.varnish ];
     serviceConfig = {
-      DynamicUser = true;
       RestartSec = mkDefault 1;
+      DynamicUser = false;
       ExecStart = ''
         ${pkgs.prometheus-varnish-exporter}/bin/prometheus_varnish_exporter \
           --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix b/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix
index c5b84e574b8d..8ae2c927b58c 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -23,20 +23,39 @@ in {
         to set the peers up.
       '';
     };
+
+    singleSubnetPerField = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        By default, all allowed IPs and subnets are comma-separated in the
+        <literal>allowed_ips</literal> field. With this option enabled,
+        a single IP and subnet will be listed in fields like <literal>allowed_ip_0</literal>,
+        <literal>allowed_ip_1</literal> and so on.
+      '';
+    };
+
+    withRemoteIp = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether or not the remote IP of a WireGuard peer should be exposed via prometheus.
+      '';
+    };
   };
   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" ];
+      ExecStart = ''
+        ${pkgs.prometheus-wireguard-exporter}/bin/prometheus_wireguard_exporter \
+          -p ${toString cfg.port} \
+          ${optionalString cfg.verbose "-v"} \
+          ${optionalString cfg.singleSubnetPerField "-s"} \
+          ${optionalString cfg.withRemoteIp "-r"} \
+          ${optionalString (cfg.wireguardConfig != null) "-n ${cfg.wireguardConfig}"}
+      '';
     };
   };
 }
diff --git a/nixos/modules/services/monitoring/riemann-dash.nix b/nixos/modules/services/monitoring/riemann-dash.nix
index 7eb4d888b0cc..16eb83008509 100644
--- a/nixos/modules/services/monitoring/riemann-dash.nix
+++ b/nixos/modules/services/monitoring/riemann-dash.nix
@@ -59,18 +59,20 @@ in {
       group = "riemanndash";
     };
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' - riemanndash riemanndash - -"
+    ];
+
     systemd.services.riemann-dash = {
       wantedBy = [ "multi-user.target" ];
       wants = [ "riemann.service" ];
       after = [ "riemann.service" ];
       preStart = ''
-        mkdir -p ${cfg.dataDir}/config
-        chown -R riemanndash:riemanndash ${cfg.dataDir}
+        mkdir -p '${cfg.dataDir}/config'
       '';
       serviceConfig = {
         User = "riemanndash";
         ExecStart = "${launcher}/bin/riemann-dash";
-        PermissionsStartOnly = true;
       };
     };
 
diff --git a/nixos/modules/services/monitoring/riemann-tools.nix b/nixos/modules/services/monitoring/riemann-tools.nix
index 4e8832dadc5e..9c400a1e3e46 100644
--- a/nixos/modules/services/monitoring/riemann-tools.nix
+++ b/nixos/modules/services/monitoring/riemann-tools.nix
@@ -54,7 +54,6 @@ in {
       serviceConfig = {
         User = "riemanntools";
         ExecStart = "${healthLauncher}/bin/riemann-health";
-        PermissionsStartOnly = true;
       };
     };
 
diff --git a/nixos/modules/services/monitoring/scollector.nix b/nixos/modules/services/monitoring/scollector.nix
index fbded746a5f7..dc0899c7e684 100644
--- a/nixos/modules/services/monitoring/scollector.nix
+++ b/nixos/modules/services/monitoring/scollector.nix
@@ -116,7 +116,6 @@ in {
       path = [ pkgs.coreutils pkgs.iproute ];
 
       serviceConfig = {
-        PermissionsStartOnly = true;
         User = cfg.user;
         Group = cfg.group;
         ExecStart = "${cfg.package.bin}/bin/scollector -conf=${conf} ${lib.concatStringsSep " " cfg.extraOpts}";
diff --git a/nixos/modules/services/monitoring/thanos.nix b/nixos/modules/services/monitoring/thanos.nix
new file mode 100644
index 000000000000..b41e99b76477
--- /dev/null
+++ b/nixos/modules/services/monitoring/thanos.nix
@@ -0,0 +1,801 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.thanos;
+
+  nullOpt = type: description: mkOption {
+    type = types.nullOr type;
+    default = null;
+    inherit description;
+  };
+
+  optionToArgs = opt: v  : optional (v != null)  ''--${opt}="${toString v}"'';
+  flagToArgs   = opt: v  : optional v            ''--${opt}'';
+  listToArgs   = opt: vs : map               (v: ''--${opt}="${v}"'') vs;
+  attrsToArgs  = opt: kvs: mapAttrsToList (k: v: ''--${opt}=${k}=\"${v}\"'') kvs;
+
+  mkParamDef = type: default: description: mkParam type (description + ''
+
+    Defaults to <literal>${toString default}</literal> in Thanos
+    when set to <literal>null</literal>.
+  '');
+
+  mkParam = type: description: {
+    toArgs = optionToArgs;
+    option = nullOpt type description;
+  };
+
+  mkFlagParam = description: {
+    toArgs = flagToArgs;
+    option = mkOption {
+      type = types.bool;
+      default = false;
+      inherit description;
+    };
+  };
+
+  mkListParam = opt: description: {
+    toArgs = _opt: listToArgs opt;
+    option = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      inherit description;
+    };
+  };
+
+  mkAttrsParam = opt: description: {
+    toArgs = _opt: attrsToArgs opt;
+    option = mkOption {
+      type = types.attrsOf types.str;
+      default = {};
+      inherit description;
+    };
+  };
+
+  mkStateDirParam = opt: default: description: {
+    toArgs = _opt: stateDir: optionToArgs opt "/var/lib/${stateDir}";
+    option = mkOption {
+      type = types.str;
+      inherit default;
+      inherit description;
+    };
+  };
+
+  toYAML = name: attrs: pkgs.runCommandNoCC name {
+    preferLocalBuild = true;
+    json = builtins.toFile "${name}.json" (builtins.toJSON attrs);
+    nativeBuildInputs = [ pkgs.remarshal ];
+  } ''json2yaml -i $json -o $out'';
+
+  thanos = cmd: "${cfg.package}/bin/thanos ${cmd}" +
+    (let args = cfg."${cmd}".arguments;
+     in optionalString (length args != 0) (" \\\n  " +
+         concatStringsSep " \\\n  " args));
+
+  argumentsOf = cmd: concatLists (collect isList
+    (flip mapParamsRecursive params."${cmd}" (path: param:
+      let opt = concatStringsSep "." path;
+          v = getAttrFromPath path cfg."${cmd}";
+      in param.toArgs opt v)));
+
+  mkArgumentsOption = cmd: mkOption {
+    type = types.listOf types.str;
+    default = argumentsOf cmd;
+    description = ''
+      Arguments to the <literal>thanos ${cmd}</literal> command.
+
+      Defaults to a list of arguments formed by converting the structured
+      options of <option>services.thanos.${cmd}</option> to a list of arguments.
+
+      Overriding this option will cause none of the structured options to have
+      any effect. So only set this if you know what you're doing!
+    '';
+  };
+
+  mapParamsRecursive =
+    let noParam = attr: !(attr ? "toArgs" && attr ? "option");
+    in mapAttrsRecursiveCond noParam;
+
+  paramsToOptions = mapParamsRecursive (_path: param: param.option);
+
+  params = {
+
+    log = {
+
+      log.level = mkParamDef (types.enum ["debug" "info" "warn" "error" "fatal"]) "info" ''
+        Log filtering level.
+      '';
+
+      log.format = mkParam types.str ''
+        Log format to use.
+      '';
+    };
+
+    tracing = cfg: {
+      tracing.config-file = {
+        toArgs = _opt: path: optionToArgs "tracing.config-file" path;
+        option = mkOption {
+          type = with types; nullOr str;
+          default = if cfg.tracing.config == null then null
+                    else toString (toYAML "tracing.yaml" cfg.tracing.config);
+          defaultText = ''
+            if config.services.thanos.<cmd>.tracing.config == null then null
+            else toString (toYAML "tracing.yaml" config.services.thanos.<cmd>.tracing.config);
+          '';
+          description = ''
+            Path to YAML file that contains tracing configuration.
+          '';
+        };
+      };
+
+      tracing.config =
+        {
+          toArgs = _opt: _attrs: [];
+          option = nullOpt types.attrs ''
+            Tracing configuration.
+
+            When not <literal>null</literal> the attribute set gets converted to
+            a YAML file and stored in the Nix store. The option
+            <option>tracing.config-file</option> will default to its path.
+
+            If <option>tracing.config-file</option> is set this option has no effect.
+          '';
+        };
+    };
+
+    common = cfg: params.log // params.tracing cfg // {
+
+      http-address = mkParamDef types.str "0.0.0.0:10902" ''
+        Listen <literal>host:port</literal> for HTTP endpoints.
+      '';
+
+      grpc-address = mkParamDef types.str "0.0.0.0:10901" ''
+        Listen <literal>ip:port</literal> address for gRPC endpoints (StoreAPI).
+
+        Make sure this address is routable from other components.
+      '';
+
+      grpc-server-tls-cert = mkParam types.str ''
+        TLS Certificate for gRPC server, leave blank to disable TLS
+      '';
+
+      grpc-server-tls-key = mkParam types.str ''
+        TLS Key for the gRPC server, leave blank to disable TLS
+      '';
+
+      grpc-server-tls-client-ca = mkParam types.str ''
+        TLS CA to verify clients against.
+
+        If no client CA is specified, there is no client verification on server side.
+        (tls.NoClientCert)
+      '';
+    };
+
+    objstore = cfg: {
+
+      objstore.config-file = {
+        toArgs = _opt: path: optionToArgs "objstore.config-file" path;
+        option = mkOption {
+          type = with types; nullOr str;
+          default = if cfg.objstore.config == null then null
+                    else toString (toYAML "objstore.yaml" cfg.objstore.config);
+          defaultText = ''
+            if config.services.thanos.<cmd>.objstore.config == null then null
+            else toString (toYAML "objstore.yaml" config.services.thanos.<cmd>.objstore.config);
+          '';
+          description = ''
+            Path to YAML file that contains object store configuration.
+          '';
+        };
+      };
+
+      objstore.config =
+        {
+          toArgs = _opt: _attrs: [];
+          option = nullOpt types.attrs ''
+            Object store configuration.
+
+            When not <literal>null</literal> the attribute set gets converted to
+            a YAML file and stored in the Nix store. The option
+            <option>objstore.config-file</option> will default to its path.
+
+            If <option>objstore.config-file</option> is set this option has no effect.
+          '';
+        };
+    };
+
+    sidecar = params.common cfg.sidecar // params.objstore cfg.sidecar // {
+
+      prometheus.url = mkParamDef types.str "http://localhost:9090" ''
+        URL at which to reach Prometheus's API.
+
+        For better performance use local network.
+      '';
+
+      tsdb.path = {
+        toArgs = optionToArgs;
+        option = mkOption {
+          type = types.str;
+          default = "/var/lib/${config.services.prometheus2.stateDir}/data";
+          defaultText = "/var/lib/\${config.services.prometheus2.stateDir}/data";
+          description = ''
+            Data directory of TSDB.
+          '';
+        };
+      };
+
+      reloader.config-file = mkParam types.str ''
+        Config file watched by the reloader.
+      '';
+
+      reloader.config-envsubst-file = mkParam types.str ''
+        Output file for environment variable substituted config file.
+      '';
+
+      reloader.rule-dirs = mkListParam "reloader.rule-dir" ''
+        Rule directories for the reloader to refresh.
+      '';
+
+    };
+
+    store = params.common cfg.store // params.objstore cfg.store // {
+
+      stateDir = mkStateDirParam "data-dir" "thanos-store" ''
+        Data directory relative to <literal>/var/lib</literal>
+        in which to cache remote blocks.
+      '';
+
+      index-cache-size = mkParamDef types.str "250MB" ''
+        Maximum size of items held in the index cache.
+      '';
+
+      chunk-pool-size = mkParamDef types.str "2GB" ''
+        Maximum size of concurrently allocatable bytes for chunks.
+      '';
+
+      store.grpc.series-sample-limit = mkParamDef types.int 0 ''
+        Maximum amount of samples returned via a single Series call.
+
+        <literal>0</literal> means no limit.
+
+        NOTE: for efficiency we take 120 as the number of samples in chunk (it
+        cannot be bigger than that), so the actual number of samples might be
+        lower, even though the maximum could be hit.
+      '';
+
+      store.grpc.series-max-concurrency = mkParamDef types.int 20 ''
+        Maximum number of concurrent Series calls.
+      '';
+
+      sync-block-duration = mkParamDef types.str "3m" ''
+        Repeat interval for syncing the blocks between local and remote view.
+      '';
+
+      block-sync-concurrency = mkParamDef types.int 20 ''
+        Number of goroutines to use when syncing blocks from object storage.
+      '';
+    };
+
+    query = params.common cfg.query // {
+
+      grpc-client-tls-secure = mkFlagParam ''
+        Use TLS when talking to the gRPC server
+      '';
+
+      grpc-client-tls-cert = mkParam types.str ''
+        TLS Certificates to use to identify this client to the server
+      '';
+
+      grpc-client-tls-key = mkParam types.str ''
+        TLS Key for the client's certificate
+      '';
+
+      grpc-client-tls-ca = mkParam types.str ''
+        TLS CA Certificates to use to verify gRPC servers
+      '';
+
+      grpc-client-server-name = mkParam types.str ''
+        Server name to verify the hostname on the returned gRPC certificates.
+        See <link xlink:href="https://tools.ietf.org/html/rfc4366#section-3.1"/>
+      '';
+
+      web.route-prefix = mkParam types.str ''
+        Prefix for API and UI endpoints.
+
+        This allows thanos UI to be served on a sub-path. This option is
+        analogous to <option>web.route-prefix</option> of Promethus.
+      '';
+
+      web.external-prefix = mkParam types.str ''
+        Static prefix for all HTML links and redirect URLs in the UI query web
+        interface.
+
+        Actual endpoints are still served on / or the
+        <option>web.route-prefix</option>. This allows thanos UI to be served
+        behind a reverse proxy that strips a URL sub-path.
+      '';
+
+      web.prefix-header = mkParam types.str ''
+        Name of HTTP request header used for dynamic prefixing of UI links and
+        redirects.
+
+        This option is ignored if the option
+        <literal>web.external-prefix</literal> is set.
+
+        Security risk: enable this option only if a reverse proxy in front of
+        thanos is resetting the header.
+
+        The setting <literal>web.prefix-header="X-Forwarded-Prefix"</literal>
+        can be useful, for example, if Thanos UI is served via Traefik reverse
+        proxy with <literal>PathPrefixStrip</literal> option enabled, which
+        sends the stripped prefix value in <literal>X-Forwarded-Prefix</literal>
+        header. This allows thanos UI to be served on a sub-path.
+      '';
+
+      query.timeout = mkParamDef types.str "2m" ''
+        Maximum time to process query by query node.
+      '';
+
+      query.max-concurrent = mkParamDef types.int 20 ''
+        Maximum number of queries processed concurrently by query node.
+      '';
+
+      query.replica-label = mkParam types.str ''
+        Label to treat as a replica indicator along which data is
+        deduplicated.
+
+        Still you will be able to query without deduplication using
+        <literal>dedup=false</literal> parameter.
+      '';
+
+      selector-labels = mkAttrsParam "selector-label" ''
+        Query selector labels that will be exposed in info endpoint.
+      '';
+
+      store.addresses = mkListParam "store" ''
+        Addresses of statically configured store API servers.
+
+        The scheme may be prefixed with <literal>dns+</literal> or
+        <literal>dnssrv+</literal> to detect store API servers through
+        respective DNS lookups.
+      '';
+
+      store.sd-files = mkListParam "store.sd-files" ''
+        Path to files that contain addresses of store API servers. The path
+        can be a glob pattern.
+      '';
+
+      store.sd-interval = mkParamDef types.str "5m" ''
+        Refresh interval to re-read file SD files. It is used as a resync fallback.
+      '';
+
+      store.sd-dns-interval = mkParamDef types.str "30s" ''
+        Interval between DNS resolutions.
+      '';
+
+      store.unhealthy-timeout = mkParamDef types.str "5m" ''
+        Timeout before an unhealthy store is cleaned from the store UI page.
+      '';
+
+      query.auto-downsampling = mkFlagParam ''
+        Enable automatic adjustment (step / 5) to what source of data should
+        be used in store gateways if no
+        <literal>max_source_resolution</literal> param is specified.
+      '';
+
+      query.partial-response = mkFlagParam ''
+        Enable partial response for queries if no
+        <literal>partial_response</literal> param is specified.
+      '';
+
+      query.default-evaluation-interval = mkParamDef types.str "1m" ''
+        Set default evaluation interval for sub queries.
+      '';
+
+      store.response-timeout = mkParamDef types.str "0ms" ''
+        If a Store doesn't send any data in this specified duration then a
+        Store will be ignored and partial data will be returned if it's
+        enabled. <literal>0</literal> disables timeout.
+      '';
+    };
+
+    rule = params.common cfg.rule // params.objstore cfg.rule // {
+
+      labels = mkAttrsParam "label" ''
+        Labels to be applied to all generated metrics.
+
+        Similar to external labels for Prometheus,
+        used to identify ruler and its blocks as unique source.
+      '';
+
+      stateDir = mkStateDirParam "data-dir" "thanos-rule" ''
+        Data directory relative to <literal>/var/lib</literal>.
+      '';
+
+      rule-files = mkListParam "rule-file" ''
+        Rule files that should be used by rule manager. Can be in glob format.
+      '';
+
+      eval-interval = mkParamDef types.str "30s" ''
+        The default evaluation interval to use.
+      '';
+
+      tsdb.block-duration = mkParamDef types.str "2h" ''
+        Block duration for TSDB block.
+      '';
+
+      tsdb.retention = mkParamDef types.str "48h" ''
+        Block retention time on local disk.
+      '';
+
+      alertmanagers.urls = mkListParam "alertmanagers.url" ''
+        Alertmanager replica URLs to push firing alerts.
+
+        Ruler claims success if push to at least one alertmanager from
+        discovered succeeds. The scheme may be prefixed with
+        <literal>dns+</literal> or <literal>dnssrv+</literal> to detect
+        Alertmanager IPs through respective DNS lookups. The port defaults to
+        <literal>9093</literal> or the SRV record's value. The URL path is
+        used as a prefix for the regular Alertmanager API path.
+      '';
+
+      alertmanagers.send-timeout = mkParamDef types.str "10s" ''
+        Timeout for sending alerts to alertmanager.
+      '';
+
+      alert.query-url = mkParam types.str ''
+        The external Thanos Query URL that would be set in all alerts 'Source' field.
+      '';
+
+      alert.label-drop = mkListParam "alert.label-drop" ''
+        Labels by name to drop before sending to alertmanager.
+
+        This allows alert to be deduplicated on replica label.
+
+        Similar Prometheus alert relabelling
+      '';
+
+      web.route-prefix = mkParam types.str ''
+        Prefix for API and UI endpoints.
+
+        This allows thanos UI to be served on a sub-path.
+
+        This option is analogous to <literal>--web.route-prefix</literal> of Promethus.
+      '';
+
+      web.external-prefix = mkParam types.str ''
+        Static prefix for all HTML links and redirect URLs in the UI query web
+        interface.
+
+        Actual endpoints are still served on / or the
+        <option>web.route-prefix</option>. This allows thanos UI to be served
+        behind a reverse proxy that strips a URL sub-path.
+      '';
+
+      web.prefix-header = mkParam types.str ''
+        Name of HTTP request header used for dynamic prefixing of UI links and
+        redirects.
+
+        This option is ignored if the option
+        <option>web.external-prefix</option> is set.
+
+        Security risk: enable this option only if a reverse proxy in front of
+        thanos is resetting the header.
+
+        The header <literal>X-Forwarded-Prefix</literal> can be useful, for
+        example, if Thanos UI is served via Traefik reverse proxy with
+        <literal>PathPrefixStrip</literal> option enabled, which sends the
+        stripped prefix value in <literal>X-Forwarded-Prefix</literal>
+        header. This allows thanos UI to be served on a sub-path.
+      '';
+
+      query.addresses = mkListParam "query" ''
+        Addresses of statically configured query API servers.
+
+        The scheme may be prefixed with <literal>dns+</literal> or
+        <literal>dnssrv+</literal> to detect query API servers through
+        respective DNS lookups.
+      '';
+
+      query.sd-files = mkListParam "query.sd-files" ''
+        Path to file that contain addresses of query peers.
+        The path can be a glob pattern.
+      '';
+
+      query.sd-interval = mkParamDef types.str "5m" ''
+        Refresh interval to re-read file SD files. (used as a fallback)
+      '';
+
+      query.sd-dns-interval = mkParamDef types.str "30s" ''
+        Interval between DNS resolutions.
+      '';
+    };
+
+    compact = params.log // params.tracing cfg.compact // params.objstore cfg.compact // {
+
+      http-address = mkParamDef types.str "0.0.0.0:10902" ''
+        Listen <literal>host:port</literal> for HTTP endpoints.
+      '';
+
+      stateDir = mkStateDirParam "data-dir" "thanos-compact" ''
+        Data directory relative to <literal>/var/lib</literal>
+        in which to cache blocks and process compactions.
+      '';
+
+      consistency-delay = mkParamDef types.str "30m" ''
+        Minimum age of fresh (non-compacted) blocks before they are being
+        processed. Malformed blocks older than the maximum of consistency-delay
+        and 30m0s will be removed.
+      '';
+
+      retention.resolution-raw = mkParamDef types.str "0d" ''
+        How long to retain raw samples in bucket.
+
+        <literal>0d</literal> - disables this retention
+      '';
+
+      retention.resolution-5m = mkParamDef types.str "0d" ''
+        How long to retain samples of resolution 1 (5 minutes) in bucket.
+
+        <literal>0d</literal> - disables this retention
+      '';
+
+      retention.resolution-1h = mkParamDef types.str "0d" ''
+        How long to retain samples of resolution 2 (1 hour) in bucket.
+
+        <literal>0d</literal> - disables this retention
+      '';
+
+      startAt = {
+        toArgs = _opt: startAt: flagToArgs "wait" (startAt == null);
+        option = nullOpt types.str ''
+          When this option is set to a <literal>systemd.time</literal>
+          specification the Thanos compactor will run at the specified period.
+
+          When this option is <literal>null</literal> the Thanos compactor service
+          will run continuously. So it will not exit after all compactions have
+          been processed but wait for new work.
+        '';
+      };
+
+      block-sync-concurrency = mkParamDef types.int 20 ''
+        Number of goroutines to use when syncing block metadata from object storage.
+      '';
+
+      compact.concurrency = mkParamDef types.int 1 ''
+        Number of goroutines to use when compacting groups.
+      '';
+    };
+
+    downsample = params.log // params.tracing cfg.downsample // params.objstore cfg.downsample // {
+
+      stateDir = mkStateDirParam "data-dir" "thanos-downsample" ''
+        Data directory relative to <literal>/var/lib</literal>
+        in which to cache blocks and process downsamplings.
+      '';
+
+    };
+
+    receive = params.common cfg.receive // params.objstore cfg.receive // {
+
+      remote-write.address = mkParamDef types.str "0.0.0.0:19291" ''
+        Address to listen on for remote write requests.
+      '';
+
+      stateDir = mkStateDirParam "tsdb.path" "thanos-receive" ''
+        Data directory relative to <literal>/var/lib</literal> of TSDB.
+      '';
+
+      labels = mkAttrsParam "labels" ''
+        External labels to announce.
+
+        This flag will be removed in the future when handling multiple tsdb
+        instances is added.
+      '';
+
+      tsdb.retention = mkParamDef types.str "15d" ''
+        How long to retain raw samples on local storage.
+
+        <literal>0d</literal> - disables this retention
+      '';
+    };
+
+  };
+
+  assertRelativeStateDir = cmd: {
+    assertions = [
+      {
+        assertion = !hasPrefix "/" cfg."${cmd}".stateDir;
+        message =
+          "The option services.thanos.${cmd}.stateDir should not be an absolute directory." +
+          " It should be a directory relative to /var/lib.";
+      }
+    ];
+  };
+
+in {
+
+  options.services.thanos = {
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.thanos;
+      defaultText = "pkgs.thanos";
+      description = ''
+        The thanos package that should be used.
+      '';
+    };
+
+    sidecar = paramsToOptions params.sidecar // {
+      enable = mkEnableOption
+        "the Thanos sidecar for Prometheus server";
+      arguments = mkArgumentsOption "sidecar";
+    };
+
+    store = paramsToOptions params.store // {
+      enable = mkEnableOption
+        "the Thanos store node giving access to blocks in a bucket provider.";
+      arguments = mkArgumentsOption "store";
+    };
+
+    query = paramsToOptions params.query // {
+      enable = mkEnableOption
+        ("the Thanos query node exposing PromQL enabled Query API " +
+         "with data retrieved from multiple store nodes");
+      arguments = mkArgumentsOption "query";
+    };
+
+    rule = paramsToOptions params.rule // {
+      enable = mkEnableOption
+        ("the Thanos ruler service which evaluates Prometheus rules against" +
+        " given Query nodes, exposing Store API and storing old blocks in bucket");
+      arguments = mkArgumentsOption "rule";
+    };
+
+    compact = paramsToOptions params.compact // {
+      enable = mkEnableOption
+        "the Thanos compactor which continuously compacts blocks in an object store bucket";
+      arguments = mkArgumentsOption "compact";
+    };
+
+    downsample = paramsToOptions params.downsample // {
+      enable = mkEnableOption
+        "the Thanos downsampler which continuously downsamples blocks in an object store bucket";
+      arguments = mkArgumentsOption "downsample";
+    };
+
+    receive = paramsToOptions params.receive // {
+      enable = mkEnableOption
+        ("the Thanos receiver which accept Prometheus remote write API requests " +
+         "and write to local tsdb (EXPERIMENTAL, this may change drastically without notice)");
+      arguments = mkArgumentsOption "receive";
+    };
+  };
+
+  config = mkMerge [
+
+    (mkIf cfg.sidecar.enable {
+      assertions = [
+        {
+          assertion = config.services.prometheus2.enable;
+          message =
+            "Please enable services.prometheus2 when enabling services.thanos.sidecar.";
+        }
+        {
+          assertion = !(config.services.prometheus2.globalConfig.external_labels == null ||
+                        config.services.prometheus2.globalConfig.external_labels == {});
+          message =
+            "services.thanos.sidecar requires uniquely identifying external labels " +
+            "to be configured in the Prometheus server. " +
+            "Please set services.prometheus2.globalConfig.external_labels.";
+        }
+      ];
+      systemd.services.thanos-sidecar = {
+        wantedBy = [ "multi-user.target" ];
+        after    = [ "network.target" "prometheus2.service" ];
+        serviceConfig = {
+          User = "prometheus";
+          Restart = "always";
+          ExecStart = thanos "sidecar";
+        };
+      };
+    })
+
+    (mkIf cfg.store.enable (mkMerge [
+      (assertRelativeStateDir "store")
+      {
+        systemd.services.thanos-store = {
+          wantedBy = [ "multi-user.target" ];
+          after    = [ "network.target" ];
+          serviceConfig = {
+            DynamicUser = true;
+            StateDirectory = cfg.store.stateDir;
+            Restart = "always";
+            ExecStart = thanos "store";
+          };
+        };
+      }
+    ]))
+
+    (mkIf cfg.query.enable {
+      systemd.services.thanos-query = {
+        wantedBy = [ "multi-user.target" ];
+        after    = [ "network.target" ];
+        serviceConfig = {
+          DynamicUser = true;
+          Restart = "always";
+          ExecStart = thanos "query";
+        };
+      };
+    })
+
+    (mkIf cfg.rule.enable (mkMerge [
+      (assertRelativeStateDir "rule")
+      {
+        systemd.services.thanos-rule = {
+          wantedBy = [ "multi-user.target" ];
+          after    = [ "network.target" ];
+          serviceConfig = {
+            DynamicUser = true;
+            StateDirectory = cfg.rule.stateDir;
+            Restart = "always";
+            ExecStart = thanos "rule";
+          };
+        };
+      }
+    ]))
+
+    (mkIf cfg.compact.enable (mkMerge [
+      (assertRelativeStateDir "compact")
+      {
+        systemd.services.thanos-compact =
+          let wait = cfg.compact.startAt == null; in {
+            wantedBy = [ "multi-user.target" ];
+            after    = [ "network.target" ];
+            serviceConfig = {
+              Type    = if wait then "simple" else "oneshot";
+              Restart = if wait then "always" else "no";
+              DynamicUser = true;
+              StateDirectory = cfg.compact.stateDir;
+              ExecStart = thanos "compact";
+            };
+          } // optionalAttrs (!wait) { inherit (cfg.compact) startAt; };
+      }
+    ]))
+
+    (mkIf cfg.downsample.enable (mkMerge [
+      (assertRelativeStateDir "downsample")
+      {
+        systemd.services.thanos-downsample = {
+          wantedBy = [ "multi-user.target" ];
+          after    = [ "network.target" ];
+          serviceConfig = {
+            DynamicUser = true;
+            StateDirectory = cfg.downsample.stateDir;
+            Restart = "always";
+            ExecStart = thanos "downsample";
+          };
+        };
+      }
+    ]))
+
+    (mkIf cfg.receive.enable (mkMerge [
+      (assertRelativeStateDir "receive")
+      {
+        systemd.services.thanos-receive = {
+          wantedBy = [ "multi-user.target" ];
+          after    = [ "network.target" ];
+          serviceConfig = {
+            DynamicUser = true;
+            StateDirectory = cfg.receive.stateDir;
+            Restart = "always";
+            ExecStart = thanos "receive";
+          };
+        };
+      }
+    ]))
+
+  ];
+}
diff --git a/nixos/modules/services/monitoring/zabbix-agent.nix b/nixos/modules/services/monitoring/zabbix-agent.nix
index 0519e7c2ad6a..b1645f861101 100644
--- a/nixos/modules/services/monitoring/zabbix-agent.nix
+++ b/nixos/modules/services/monitoring/zabbix-agent.nix
@@ -1,73 +1,118 @@
-# Zabbix agent daemon.
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
-
   cfg = config.services.zabbixAgent;
 
-  zabbix = cfg.package;
-
-  stateDir = "/run/zabbix";
-
-  logDir = "/var/log/zabbix";
-
-  pidFile = "${stateDir}/zabbix_agentd.pid";
+  inherit (lib) mkDefault mkEnableOption mkIf mkOption;
+  inherit (lib) attrValues concatMapStringsSep literalExample optionalString types;
 
-  configFile = pkgs.writeText "zabbix_agentd.conf"
-    ''
-      Server = ${cfg.server}
+  user = "zabbix-agent";
+  group = "zabbix-agent";
 
-      LogFile = ${logDir}/zabbix_agentd
-
-      PidFile = ${pidFile}
-
-      StartAgents = 1
+  moduleEnv = pkgs.symlinkJoin {
+    name = "zabbix-agent-module-env";
+    paths = attrValues cfg.modules;
+  };
 
-      ${config.services.zabbixAgent.extraConfig}
-    '';
+  configFile = pkgs.writeText "zabbix_agent.conf" ''
+    LogType = console
+    Server = ${cfg.server}
+    ListenIP = ${cfg.listen.ip}
+    ListenPort = ${toString cfg.listen.port}
+    ${optionalString (cfg.modules != {}) "LoadModulePath = ${moduleEnv}/lib"}
+    ${concatMapStringsSep "\n" (name: "LoadModule = ${name}") (builtins.attrNames cfg.modules)}
+    ${cfg.extraConfig}
+  '';
 
 in
 
 {
-
-  ###### interface
+  # interface
 
   options = {
 
     services.zabbixAgent = {
+      enable = mkEnableOption "the Zabbix Agent";
 
-      enable = mkOption {
-        default = false;
+      package = mkOption {
+        type = types.package;
+        default = pkgs.zabbix.agent;
+        defaultText = "pkgs.zabbix.agent";
+        description = "The Zabbix package to use.";
+      };
+
+      extraPackages = mkOption {
+        type = types.listOf types.package;
+        default = with pkgs; [ nettools ];
+        defaultText = "[ nettools ]";
+        example = "[ nettools mysql ]";
         description = ''
-          Whether to run the Zabbix monitoring agent on this machine.
-          It will send monitoring data to a Zabbix server.
+          Packages to be added to the Zabbix <envar>PATH</envar>.
+          Typically used to add executables for scripts, but can be anything.
         '';
       };
 
-      package = mkOption {
-        type = types.attrs; # Note: pkgs.zabbixXY isn't a derivation, but an attrset of { server = ...; agent = ...; }.
-        default = pkgs.zabbix;
-        defaultText = "pkgs.zabbix";
-        example = literalExample "pkgs.zabbix34";
-        description = ''
-          The Zabbix package to use.
+      modules = mkOption {
+        type = types.attrsOf types.package;
+        description = "A set of modules to load.";
+        default = {};
+        example = literalExample ''
+          {
+            "dummy.so" = pkgs.stdenv.mkDerivation {
+              name = "zabbix-dummy-module-''${cfg.package.version}";
+              src = cfg.package.src;
+              buildInputs = [ cfg.package ];
+              sourceRoot = "zabbix-''${cfg.package.version}/src/modules/dummy";
+              installPhase = '''
+                mkdir -p $out/lib
+                cp dummy.so $out/lib/
+              ''';
+            };
+          }
         '';
       };
 
       server = mkOption {
-        default = "127.0.0.1";
+        type = types.str;
         description = ''
           The IP address or hostname of the Zabbix server to connect to.
         '';
       };
 
+      listen = {
+        ip = mkOption {
+          type = types.str;
+          default = "0.0.0.0";
+          description = ''
+            List of comma delimited IP addresses that the agent should listen on.
+          '';
+        };
+
+        port = mkOption {
+          type = types.port;
+          default = 10050;
+          description = ''
+            Agent will listen on this port for connections from the server.
+          '';
+        };
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Open ports in the firewall for the Zabbix Agent.
+        '';
+      };
+
+      # TODO: for bonus points migrate this to https://github.com/NixOS/rfcs/pull/42
       extraConfig = mkOption {
         default = "";
         type = types.lines;
         description = ''
-          Configuration that is injected verbatim into the configuration file.
+          Configuration that is injected verbatim into the configuration file. Refer to
+          <link xlink:href="https://www.zabbix.com/documentation/current/manual/appendix/config/zabbix_agentd"/>
+          for details on supported values.
         '';
       };
 
@@ -75,38 +120,38 @@ in
 
   };
 
-
-  ###### implementation
+  # implementation
 
   config = mkIf cfg.enable {
 
-    users.users = mkIf (!config.services.zabbixServer.enable) (singleton
-      { name = "zabbix";
-        uid = config.ids.uids.zabbix;
-        description = "Zabbix daemon user";
-      });
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.listen.port ];
+    };
 
-    systemd.services."zabbix-agent" =
-      { description = "Zabbix Agent";
+    users.users.${user} = {
+      description = "Zabbix Agent daemon user";
+      inherit group;
+    };
 
-        wantedBy = [ "multi-user.target" ];
+    users.groups.${group} = { };
 
-        path = [ pkgs.nettools ];
+    systemd.services."zabbix-agent" = {
+      description = "Zabbix Agent";
 
-        preStart =
-          ''
-            mkdir -m 0755 -p ${stateDir} ${logDir}
-            chown zabbix ${stateDir} ${logDir}
-          '';
+      wantedBy = [ "multi-user.target" ];
 
-        serviceConfig.ExecStart = "@${zabbix.agent}/sbin/zabbix_agentd zabbix_agentd --config ${configFile}";
-        serviceConfig.Type = "forking";
-        serviceConfig.RemainAfterExit = true;
-        serviceConfig.Restart = "always";
-        serviceConfig.RestartSec = 2;
-      };
+      path = [ "/run/wrappers" ] ++ cfg.extraPackages;
+
+      serviceConfig = {
+        ExecStart = "@${cfg.package}/sbin/zabbix_agentd zabbix_agentd -f --config ${configFile}";
+        Restart = "always";
+        RestartSec = 2;
 
-    environment.systemPackages = [ zabbix.agent ];
+        User = user;
+        Group = group;
+        PrivateTmp = true;
+      };
+    };
 
   };
 
diff --git a/nixos/modules/services/monitoring/zabbix-proxy.nix b/nixos/modules/services/monitoring/zabbix-proxy.nix
new file mode 100644
index 000000000000..9cfcd1697c11
--- /dev/null
+++ b/nixos/modules/services/monitoring/zabbix-proxy.nix
@@ -0,0 +1,298 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.zabbixProxy;
+  pgsql = config.services.postgresql;
+  mysql = config.services.mysql;
+
+  inherit (lib) mkDefault mkEnableOption mkIf mkOption;
+  inherit (lib) attrValues concatMapStringsSep literalExample optional optionalAttrs optionalString types;
+
+  user = "zabbix";
+  group = "zabbix";
+  runtimeDir = "/run/zabbix";
+  stateDir = "/var/lib/zabbix";
+  passwordFile = "${runtimeDir}/zabbix-dbpassword.conf";
+
+  moduleEnv = pkgs.symlinkJoin {
+    name = "zabbix-proxy-module-env";
+    paths = attrValues cfg.modules;
+  };
+
+  configFile = pkgs.writeText "zabbix_proxy.conf" ''
+    LogType = console
+    ListenIP = ${cfg.listen.ip}
+    ListenPort = ${toString cfg.listen.port}
+    Server = ${cfg.server}
+    # TODO: set to cfg.database.socket if database type is pgsql?
+    DBHost = ${optionalString (cfg.database.createLocally != true) cfg.database.host}
+    ${optionalString (cfg.database.createLocally != true) "DBPort = ${cfg.database.port}"}
+    DBName = ${cfg.database.name}
+    DBUser = ${cfg.database.user}
+    ${optionalString (cfg.database.passwordFile != null) "Include ${passwordFile}"}
+    ${optionalString (mysqlLocal && cfg.database.socket != null) "DBSocket = ${cfg.database.socket}"}
+    SocketDir = ${runtimeDir}
+    FpingLocation = /run/wrappers/bin/fping
+    ${optionalString (cfg.modules != {}) "LoadModulePath = ${moduleEnv}/lib"}
+    ${concatMapStringsSep "\n" (name: "LoadModule = ${name}") (builtins.attrNames cfg.modules)}
+    ${cfg.extraConfig}
+  '';
+
+  mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql";
+  pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql";
+
+in
+
+{
+  # interface
+
+  options = {
+
+    services.zabbixProxy = {
+      enable = mkEnableOption "the Zabbix Proxy";
+
+      server = mkOption {
+        type = types.str;
+        description = ''
+          The IP address or hostname of the Zabbix server to connect to.
+          '';
+        };
+
+      package = mkOption {
+        type = types.package;
+        default =
+          if cfg.database.type == "mysql" then pkgs.zabbix.proxy-mysql
+          else if cfg.database.type == "pgsql" then pkgs.zabbix.proxy-pgsql
+          else pkgs.zabbix.proxy-sqlite;
+        defaultText = "pkgs.zabbix.proxy-pgsql";
+        description = "The Zabbix package to use.";
+      };
+
+      extraPackages = mkOption {
+        type = types.listOf types.package;
+        default = with pkgs; [ nettools nmap traceroute ];
+        defaultText = "[ nettools nmap traceroute ]";
+        description = ''
+          Packages to be added to the Zabbix <envar>PATH</envar>.
+          Typically used to add executables for scripts, but can be anything.
+        '';
+      };
+
+      modules = mkOption {
+        type = types.attrsOf types.package;
+        description = "A set of modules to load.";
+        default = {};
+        example = literalExample ''
+          {
+            "dummy.so" = pkgs.stdenv.mkDerivation {
+              name = "zabbix-dummy-module-''${cfg.package.version}";
+              src = cfg.package.src;
+              buildInputs = [ cfg.package ];
+              sourceRoot = "zabbix-''${cfg.package.version}/src/modules/dummy";
+              installPhase = '''
+                mkdir -p $out/lib
+                cp dummy.so $out/lib/
+              ''';
+            };
+          }
+        '';
+      };
+
+      database = {
+        type = mkOption {
+          type = types.enum [ "mysql" "pgsql" "sqlite" ];
+          example = "mysql";
+          default = "pgsql";
+          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 == "mysql" then mysql.port else pgsql.port;
+          description = "Database host port.";
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "zabbix";
+          description = "Database name.";
+        };
+
+        user = mkOption {
+          type = types.str;
+          default = "zabbix";
+          description = "Database user.";
+        };
+
+        passwordFile = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          example = "/run/keys/zabbix-dbpassword";
+          description = ''
+            A file containing the password corresponding to
+            <option>database.user</option>.
+          '';
+        };
+
+        socket = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          example = "/run/postgresql";
+          description = "Path to the unix socket file to use for authentication.";
+        };
+
+        createLocally = mkOption {
+          type = types.bool;
+          default = true;
+          description = "Whether to create a local database automatically.";
+        };
+      };
+
+      listen = {
+        ip = mkOption {
+          type = types.str;
+          default = "0.0.0.0";
+          description = ''
+            List of comma delimited IP addresses that the trapper should listen on.
+            Trapper will listen on all network interfaces if this parameter is missing.
+          '';
+        };
+
+        port = mkOption {
+          type = types.port;
+          default = 10051;
+          description = ''
+            Listen port for trapper.
+          '';
+        };
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Open ports in the firewall for the Zabbix Proxy.
+        '';
+      };
+
+      # TODO: for bonus points migrate this to https://github.com/NixOS/rfcs/pull/42
+      extraConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Configuration that is injected verbatim into the configuration file. Refer to
+          <link xlink:href="https://www.zabbix.com/documentation/current/manual/appendix/config/zabbix_proxy"/>
+          for details on supported values.
+        '';
+      };
+
+    };
+
+  };
+
+  # implementation
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion = !config.services.zabbixServer.enable;
+        message = "Please choose one of services.zabbixServer or services.zabbixProxy.";
+      }
+      { assertion = cfg.database.createLocally -> cfg.database.user == user;
+        message = "services.zabbixProxy.database.user must be set to ${user} if services.zabbixProxy.database.createLocally is set true";
+      }
+      { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
+        message = "a password cannot be specified if services.zabbixProxy.database.createLocally is set to true";
+      }
+    ];
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.listen.port ];
+    };
+
+    services.mysql = optionalAttrs mysqlLocal {
+      enable = true;
+      package = mkDefault pkgs.mariadb;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.database.user;
+          ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
+
+    services.postgresql = optionalAttrs pgsqlLocal {
+      enable = true;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.database.user;
+          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
+
+    users.users.${user} = {
+      description = "Zabbix daemon user";
+      uid = config.ids.uids.zabbix;
+      inherit group;
+    };
+
+    users.groups.${group} = {
+      gid = config.ids.gids.zabbix;
+    };
+
+    security.wrappers = {
+      fping.source = "${pkgs.fping}/bin/fping";
+    };
+
+    systemd.services."zabbix-proxy" = {
+      description = "Zabbix Proxy";
+
+      wantedBy = [ "multi-user.target" ];
+      after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
+
+      path = [ "/run/wrappers" ] ++ cfg.extraPackages;
+      preStart = optionalString pgsqlLocal ''
+        if ! test -e "${stateDir}/db-created"; then
+          cat ${cfg.package}/share/zabbix/database/postgresql/schema.sql | ${pgsql.package}/bin/psql ${cfg.database.name}
+          cat ${cfg.package}/share/zabbix/database/postgresql/images.sql | ${pgsql.package}/bin/psql ${cfg.database.name}
+          cat ${cfg.package}/share/zabbix/database/postgresql/data.sql | ${pgsql.package}/bin/psql ${cfg.database.name}
+          touch "${stateDir}/db-created"
+        fi
+      '' + optionalString mysqlLocal ''
+        if ! test -e "${stateDir}/db-created"; then
+          cat ${cfg.package}/share/zabbix/database/mysql/schema.sql | ${mysql.package}/bin/mysql ${cfg.database.name}
+          cat ${cfg.package}/share/zabbix/database/mysql/images.sql | ${mysql.package}/bin/mysql ${cfg.database.name}
+          cat ${cfg.package}/share/zabbix/database/mysql/data.sql | ${mysql.package}/bin/mysql ${cfg.database.name}
+          touch "${stateDir}/db-created"
+        fi
+      '' + optionalString (cfg.database.passwordFile != null) ''
+        # create a copy of the supplied password file in a format zabbix can consume
+        touch ${passwordFile}
+        chmod 0600 ${passwordFile}
+        echo -n "DBPassword = " > ${passwordFile}
+        cat ${cfg.database.passwordFile} >> ${passwordFile}
+      '';
+
+      serviceConfig = {
+        ExecStart = "@${cfg.package}/sbin/zabbix_proxy zabbix_proxy -f --config ${configFile}";
+        Restart = "always";
+        RestartSec = 2;
+
+        User = user;
+        Group = group;
+        RuntimeDirectory = "zabbix";
+        StateDirectory = "zabbix";
+        PrivateTmp = true;
+      };
+    };
+
+  };
+
+}
diff --git a/nixos/modules/services/monitoring/zabbix-server.nix b/nixos/modules/services/monitoring/zabbix-server.nix
index fdeab6af4417..11311b466c3f 100644
--- a/nixos/modules/services/monitoring/zabbix-server.nix
+++ b/nixos/modules/services/monitoring/zabbix-server.nix
@@ -1,125 +1,292 @@
-# Zabbix server daemon.
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
-
   cfg = config.services.zabbixServer;
+  pgsql = config.services.postgresql;
+  mysql = config.services.mysql;
 
-  stateDir = "/run/zabbix";
+  inherit (lib) mkDefault mkEnableOption mkIf mkOption;
+  inherit (lib) attrValues concatMapStringsSep literalExample optional optionalAttrs optionalString types;
 
-  logDir = "/var/log/zabbix";
+  user = "zabbix";
+  group = "zabbix";
+  runtimeDir = "/run/zabbix";
+  stateDir = "/var/lib/zabbix";
+  passwordFile = "${runtimeDir}/zabbix-dbpassword.conf";
 
-  libDir = "/var/lib/zabbix";
+  moduleEnv = pkgs.symlinkJoin {
+    name = "zabbix-server-module-env";
+    paths = attrValues cfg.modules;
+  };
 
-  pidFile = "${stateDir}/zabbix_server.pid";
+  configFile = pkgs.writeText "zabbix_server.conf" ''
+    LogType = console
+    ListenIP = ${cfg.listen.ip}
+    ListenPort = ${toString cfg.listen.port}
+    # TODO: set to cfg.database.socket if database type is pgsql?
+    DBHost = ${optionalString (cfg.database.createLocally != true) cfg.database.host}
+    ${optionalString (cfg.database.createLocally != true) "DBPort = ${cfg.database.port}"}
+    DBName = ${cfg.database.name}
+    DBUser = ${cfg.database.user}
+    ${optionalString (cfg.database.passwordFile != null) "Include ${passwordFile}"}
+    ${optionalString (mysqlLocal && cfg.database.socket != null) "DBSocket = ${cfg.database.socket}"}
+    SocketDir = ${runtimeDir}
+    FpingLocation = /run/wrappers/bin/fping
+    ${optionalString (cfg.modules != {}) "LoadModulePath = ${moduleEnv}/lib"}
+    ${concatMapStringsSep "\n" (name: "LoadModule = ${name}") (builtins.attrNames cfg.modules)}
+    ${cfg.extraConfig}
+  '';
+
+  mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql";
+  pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql";
 
-  configFile = pkgs.writeText "zabbix_server.conf"
-    ''
-      LogFile = ${logDir}/zabbix_server
+in
 
-      PidFile = ${pidFile}
+{
+  # interface
 
-      ${optionalString (cfg.dbServer != "localhost") ''
-        DBHost = ${cfg.dbServer}
-      ''}
+  options = {
 
-      DBName = zabbix
+    services.zabbixServer = {
+      enable = mkEnableOption "the Zabbix Server";
 
-      DBUser = zabbix
+      package = mkOption {
+        type = types.package;
+        default = if cfg.database.type == "mysql" then pkgs.zabbix.server-mysql else pkgs.zabbix.server-pgsql;
+        defaultText = "pkgs.zabbix.server-pgsql";
+        description = "The Zabbix package to use.";
+      };
 
-      ${optionalString (cfg.dbPassword != "") ''
-        DBPassword = ${cfg.dbPassword}
-      ''}
+      extraPackages = mkOption {
+        type = types.listOf types.package;
+        default = with pkgs; [ nettools nmap traceroute ];
+        defaultText = "[ nettools nmap traceroute ]";
+        description = ''
+          Packages to be added to the Zabbix <envar>PATH</envar>.
+          Typically used to add executables for scripts, but can be anything.
+        '';
+      };
 
-      ${config.services.zabbixServer.extraConfig}
-    '';
+      modules = mkOption {
+        type = types.attrsOf types.package;
+        description = "A set of modules to load.";
+        default = {};
+        example = literalExample ''
+          {
+            "dummy.so" = pkgs.stdenv.mkDerivation {
+              name = "zabbix-dummy-module-''${cfg.package.version}";
+              src = cfg.package.src;
+              buildInputs = [ cfg.package ];
+              sourceRoot = "zabbix-''${cfg.package.version}/src/modules/dummy";
+              installPhase = '''
+                mkdir -p $out/lib
+                cp dummy.so $out/lib/
+              ''';
+            };
+          }
+        '';
+      };
 
-  useLocalPostgres = cfg.dbServer == "localhost" || cfg.dbServer == "";
+      database = {
+        type = mkOption {
+          type = types.enum [ "mysql" "pgsql" ];
+          example = "mysql";
+          default = "pgsql";
+          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 == "mysql" then mysql.port else pgsql.port;
+          description = "Database host port.";
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "zabbix";
+          description = "Database name.";
+        };
+
+        user = mkOption {
+          type = types.str;
+          default = "zabbix";
+          description = "Database user.";
+        };
+
+        passwordFile = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          example = "/run/keys/zabbix-dbpassword";
+          description = ''
+            A file containing the password corresponding to
+            <option>database.user</option>.
+          '';
+        };
+
+        socket = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          example = "/run/postgresql";
+          description = "Path to the unix socket file to use for authentication.";
+        };
+
+        createLocally = mkOption {
+          type = types.bool;
+          default = true;
+          description = "Whether to create a local database automatically.";
+        };
+      };
 
-in
+      listen = {
+        ip = mkOption {
+          type = types.str;
+          default = "0.0.0.0";
+          description = ''
+            List of comma delimited IP addresses that the trapper should listen on.
+            Trapper will listen on all network interfaces if this parameter is missing.
+          '';
+        };
 
-{
+        port = mkOption {
+          type = types.port;
+          default = 10051;
+          description = ''
+            Listen port for trapper.
+          '';
+        };
+      };
 
-  ###### interface
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Open ports in the firewall for the Zabbix Server.
+        '';
+      };
 
-  options = {
+      # TODO: for bonus points migrate this to https://github.com/NixOS/rfcs/pull/42
+      extraConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Configuration that is injected verbatim into the configuration file. Refer to
+          <link xlink:href="https://www.zabbix.com/documentation/current/manual/appendix/config/zabbix_server"/>
+          for details on supported values.
+        '';
+      };
 
-    services.zabbixServer.enable = mkOption {
-      default = false;
-      type = types.bool;
-      description = ''
-        Whether to run the Zabbix server on this machine.
-      '';
     };
 
-    services.zabbixServer.dbServer = mkOption {
-      default = "localhost";
-      type = types.str;
-      description = ''
-        Hostname or IP address of the database server.
-        Use an empty string ("") to use peer authentication.
-      '';
-    };
+  };
 
-    services.zabbixServer.dbPassword = mkOption {
-      default = "";
-      type = types.str;
-      description = "Password used to connect to the database server.";
-    };
+  # implementation
 
-    services.zabbixServer.extraConfig = mkOption {
-      default = "";
-      type = types.lines;
-      description = ''
-        Configuration that is injected verbatim into the configuration file.
-      '';
+  config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion = cfg.database.createLocally -> cfg.database.user == user;
+        message = "services.zabbixServer.database.user must be set to ${user} if services.zabbixServer.database.createLocally is set true";
+      }
+      { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
+        message = "a password cannot be specified if services.zabbixServer.database.createLocally is set to true";
+      }
+    ];
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.listen.port ];
     };
 
-  };
+    services.mysql = optionalAttrs mysqlLocal {
+      enable = true;
+      package = mkDefault pkgs.mariadb;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.database.user;
+          ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
 
-  ###### implementation
+    services.postgresql = optionalAttrs pgsqlLocal {
+      enable = true;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.database.user;
+          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
 
-  config = mkIf cfg.enable {
+    users.users.${user} = {
+      description = "Zabbix daemon user";
+      uid = config.ids.uids.zabbix;
+      inherit group;
+    };
 
-    services.postgresql.enable = useLocalPostgres;
+    users.groups.${group} = {
+      gid = config.ids.gids.zabbix;
+    };
 
-    users.users = singleton
-      { name = "zabbix";
-        uid = config.ids.uids.zabbix;
-        description = "Zabbix daemon user";
-      };
+    security.wrappers = {
+      fping.source = "${pkgs.fping}/bin/fping";
+    };
 
-    systemd.services."zabbix-server" =
-      { description = "Zabbix Server";
-
-        wantedBy = [ "multi-user.target" ];
-        after = optional useLocalPostgres "postgresql.service";
-
-        preStart =
-          ''
-            mkdir -m 0755 -p ${stateDir} ${logDir} ${libDir}
-            chown zabbix ${stateDir} ${logDir} ${libDir}
-
-            if ! test -e "${libDir}/db-created"; then
-                ${pkgs.su}/bin/su -s "$SHELL" ${config.services.postgresql.superUser} -c '${pkgs.postgresql}/bin/createuser --no-superuser --no-createdb --no-createrole zabbix' || true
-                ${pkgs.su}/bin/su -s "$SHELL" ${config.services.postgresql.superUser} -c '${pkgs.postgresql}/bin/createdb --owner zabbix zabbix' || true
-                cat ${pkgs.zabbix.server}/share/zabbix/db/schema/postgresql.sql | ${pkgs.su}/bin/su -s "$SHELL" zabbix -c '${pkgs.postgresql}/bin/psql zabbix'
-                cat ${pkgs.zabbix.server}/share/zabbix/db/data/images_pgsql.sql | ${pkgs.su}/bin/su -s "$SHELL" zabbix -c '${pkgs.postgresql}/bin/psql zabbix'
-                cat ${pkgs.zabbix.server}/share/zabbix/db/data/data.sql | ${pkgs.su}/bin/su -s "$SHELL" zabbix -c '${pkgs.postgresql}/bin/psql zabbix'
-                touch "${libDir}/db-created"
-            fi
-          '';
+    systemd.services."zabbix-server" = {
+      description = "Zabbix Server";
+
+      wantedBy = [ "multi-user.target" ];
+      after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
+
+      path = [ "/run/wrappers" ] ++ cfg.extraPackages;
+      preStart = ''
+        # pre 19.09 compatibility
+        if test -e "${runtimeDir}/db-created"; then
+          mv "${runtimeDir}/db-created" "${stateDir}/"
+        fi
+      '' + optionalString pgsqlLocal ''
+        if ! test -e "${stateDir}/db-created"; then
+          cat ${cfg.package}/share/zabbix/database/postgresql/schema.sql | ${pgsql.package}/bin/psql ${cfg.database.name}
+          cat ${cfg.package}/share/zabbix/database/postgresql/images.sql | ${pgsql.package}/bin/psql ${cfg.database.name}
+          cat ${cfg.package}/share/zabbix/database/postgresql/data.sql | ${pgsql.package}/bin/psql ${cfg.database.name}
+          touch "${stateDir}/db-created"
+        fi
+      '' + optionalString mysqlLocal ''
+        if ! test -e "${stateDir}/db-created"; then
+          cat ${cfg.package}/share/zabbix/database/mysql/schema.sql | ${mysql.package}/bin/mysql ${cfg.database.name}
+          cat ${cfg.package}/share/zabbix/database/mysql/images.sql | ${mysql.package}/bin/mysql ${cfg.database.name}
+          cat ${cfg.package}/share/zabbix/database/mysql/data.sql | ${mysql.package}/bin/mysql ${cfg.database.name}
+          touch "${stateDir}/db-created"
+        fi
+      '' + optionalString (cfg.database.passwordFile != null) ''
+        # create a copy of the supplied password file in a format zabbix can consume
+        touch ${passwordFile}
+        chmod 0600 ${passwordFile}
+        echo -n "DBPassword = " > ${passwordFile}
+        cat ${cfg.database.passwordFile} >> ${passwordFile}
+      '';
 
-        path = [ pkgs.nettools ];
+      serviceConfig = {
+        ExecStart = "@${cfg.package}/sbin/zabbix_server zabbix_server -f --config ${configFile}";
+        Restart = "always";
+        RestartSec = 2;
 
-        serviceConfig.ExecStart = "@${pkgs.zabbix.server}/sbin/zabbix_server zabbix_server --config ${configFile}";
-        serviceConfig.Type = "forking";
-        serviceConfig.Restart = "always";
-        serviceConfig.RestartSec = 2;
-        serviceConfig.PIDFile = pidFile;
+        User = user;
+        Group = group;
+        RuntimeDirectory = "zabbix";
+        StateDirectory = "zabbix";
+        PrivateTmp = true;
       };
+    };
+
+    systemd.services.httpd.after =
+      optional (config.services.zabbixWeb.enable && mysqlLocal) "mysql.service" ++
+      optional (config.services.zabbixWeb.enable && pgsqlLocal) "postgresql.service";
 
   };
 
diff --git a/nixos/modules/services/network-filesystems/kbfs.nix b/nixos/modules/services/network-filesystems/kbfs.nix
index 7b2eea3b5850..263b70d04a56 100644
--- a/nixos/modules/services/network-filesystems/kbfs.nix
+++ b/nixos/modules/services/network-filesystems/kbfs.nix
@@ -48,6 +48,7 @@ in {
       requires = [ "keybase.service" ];
       after = [ "keybase.service" ];
       path = [ "/run/wrappers" ];
+      unitConfig.ConditionUser = "!@system";
       serviceConfig = {
         ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p ${cfg.mountPoint}";
         ExecStart = "${pkgs.kbfs}/bin/kbfsfuse ${toString cfg.extraFlags} ${cfg.mountPoint}";
diff --git a/nixos/modules/services/network-filesystems/samba.nix b/nixos/modules/services/network-filesystems/samba.nix
index 10dc58311212..69368441c62c 100644
--- a/nixos/modules/services/network-filesystems/samba.nix
+++ b/nixos/modules/services/network-filesystems/samba.nix
@@ -86,10 +86,10 @@ in
 
           <note>
             <para>If you use the firewall consider adding the following:</para>
-            <programlisting>
-              networking.firewall.allowedTCPPorts = [ 139 445 ];
-              networking.firewall.allowedUDPPorts = [ 137 138 ];
-            </programlisting>
+          <programlisting>
+            networking.firewall.allowedTCPPorts = [ 139 445 ];
+            networking.firewall.allowedUDPPorts = [ 137 138 ];
+          </programlisting>
           </note>
         '';
       };
diff --git a/nixos/modules/services/networking/aria2.nix b/nixos/modules/services/networking/aria2.nix
index 98eb00861016..53829bf18863 100644
--- a/nixos/modules/services/networking/aria2.nix
+++ b/nixos/modules/services/networking/aria2.nix
@@ -102,22 +102,19 @@ in
 
     users.groups.aria2.gid = config.ids.gids.aria2;
 
+    systemd.tmpfiles.rules = [
+      "d '${homeDir}' 0770 aria2 aria2 - -"
+      "d '${config.services.aria2.downloadDir}' 0770 aria2 aria2 - -"
+    ];
+
     systemd.services.aria2 = {
       description = "aria2 Service";
       after = [ "local-fs.target" "network.target" ];
       wantedBy = [ "multi-user.target" ];
       preStart = ''
-        mkdir -m 0770 -p "${homeDir}"
-        chown aria2:aria2 "${homeDir}"
-        if [[ ! -d "${config.services.aria2.downloadDir}" ]]
-        then
-          mkdir -m 0770 -p "${config.services.aria2.downloadDir}"
-          chown aria2:aria2 "${config.services.aria2.downloadDir}"
-        fi
         if [[ ! -e "${sessionFile}" ]]
         then
           touch "${sessionFile}"
-          chown aria2:aria2 "${sessionFile}"
         fi
         cp -f "${settingsFile}" "${settingsDir}/aria2.conf"
       '';
@@ -128,7 +125,6 @@ in
         ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
         User = "aria2";
         Group = "aria2";
-        PermissionsStartOnly = true;
       };
     };
   };
diff --git a/nixos/modules/services/networking/autossh.nix b/nixos/modules/services/networking/autossh.nix
index 9ea17469870d..a098a155e991 100644
--- a/nixos/modules/services/networking/autossh.nix
+++ b/nixos/modules/services/networking/autossh.nix
@@ -99,7 +99,6 @@ in
 
               serviceConfig = {
                   User = "${s.user}";
-                  PermissionsStartOnly = true;
                   # AutoSSH may exit with 0 code if the SSH session was
                   # gracefully terminated by either local or remote side.
                   Restart = "on-success";
diff --git a/nixos/modules/services/networking/avahi-daemon.nix b/nixos/modules/services/networking/avahi-daemon.nix
index 4c91a0c415b6..ddcfe3d77e2f 100644
--- a/nixos/modules/services/networking/avahi-daemon.nix
+++ b/nixos/modules/services/networking/avahi-daemon.nix
@@ -1,10 +1,8 @@
-# Avahi daemon.
 { config, lib, pkgs, ... }:
 
 with lib;
 
 let
-
   cfg = config.services.avahi;
 
   yesNo = yes : if yes then "yes" else "no";
@@ -39,215 +37,245 @@ let
     enable-reflector=${yesNo reflector}
     ${extraConfig}
   '';
-
 in
-
 {
+  options.services.avahi = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to run the Avahi daemon, which allows Avahi clients
+        to use Avahi's service discovery facilities and also allows
+        the local machine to advertise its presence and services
+        (through the mDNS responder implemented by `avahi-daemon').
+      '';
+    };
 
-  ###### interface
+    hostName = mkOption {
+      type = types.str;
+      default = config.networking.hostName;
+      defaultText = literalExample "config.networking.hostName";
+      description = ''
+        Host name advertised on the LAN. If not set, avahi will use the value
+        of <option>config.networking.hostName</option>.
+      '';
+    };
 
-  options = {
+    domainName = mkOption {
+      type = types.str;
+      default = "local";
+      description = ''
+        Domain name for all advertisements.
+      '';
+    };
 
-    services.avahi = {
+    browseDomains = mkOption {
+      type = types.listOf types.str;
+      default = [ ];
+      example = [ "0pointer.de" "zeroconf.org" ];
+      description = ''
+        List of non-local DNS domains to be browsed.
+      '';
+    };
 
-      enable = mkOption {
-        default = false;
-        description = ''
-          Whether to run the Avahi daemon, which allows Avahi clients
-          to use Avahi's service discovery facilities and also allows
-          the local machine to advertise its presence and services
-          (through the mDNS responder implemented by `avahi-daemon').
-        '';
-      };
+    ipv4 = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Whether to use IPv4.";
+    };
 
-      hostName = mkOption {
-        type = types.str;
-        description = ''
-          Host name advertised on the LAN. If not set, avahi will use the value
-          of config.networking.hostName.
-        '';
-      };
+    ipv6 = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Whether to use IPv6.";
+    };
 
-      domainName = mkOption {
-        type = types.str;
-        default = "local";
-        description = ''
-          Domain name for all advertisements.
-        '';
-      };
+    interfaces = mkOption {
+      type = types.nullOr (types.listOf types.str);
+      default = null;
+      description = ''
+        List of network interfaces that should be used by the <command>avahi-daemon</command>.
+        Other interfaces will be ignored. If <literal>null</literal>, all local interfaces
+        except loopback and point-to-point will be used.
+      '';
+    };
 
-      browseDomains = mkOption {
-        default = [ ];
-        example = [ "0pointer.de" "zeroconf.org" ];
-        description = ''
-          List of non-local DNS domains to be browsed.
-        '';
-      };
+    openFirewall = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether to open the firewall for UDP port 5353.
+      '';
+    };
 
-      ipv4 = mkOption {
-        default = true;
-        description = ''Whether to use IPv4'';
-      };
+    allowPointToPoint = mkOption {
+      type = types.bool;
+      default = false;
+      description= ''
+        Whether to use POINTTOPOINT interfaces. Might make mDNS unreliable due to usually large
+        latencies with such links and opens a potential security hole by allowing mDNS access from Internet
+        connections.
+      '';
+    };
 
-      ipv6 = mkOption {
-        default = false;
-        description = ''Whether to use IPv6'';
-      };
+    wideArea = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Whether to enable wide-area service discovery.";
+    };
 
-      interfaces = mkOption {
-        type = types.nullOr (types.listOf types.str);
-        default = null;
-        description = ''
-          List of network interfaces that should be used by the <command>avahi-daemon</command>.
-          Other interfaces will be ignored. If <literal>null</literal> all local interfaces
-          except loopback and point-to-point will be used.
-        '';
-      };
+    reflector = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Reflect incoming mDNS requests to all allowed network interfaces.";
+    };
 
-      allowPointToPoint = mkOption {
-        default = false;
-        description= ''
-          Whether to use POINTTOPOINT interfaces. Might make mDNS unreliable due to usually large
-          latencies with such links and opens a potential security hole by allowing mDNS access from Internet
-          connections. Use with care and YMMV!
-        '';
-      };
+    extraServiceFiles = mkOption {
+      type = with types; attrsOf (either str path);
+      default = {};
+      example = literalExample ''
+        {
+          ssh = "''${pkgs.avahi}/etc/avahi/services/ssh.service";
+          smb = '''
+            <?xml version="1.0" standalone='no'?><!--*-nxml-*-->
+            <!DOCTYPE service-group SYSTEM "avahi-service.dtd">
+            <service-group>
+              <name replace-wildcards="yes">%h</name>
+              <service>
+                <type>_smb._tcp</type>
+                <port>445</port>
+              </service>
+            </service-group>
+          ''';
+        }
+      '';
+      description = ''
+        Specify custom service definitions which are placed in the avahi service directory.
+        See the <citerefentry><refentrytitle>avahi.service</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> manpage for detailed information.
+      '';
+    };
 
-      wideArea = mkOption {
-        default = true;
-        description = ''Whether to enable wide-area service discovery.'';
+    publish = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to allow publishing in general.";
       };
 
-      reflector = mkOption {
+      userServices = mkOption {
+        type = types.bool;
         default = false;
-        description = ''Reflect incoming mDNS requests to all allowed network interfaces.'';
+        description = "Whether to publish user services. Will set <literal>addresses=true</literal>.";
       };
 
-      publish = {
-        enable = mkOption {
-          default = false;
-          description = ''Whether to allow publishing in general.'';
-        };
-
-        userServices = mkOption {
-          default = false;
-          description = ''Whether to publish user services. Will set <literal>addresses=true</literal>.'';
-        };
-
-        addresses = mkOption {
-          default = false;
-          description = ''Whether to register mDNS address records for all local IP addresses.'';
-        };
-
-        hinfo = mkOption {
-          default = false;
-          description = ''
-            Whether to register an mDNS HINFO record which contains information about the
-            local operating system and CPU.
-          '';
-        };
-
-        workstation = mkOption {
-          default = false;
-          description = ''Whether to register a service of type "_workstation._tcp" on the local LAN.'';
-        };
-
-        domain = mkOption {
-          default = false;
-          description = ''Whether to announce the locally used domain name for browsing by other hosts.'';
-        };
-
+      addresses = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to register mDNS address records for all local IP addresses.";
       };
 
-      nssmdns = mkOption {
+      hinfo = mkOption {
+        type = types.bool;
         default = false;
         description = ''
-          Whether to enable the mDNS NSS (Name Service Switch) plug-in.
-          Enabling it allows applications to resolve names in the `.local'
-          domain by transparently querying the Avahi daemon.
+          Whether to register a mDNS HINFO record which contains information about the
+          local operating system and CPU.
         '';
       };
 
-      cacheEntriesMax = mkOption {
-        default = null;
-        type = types.nullOr types.int;
+      workstation = mkOption {
+        type = types.bool;
+        default = false;
         description = ''
-          Number of resource records to be cached per interface. Use 0 to
-          disable caching. Avahi daemon defaults to 4096 if not set.
+          Whether to register a service of type "_workstation._tcp" on the local LAN.
         '';
       };
 
-      extraConfig = mkOption {
-        default = "";
-        type = types.lines;
-        description = ''
-          Extra config to append to avahi-daemon.conf.
-        '';
+      domain = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to announce the locally used domain name for browsing by other hosts.";
       };
-
     };
 
-  };
+    nssmdns = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable the mDNS NSS (Name Service Switch) plug-in.
+        Enabling it allows applications to resolve names in the `.local'
+        domain by transparently querying the Avahi daemon.
+      '';
+    };
 
+    cacheEntriesMax = mkOption {
+      type = types.nullOr types.int;
+      default = null;
+      description = ''
+        Number of resource records to be cached per interface. Use 0 to
+        disable caching. Avahi daemon defaults to 4096 if not set.
+      '';
+    };
 
-  ###### implementation
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = ''
+        Extra config to append to avahi-daemon.conf.
+      '';
+    };
+  };
 
   config = mkIf cfg.enable {
+    users.users.avahi = {
+      description = "avahi-daemon privilege separation user";
+      home = "/var/empty";
+      group = "avahi";
+      isSystemUser = true;
+    };
 
-    services.avahi.hostName = mkDefault config.networking.hostName;
-
-    users.users = singleton
-      { name = "avahi";
-        uid = config.ids.uids.avahi;
-        description = "`avahi-daemon' privilege separation user";
-        home = "/var/empty";
-      };
-
-    users.groups = singleton
-      { name = "avahi";
-        gid = config.ids.gids.avahi;
-      };
+    users.groups.avahi = {};
 
     system.nssModules = optional cfg.nssmdns pkgs.nssmdns;
 
     environment.systemPackages = [ pkgs.avahi ];
 
-    systemd.sockets.avahi-daemon =
-      { description = "Avahi mDNS/DNS-SD Stack Activation Socket";
-        listenStreams = [ "/run/avahi-daemon/socket" ];
-        wantedBy = [ "sockets.target" ];
-      };
+    environment.etc = (mapAttrs' (n: v: nameValuePair
+      "avahi/services/${n}.service"
+      { ${if types.path.check v then "source" else "text"} = v; }
+    ) cfg.extraServiceFiles);
 
-    systemd.services.avahi-daemon =
-      { description = "Avahi mDNS/DNS-SD Stack";
-        wantedBy = [ "multi-user.target" ];
-        requires = [ "avahi-daemon.socket" ];
+    systemd.sockets.avahi-daemon = {
+      description = "Avahi mDNS/DNS-SD Stack Activation Socket";
+      listenStreams = [ "/run/avahi-daemon/socket" ];
+      wantedBy = [ "sockets.target" ];
+    };
 
-        serviceConfig."NotifyAccess" = "main";
-        serviceConfig."BusName" = "org.freedesktop.Avahi";
-        serviceConfig."Type" = "dbus";
+    systemd.tmpfiles.rules = [ "d /run/avahi-daemon - avahi avahi -" ];
 
-        path = [ pkgs.coreutils pkgs.avahi ];
+    systemd.services.avahi-daemon = {
+      description = "Avahi mDNS/DNS-SD Stack";
+      wantedBy = [ "multi-user.target" ];
+      requires = [ "avahi-daemon.socket" ];
 
-        preStart = "mkdir -p /run/avahi-daemon";
+      # Make NSS modules visible so that `avahi_nss_support ()' can
+      # return a sensible value.
+      environment.LD_LIBRARY_PATH = config.system.nssModules.path;
 
-        script =
-          ''
-            # Make NSS modules visible so that `avahi_nss_support ()' can
-            # return a sensible value.
-            export LD_LIBRARY_PATH="${config.system.nssModules.path}"
+      path = [ pkgs.coreutils pkgs.avahi ];
 
-            exec ${pkgs.avahi}/sbin/avahi-daemon --syslog -f "${avahiDaemonConf}"
-          '';
+      serviceConfig = {
+        NotifyAccess = "main";
+        BusName = "org.freedesktop.Avahi";
+        Type = "dbus";
+        ExecStart = "${pkgs.avahi}/sbin/avahi-daemon --syslog -f ${avahiDaemonConf}";
       };
+    };
 
     services.dbus.enable = true;
     services.dbus.packages = [ pkgs.avahi ];
 
-    # Enabling Avahi without exposing it in the firewall doesn't make
-    # sense.
-    networking.firewall.allowedUDPPorts = [ 5353 ];
-
+    networking.firewall.allowedUDPPorts = mkIf cfg.openFirewall [ 5353 ];
   };
-
 }
diff --git a/nixos/modules/services/networking/bind.nix b/nixos/modules/services/networking/bind.nix
index 7f89cff22329..06af4dbcca4e 100644
--- a/nixos/modules/services/networking/bind.nix
+++ b/nixos/modules/services/networking/bind.nix
@@ -33,7 +33,7 @@ let
       ${cfg.extraConfig}
 
       ${ concatMapStrings
-          ({ name, file, master ? true, slaves ? [], masters ? [] }:
+          ({ name, file, master ? true, slaves ? [], masters ? [], extraConfig ? "" }:
             ''
               zone "${name}" {
                 type ${if master then "master" else "slave"};
@@ -52,6 +52,7 @@ let
                    ''
                 }
                 allow-query { any; };
+                ${extraConfig}
               };
             '')
           cfg.zones }
@@ -131,6 +132,7 @@ in
           file = "/var/dns/example.com";
           masters = ["192.168.0.1"];
           slaves = [];
+          extraConfig = "";
         }];
       };
 
@@ -168,7 +170,9 @@ in
 
   ###### implementation
 
-  config = mkIf config.services.bind.enable {
+  config = mkIf cfg.enable {
+
+    networking.resolvconf.useLocalResolver = mkDefault true;
 
     users.users = singleton
       { name = bindUser;
diff --git a/nixos/modules/services/networking/bitcoind.nix b/nixos/modules/services/networking/bitcoind.nix
index e94265564595..d3501636b41d 100644
--- a/nixos/modules/services/networking/bitcoind.nix
+++ b/nixos/modules/services/networking/bitcoind.nix
@@ -28,7 +28,7 @@ let
     "-datadir=${cfg.dataDir}"
     "-pid=${pidFile}"
   ];
-  hexStr = types.strMatching "[0-9a-f]+";
+
   rpcUserOpts = { name, ... }: {
     options = {
       name = mkOption {
diff --git a/nixos/modules/services/networking/charybdis.nix b/nixos/modules/services/networking/charybdis.nix
index 3d02dc8d1375..e3aba063f87b 100644
--- a/nixos/modules/services/networking/charybdis.nix
+++ b/nixos/modules/services/networking/charybdis.nix
@@ -83,6 +83,10 @@ in
         gid = config.ids.gids.ircd;
       };
 
+      systemd.tmpfiles.rules = [
+        "d ${cfg.statedir} - ${cfg.user} ${cfg.group} - -"
+      ];
+
       systemd.services.charybdis = {
         description = "Charybdis IRC daemon";
         wantedBy = [ "multi-user.target" ];
@@ -93,12 +97,7 @@ in
           ExecStart   = "${charybdis}/bin/charybdis -foreground -logfile /dev/stdout -configfile ${configFile}";
           Group = cfg.group;
           User = cfg.user;
-          PermissionsStartOnly = true; # preStart needs to run with root permissions
         };
-        preStart = ''
-          ${coreutils}/bin/mkdir -p ${cfg.statedir}
-          ${coreutils}/bin/chown ${cfg.user}:${cfg.group} ${cfg.statedir}
-        '';
       };
 
     }
diff --git a/nixos/modules/services/networking/ddclient.nix b/nixos/modules/services/networking/ddclient.nix
index 9a2e13e9553c..04ce5ca3a874 100644
--- a/nixos/modules/services/networking/ddclient.nix
+++ b/nixos/modules/services/networking/ddclient.nix
@@ -20,8 +20,8 @@ let
     wildcard=YES
     quiet=${boolToStr cfg.quiet}
     verbose=${boolToStr cfg.verbose}
-    ${lib.concatStringsSep "," cfg.domains}
     ${cfg.extraConfig}
+    ${lib.concatStringsSep "," cfg.domains}
   '';
 
 in
diff --git a/nixos/modules/services/networking/dhcpcd.nix b/nixos/modules/services/networking/dhcpcd.nix
index c217ccaa405a..7b2786034552 100644
--- a/nixos/modules/services/networking/dhcpcd.nix
+++ b/nixos/modules/services/networking/dhcpcd.nix
@@ -162,7 +162,7 @@ in
 
         wantedBy = [ "multi-user.target" ] ++ optional (!hasDefaultGatewaySet) "network-online.target";
         wants = [ "network.target" "systemd-udev-settle.service" ];
-        before = [ "network.target" ];
+        before = [ "network-online.target" ];
         after = [ "systemd-udev-settle.service" ];
 
         # Stopping dhcpcd during a reconfiguration is undesirable
diff --git a/nixos/modules/services/networking/dnscrypt-proxy.xml b/nixos/modules/services/networking/dnscrypt-proxy.xml
index f90eef69848c..afc7880392a1 100644
--- a/nixos/modules/services/networking/dnscrypt-proxy.xml
+++ b/nixos/modules/services/networking/dnscrypt-proxy.xml
@@ -18,7 +18,7 @@
    To enable the client proxy, set
 <programlisting>
 <xref linkend="opt-services.dnscrypt-proxy.enable"/> = true;
-    </programlisting>
+</programlisting>
   </para>
 
   <para>
@@ -36,7 +36,7 @@
    the other client to it:
 <programlisting>
 <xref linkend="opt-services.dnscrypt-proxy.localPort"/> = 43;
-    </programlisting>
+</programlisting>
   </para>
 
   <sect2 xml:id="sec-dnscrypt-proxy-forwarder-dsnmasq">
@@ -47,7 +47,7 @@
   <xref linkend="opt-services.dnsmasq.enable"/> = true;
   <xref linkend="opt-services.dnsmasq.servers"/> = [ "127.0.0.1#43" ];
 }
-    </programlisting>
+</programlisting>
    </para>
   </sect2>
 
@@ -59,7 +59,7 @@
   <xref linkend="opt-services.unbound.enable"/> = true;
   <xref linkend="opt-services.unbound.forwardAddresses"/> = [ "127.0.0.1@43" ];
 }
-    </programlisting>
+</programlisting>
    </para>
   </sect2>
  </sect1>
diff --git a/nixos/modules/services/networking/dnsmasq.nix b/nixos/modules/services/networking/dnsmasq.nix
index 24d16046c63e..714a5903bff1 100644
--- a/nixos/modules/services/networking/dnsmasq.nix
+++ b/nixos/modules/services/networking/dnsmasq.nix
@@ -79,7 +79,7 @@ in
 
   ###### implementation
 
-  config = mkIf config.services.dnsmasq.enable {
+  config = mkIf cfg.enable {
 
     networking.nameservers =
       optional cfg.resolveLocalQueries "127.0.0.1";
@@ -92,6 +92,15 @@ in
       description = "Dnsmasq daemon user";
     };
 
+    networking.resolvconf = mkIf cfg.resolveLocalQueries {
+      useLocalResolver = mkDefault true;
+
+      extraConfig = ''
+        dnsmasq_conf=/etc/dnsmasq-conf.conf
+        dnsmasq_resolv=/etc/dnsmasq-resolv.conf
+      '';
+    };
+
     systemd.services.dnsmasq = {
         description = "Dnsmasq Daemon";
         after = [ "network.target" "systemd-resolved.service" ];
diff --git a/nixos/modules/services/networking/hostapd.nix b/nixos/modules/services/networking/hostapd.nix
index 7add48308f80..54a5bed2563f 100644
--- a/nixos/modules/services/networking/hostapd.nix
+++ b/nixos/modules/services/networking/hostapd.nix
@@ -30,10 +30,11 @@ let
     ctrl_interface=/run/hostapd
     ctrl_interface_group=${cfg.group}
 
-    ${if cfg.wpa then ''
+    ${optionalString cfg.wpa ''
       wpa=2
       wpa_passphrase=${cfg.wpaPassphrase}
-      '' else ""}
+    ''}
+    ${optionalString cfg.noScan "noscan=1"}
 
     ${cfg.extraConfig}
   '' ;
@@ -69,6 +70,14 @@ in
         '';
       };
 
+      noScan = mkOption {
+        default = false;
+        description = ''
+          Do not scan for overlapping BSSs in HT40+/- mode.
+          Caution: turning this on will violate regulatory requirements!
+        '';
+      };
+
       driver = mkOption {
         default = "nl80211";
         example = "hostapd";
@@ -162,6 +171,7 @@ in
         after = [ "sys-subsystem-net-devices-${escapedInterface}.device" ];
         bindsTo = [ "sys-subsystem-net-devices-${escapedInterface}.device" ];
         requiredBy = [ "network-link-${cfg.interface}.service" ];
+        wantedBy = [ "multi-user.target" ];
 
         serviceConfig =
           { ExecStart = "${pkgs.hostapd}/bin/hostapd ${configFile}";
diff --git a/nixos/modules/services/networking/iperf3.nix b/nixos/modules/services/networking/iperf3.nix
index 742404a5692f..0fe378b225d7 100644
--- a/nixos/modules/services/networking/iperf3.nix
+++ b/nixos/modules/services/networking/iperf3.nix
@@ -19,6 +19,11 @@ let
       default     = null;
       description = "Bind to the specific interface associated with the given address.";
     };
+    openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Open ports in the firewall for iperf3.";
+    };
     verbose = mkOption {
       type        = types.bool;
       default     = false;
@@ -52,6 +57,11 @@ let
   };
 
   imp = {
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.port ];
+    };
+
     systemd.services.iperf3 = {
       description = "iperf3 daemon";
       unitConfig.Documentation = "man:iperf3(1) https://iperf.fr/iperf-doc.php";
diff --git a/nixos/modules/services/networking/keybase.nix b/nixos/modules/services/networking/keybase.nix
index a149f16a84cb..85f52be8a6ac 100644
--- a/nixos/modules/services/networking/keybase.nix
+++ b/nixos/modules/services/networking/keybase.nix
@@ -26,6 +26,7 @@ in {
 
     systemd.user.services.keybase = {
       description = "Keybase service";
+      unitConfig.ConditionUser = "!@system";
       serviceConfig = {
         ExecStart = ''
           ${pkgs.keybase}/bin/keybase service --auto-forked
diff --git a/nixos/modules/services/networking/kresd.nix b/nixos/modules/services/networking/kresd.nix
index ca34ff9df4ef..fc516c01230a 100644
--- a/nixos/modules/services/networking/kresd.nix
+++ b/nixos/modules/services/networking/kresd.nix
@@ -80,8 +80,11 @@ in
         # Syntax depends on being IPv6 or IPv4.
         (iface: if elem ":" (stringToCharacters iface) then "[${iface}]:53" else "${iface}:53")
         cfg.interfaces;
-      socketConfig.ListenDatagram = listenStreams;
-      socketConfig.FreeBind = true;
+      socketConfig = {
+        ListenDatagram = listenStreams;
+        FreeBind = true;
+        FileDescriptorName = "dns";
+      };
     };
 
     systemd.sockets.kresd-tls = mkIf (cfg.listenTLS != []) rec {
diff --git a/nixos/modules/services/networking/minidlna.nix b/nixos/modules/services/networking/minidlna.nix
index 1858f03cac1f..ed0c1044a570 100644
--- a/nixos/modules/services/networking/minidlna.nix
+++ b/nixos/modules/services/networking/minidlna.nix
@@ -98,16 +98,10 @@ in
         wantedBy = [ "multi-user.target" ];
         after = [ "network.target" "local-fs.target" ];
 
-        preStart =
-          ''
-            mkdir -p /var/cache/minidlna
-            chown -R minidlna:minidlna /var/cache/minidlna
-          '';
-
         serviceConfig =
           { User = "minidlna";
             Group = "minidlna";
-            PermissionsStartOnly = true;
+            CacheDirectory = "minidlna";
             RuntimeDirectory = "minidlna";
             PIDFile = "/run/minidlna/pid";
             ExecStart =
diff --git a/nixos/modules/services/networking/networkmanager.nix b/nixos/modules/services/networking/networkmanager.nix
index f1ba7dd4e4f2..551636a33d25 100644
--- a/nixos/modules/services/networking/networkmanager.nix
+++ b/nixos/modules/services/networking/networkmanager.nix
@@ -1,6 +1,5 @@
 { config, lib, pkgs, ... }:
 
-with pkgs;
 with lib;
 
 let
@@ -12,11 +11,13 @@ let
   # /var/lib/misc is for dnsmasq.leases.
   stateDirs = "/var/lib/NetworkManager /var/lib/dhclient /var/lib/misc";
 
-  configFile = writeText "NetworkManager.conf" ''
+  configFile = pkgs.writeText "NetworkManager.conf" ''
     [main]
     plugins=keyfile
     dhcp=${cfg.dhcp}
     dns=${cfg.dns}
+    # If resolvconf is disabled that means that resolv.conf is managed by some other module.
+    rc-manager=${if config.networking.resolvconf.enable then "resolvconf" else "unmanaged"}
 
     [keyfile]
     ${optionalString (cfg.unmanaged != [])
@@ -64,19 +65,19 @@ let
     });
   '';
 
-  ns = xs: writeText "nameservers" (
+  ns = xs: pkgs.writeText "nameservers" (
     concatStrings (map (s: "nameserver ${s}\n") xs)
   );
 
-  overrideNameserversScript = writeScript "02overridedns" ''
+  overrideNameserversScript = pkgs.writeScript "02overridedns" ''
     #!/bin/sh
-    tmp=`${coreutils}/bin/mktemp`
-    ${gnused}/bin/sed '/nameserver /d' /etc/resolv.conf > $tmp
-    ${gnugrep}/bin/grep 'nameserver ' /etc/resolv.conf | \
-      ${gnugrep}/bin/grep -vf ${ns (cfg.appendNameservers ++ cfg.insertNameservers)} > $tmp.ns
-    ${optionalString (cfg.appendNameservers != []) "${coreutils}/bin/cat $tmp $tmp.ns ${ns cfg.appendNameservers} > /etc/resolv.conf"}
-    ${optionalString (cfg.insertNameservers != []) "${coreutils}/bin/cat $tmp ${ns cfg.insertNameservers} $tmp.ns > /etc/resolv.conf"}
-    ${coreutils}/bin/rm -f $tmp $tmp.ns
+    PATH=${with pkgs; makeBinPath [ gnused gnugrep coreutils ]}
+    tmp=$(mktemp)
+    sed '/nameserver /d' /etc/resolv.conf > $tmp
+    grep 'nameserver ' /etc/resolv.conf | \
+      grep -vf ${ns (cfg.appendNameservers ++ cfg.insertNameservers)} > $tmp.ns
+    cat $tmp ${ns cfg.insertNameservers} $tmp.ns ${ns cfg.appendNameservers} > /etc/resolv.conf
+    rm -f $tmp $tmp.ns
   '';
 
   dispatcherTypesSubdirMap = {
@@ -90,11 +91,29 @@ let
     default = "preserve";
     example = "00:11:22:33:44:55";
     description = ''
-      "XX:XX:XX:XX:XX:XX": MAC address of the interface.
-      <literal>permanent</literal>: use the permanent MAC address of the device.
-      <literal>preserve</literal>: don’t change the MAC address of the device upon activation.
-      <literal>random</literal>: generate a randomized value upon each connect.
-      <literal>stable</literal>: generate a stable, hashed MAC address.
+      Set the MAC address of the interface.
+      <variablelist>
+        <varlistentry>
+          <term>"XX:XX:XX:XX:XX:XX"</term>
+          <listitem><para>MAC address of the interface</para></listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>"permanent"</literal></term>
+          <listitem><para>Use the permanent MAC address of the device</para></listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>"preserve"</literal></term>
+          <listitem><para>Don’t change the MAC address of the device upon activation</para></listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>"random"</literal></term>
+          <listitem><para>Generate a randomized value upon each connect</para></listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>"stable"</literal></term>
+          <listitem><para>Generate a stable, hashed MAC address</para></listitem>
+        </varlistentry>
+      </variablelist>
     '';
   };
 
@@ -123,6 +142,16 @@ in {
         default = "";
         description = ''
           Configuration appended to the generated NetworkManager.conf.
+          Refer to
+          <link xlink:href="https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html">
+            https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html
+          </link>
+          or
+          <citerefentry>
+            <refentrytitle>NetworkManager.conf</refentrytitle>
+            <manvolnum>5</manvolnum>
+          </citerefentry>
+          for more information.
         '';
       };
 
@@ -131,15 +160,24 @@ in {
         default = [];
         description = ''
           List of interfaces that will not be managed by NetworkManager.
-          Interface name can be specified here, but if you need more fidelity
-          see "Device List Format" in NetworkManager.conf man page.
+          Interface name can be specified here, but if you need more fidelity,
+          refer to
+          <link xlink:href="https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html#device-spec">
+            https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html#device-spec
+          </link>
+          or the "Device List Format" Appendix of
+          <citerefentry>
+            <refentrytitle>NetworkManager.conf</refentrytitle>
+            <manvolnum>5</manvolnum>
+          </citerefentry>.
         '';
       };
 
       # Ugly hack for using the correct gnome3 packageSet
       basePackages = mkOption {
         type = types.attrsOf types.package;
-        default = { inherit networkmanager modemmanager wpa_supplicant
+        default = { inherit (pkgs)
+                            networkmanager modemmanager wpa_supplicant crda
                             networkmanager-openvpn networkmanager-vpnc
                             networkmanager-openconnect networkmanager-fortisslvpn
                             networkmanager-l2tp networkmanager-iodine; };
@@ -219,67 +257,15 @@ in {
           Set the DNS (<literal>resolv.conf</literal>) processing mode.
           </para>
           <para>
-          Options:
-          <variablelist>
-          <varlistentry>
-            <term><literal>"default"</literal></term>
-            <listitem><para>
-              NetworkManager will update <literal>/etc/resolv.conf</literal> to
-              reflect the nameservers provided by currently active connections.
-            </para></listitem>
-          </varlistentry>
-          <varlistentry>
-            <term><literal>"dnsmasq"</literal></term>
-            <listitem>
-              <para>
-                Enable NetworkManager's dnsmasq integration. NetworkManager will
-                run dnsmasq as a local caching nameserver, using a "split DNS"
-                configuration if you are connected to a VPN, and then update
-                <literal>resolv.conf</literal> to point to the local nameserver.
-              </para>
-              <para>
-                It is possible to pass custom options to the dnsmasq instance by
-                adding them to files in the
-                <literal>/etc/NetworkManager/dnsmasq.d/</literal> directory.
-              </para>
-              <para>
-                When multiple upstream servers are available, dnsmasq will
-                initially contact them in parallel and then use the fastest to
-                respond, probing again other servers after some time.  This
-                behavior can be modified passing the
-                <literal>all-servers</literal> or <literal>strict-order</literal>
-                options to dnsmasq (see the manual page for more details).
-              </para>
-              <para>
-                Note that this option causes NetworkManager to launch and manage
-                its own instance of the dnsmasq daemon, which is
-                <emphasis>not</emphasis> the same as setting
-                <literal>services.dnsmasq.enable = true;</literal>.
-              </para>
-            </listitem>
-          </varlistentry>
-          <varlistentry>
-            <term><literal>"unbound"</literal></term>
-            <listitem><para>
-              NetworkManager will talk to unbound and dnssec-triggerd,
-              providing a "split DNS" configuration with DNSSEC support.
-              <literal>/etc/resolv.conf</literal> will be managed by
-              dnssec-trigger daemon.
-            </para></listitem>
-          </varlistentry>
-          <varlistentry>
-            <term><literal>"systemd-resolved"</literal></term>
-            <listitem><para>
-              NetworkManager will push the DNS configuration to systemd-resolved.
-            </para></listitem>
-          </varlistentry>
-          <varlistentry>
-            <term><literal>"none"</literal></term>
-            <listitem><para>
-              NetworkManager will not modify resolv.conf.
-            </para></listitem>
-          </varlistentry>
-          </variablelist>
+          A description of these modes can be found in the main section of
+          <link xlink:href="https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html">
+            https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html
+          </link>
+          or in
+          <citerefentry>
+            <refentrytitle>NetworkManager.conf</refentrytitle>
+            <manvolnum>5</manvolnum>
+          </citerefentry>.
         '';
       };
 
@@ -421,13 +407,10 @@ in {
       { source = "${networkmanager-l2tp}/lib/NetworkManager/VPN/nm-l2tp-service.name";
         target = "NetworkManager/VPN/nm-l2tp-service.name";
       }
-      { source = "${networkmanager_strongswan}/lib/NetworkManager/VPN/nm-strongswan-service.name";
-        target = "NetworkManager/VPN/nm-strongswan-service.name";
-      }
       { source = "${networkmanager-iodine}/lib/NetworkManager/VPN/nm-iodine-service.name";
         target = "NetworkManager/VPN/nm-iodine-service.name";
       }
-    ] ++ optional (cfg.appendNameservers == [] || cfg.insertNameservers == [])
+    ] ++ optional (cfg.appendNameservers != [] || cfg.insertNameservers != [])
            { source = overrideNameserversScript;
              target = "NetworkManager/dispatcher.d/02overridedns";
            }
@@ -436,11 +419,15 @@ in {
         target = "NetworkManager/dispatcher.d/${dispatcherTypesSubdirMap.${s.type}}03userscript${lib.fixedWidthNumber 4 i}";
         mode = "0544";
       }) cfg.dispatcherScripts
-      ++ optional (dynamicHostsEnabled)
+      ++ optional dynamicHostsEnabled
            { target = "NetworkManager/dnsmasq.d/dyndns.conf";
              text = concatMapStrings (n: ''
                hostsdir=/run/NetworkManager/hostsdirs/${n}
              '') (attrNames cfg.dynamicHosts.hostsDirs);
+           }
+      ++ optional cfg.enableStrongSwan
+           { source = "${pkgs.networkmanager_strongswan}/lib/NetworkManager/VPN/nm-strongswan-service.name";
+             target = "NetworkManager/VPN/nm-strongswan-service.name";
            };
 
     environment.systemPackages = cfg.packages;
@@ -508,7 +495,7 @@ in {
     networking = {
       useDHCP = false;
       # use mkDefault to trigger the assertion about the conflict above
-      wireless.enable = lib.mkDefault false;
+      wireless.enable = mkDefault false;
     };
 
     security.polkit.extraConfig = polkitConf;
diff --git a/nixos/modules/services/networking/quassel.nix b/nixos/modules/services/networking/quassel.nix
index b223a48e0550..b495b3948fb5 100644
--- a/nixos/modules/services/networking/quassel.nix
+++ b/nixos/modules/services/networking/quassel.nix
@@ -104,6 +104,10 @@ in
         gid = config.ids.gids.quassel;
       }];
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' - ${user} - - -"
+    ];
+
     systemd.services.quassel =
       { description = "Quassel IRC client daemon";
 
@@ -111,11 +115,6 @@ in
         after = [ "network.target" ] ++ optional config.services.postgresql.enable "postgresql.service"
                                      ++ optional config.services.mysql.enable "mysql.service";
 
-        preStart = ''
-          mkdir -p ${cfg.dataDir}
-          chown ${user} ${cfg.dataDir}
-        '';
-
         serviceConfig =
         {
           ExecStart = concatStringsSep " " ([
@@ -126,7 +125,6 @@ in
           ] ++ optional cfg.requireSSL "--require-ssl"
             ++ optional (cfg.certificateFile != null) "--ssl-cert=${cfg.certificateFile}");
           User = user;
-          PermissionsStartOnly = true;
         };
       };
 
diff --git a/nixos/modules/services/networking/rdnssd.nix b/nixos/modules/services/networking/rdnssd.nix
index 887772f6e5f0..bccab805beeb 100644
--- a/nixos/modules/services/networking/rdnssd.nix
+++ b/nixos/modules/services/networking/rdnssd.nix
@@ -35,6 +35,11 @@ in
 
   config = mkIf config.services.rdnssd.enable {
 
+    assertions = [{
+      assertion = config.networking.resolvconf.enable;
+      message = "rdnssd needs resolvconf to work (probably something sets up a static resolv.conf)";
+    }];
+
     systemd.services.rdnssd = {
       description = "RDNSS daemon";
       after = [ "network.target" ];
diff --git a/nixos/modules/services/networking/smokeping.nix b/nixos/modules/services/networking/smokeping.nix
index fab3ed5bb39d..c41d0edaf17f 100644
--- a/nixos/modules/services/networking/smokeping.nix
+++ b/nixos/modules/services/networking/smokeping.nix
@@ -101,17 +101,17 @@ in
         '';
         example = literalExample ''
           # near constant pings.
-					step     = 30
-					pings    = 20
-					# consfn mrhb steps total
-					AVERAGE  0.5   1  10080
-					AVERAGE  0.5  12  43200
-							MIN  0.5  12  43200
-							MAX  0.5  12  43200
-					AVERAGE  0.5 144   7200
-							MAX  0.5 144   7200
-							MIN  0.5 144   7200
-				'';
+          step     = 30
+          pings    = 20
+          # consfn mrhb steps total
+          AVERAGE  0.5   1  10080
+          AVERAGE  0.5  12  43200
+              MIN  0.5  12  43200
+              MAX  0.5  12  43200
+          AVERAGE  0.5 144   7200
+              MAX  0.5 144   7200
+              MIN  0.5 144   7200
+        '';
         description = ''Configure the ping frequency and retention of the rrd files.
           Once set, changing the interval will require deletion or migration of all
           the collected data.'';
diff --git a/nixos/modules/services/networking/squid.nix b/nixos/modules/services/networking/squid.nix
index b220c21b604f..9d063b92aa1e 100644
--- a/nixos/modules/services/networking/squid.nix
+++ b/nixos/modules/services/networking/squid.nix
@@ -159,11 +159,10 @@ in
       serviceConfig = {
         Type="forking";
         PIDFile="/run/squid.pid";
-        PermissionsStartOnly = true;
         ExecStart  = "${pkgs.squid}/bin/squid -YCs -f ${squidConfig}";
       };
     };
 
   };
 
-}
\ No newline at end of file
+}
diff --git a/nixos/modules/services/networking/ssh/sshd.nix b/nixos/modules/services/networking/ssh/sshd.nix
index 89f56a726f46..0f9d2420903b 100644
--- a/nixos/modules/services/networking/ssh/sshd.nix
+++ b/nixos/modules/services/networking/ssh/sshd.nix
@@ -4,7 +4,15 @@ with lib;
 
 let
 
-  sshconf = pkgs.runCommand "sshd.conf-validated" { nativeBuildInputs = [ cfgc.package ]; } ''
+  # The splicing information needed for nativeBuildInputs isn't available
+  # on the derivations likely to be used as `cfgc.package`.
+  # This middle-ground solution ensures *an* sshd can do their basic validation
+  # on the configuration.
+  validationPackage = if pkgs.stdenv.buildPlatform == pkgs.stdenv.hostPlatform
+    then [ cfgc.package ]
+    else [ pkgs.buildPackages.openssh ];
+
+  sshconf = pkgs.runCommand "sshd.conf-validated" { nativeBuildInputs = [ validationPackage ]; } ''
     cat >$out <<EOL
     ${cfg.extraConfig}
     EOL
diff --git a/nixos/modules/services/networking/syncthing.nix b/nixos/modules/services/networking/syncthing.nix
index d78a54a3327b..126f5b7b527b 100644
--- a/nixos/modules/services/networking/syncthing.nix
+++ b/nixos/modules/services/networking/syncthing.nix
@@ -291,7 +291,7 @@ in {
 
       group = mkOption {
         type = types.str;
-        default = "nogroup";
+        default = defaultUser;
         description = ''
           Syncthing will be run under this group (group will not be created if it doesn't exist.
           This can be your user name).
@@ -372,16 +372,18 @@ in {
 
     systemd.packages = [ pkgs.syncthing ];
 
-    users = mkIf (cfg.systemService && cfg.user == defaultUser) {
-      users."${defaultUser}" =
+    users.users = mkIf (cfg.systemService && cfg.user == defaultUser) {
+      "${defaultUser}" =
         { group = cfg.group;
           home  = cfg.dataDir;
           createHome = true;
           uid = config.ids.uids.syncthing;
           description = "Syncthing daemon user";
         };
+    };
 
-      groups."${defaultUser}".gid =
+    users.groups = mkIf (cfg.systemService && cfg.group == defaultUser) {
+      "${defaultUser}".gid =
         config.ids.gids.syncthing;
     };
 
@@ -403,18 +405,12 @@ in {
           Group = cfg.group;
           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}
+              install -dm700 -o ${cfg.user} -g ${cfg.group} ${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
+                install -Dm400 -o ${cfg.user} -g ${cfg.group} ${toString cfg.declarative.cert} ${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
+                install -Dm400 -o ${cfg.user} -g ${cfg.group} ${toString cfg.declarative.key} ${cfg.configDir}/key.pem
               ''}
             ''}"
           ;
diff --git a/nixos/modules/services/networking/teamspeak3.nix b/nixos/modules/services/networking/teamspeak3.nix
index 9ea9c83e37cd..fadb32dcd777 100644
--- a/nixos/modules/services/networking/teamspeak3.nix
+++ b/nixos/modules/services/networking/teamspeak3.nix
@@ -111,16 +111,15 @@ in
       gid = config.ids.gids.teamspeak;
     };
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.logPath}' - ${user} ${group} - -"
+    ];
+
     systemd.services.teamspeak3-server = {
       description = "Teamspeak3 voice communication server daemon";
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
 
-      preStart = ''
-        mkdir -p ${cfg.logPath}
-        chown ${user}:${group} ${cfg.logPath}
-      '';
-
       serviceConfig = {
         ExecStart = ''
           ${ts3}/bin/ts3server \
@@ -135,7 +134,6 @@ in
         WorkingDirectory = cfg.dataDir;
         User = user;
         Group = group;
-        PermissionsStartOnly = true;
       };
     };
   };
diff --git a/nixos/modules/services/networking/thelounge.nix b/nixos/modules/services/networking/thelounge.nix
new file mode 100644
index 000000000000..b1d23372955e
--- /dev/null
+++ b/nixos/modules/services/networking/thelounge.nix
@@ -0,0 +1,75 @@
+{ pkgs, lib, config, ... }:
+
+with lib;
+
+let
+  cfg = config.services.thelounge;
+  dataDir = "/var/lib/thelounge";
+  configJsData = "module.exports = " + builtins.toJSON (
+    { private = cfg.private; port = cfg.port; } // cfg.extraConfig
+  );
+in {
+  options.services.thelounge = {
+    enable = mkEnableOption "The Lounge web IRC client";
+
+    private = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Make your The Lounge instance private. You will need to configure user
+        accounts by using the (<command>thelounge</command>) command or by adding
+        entries in <filename>${dataDir}/users</filename>. You might need to restart
+        The Lounge after making changes to the state directory.
+      '';
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 9000;
+      description = "TCP port to listen on for http connections.";
+    };
+
+    extraConfig = mkOption {
+      default = {};
+      type = types.attrs;
+      example = literalExample ''{
+        reverseProxy = true;
+        defaults = {
+          name = "Your Network";
+          host = "localhost";
+          port = 6697;
+        };
+      }'';
+      description = ''
+        The Lounge's <filename>config.js</filename> contents as attribute set (will be
+        converted to JSON to generate the configuration file).
+
+        The options defined here will be merged to the default configuration file.
+        Note: In case of duplicate configuration, options from <option>extraConfig</option> have priority.
+
+        Documentation: <link xlink:href="https://thelounge.chat/docs/server/configuration" />
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.users.thelounge = {
+      description = "thelounge service user";
+      group = "thelounge";
+    };
+    users.groups.thelounge = {};
+    systemd.services.thelounge = {
+      description = "The Lounge web IRC client";
+      wantedBy = [ "multi-user.target" ];
+      environment = { THELOUNGE_HOME = dataDir; };
+      preStart = "ln -sf ${pkgs.writeText "config.js" configJsData} ${dataDir}/config.js";
+      serviceConfig = {
+        User = "thelounge";
+        StateDirectory = baseNameOf dataDir;
+        ExecStart = "${pkgs.thelounge}/bin/thelounge start";
+      };
+    };
+
+    environment.systemPackages = [ pkgs.thelounge ];
+  };
+}
diff --git a/nixos/modules/services/networking/unbound.nix b/nixos/modules/services/networking/unbound.nix
index 1a35979ad44c..3cf82e8839bb 100644
--- a/nixos/modules/services/networking/unbound.nix
+++ b/nixos/modules/services/networking/unbound.nix
@@ -101,6 +101,8 @@ in
       isSystemUser = true;
     };
 
+    networking.resolvconf.useLocalResolver = mkDefault true;
+
     systemd.services.unbound = {
       description = "Unbound recursive Domain Name Server";
       after = [ "network.target" ];
diff --git a/nixos/modules/services/networking/unifi.nix b/nixos/modules/services/networking/unifi.nix
index c82e0af2803d..6239c88b7e41 100644
--- a/nixos/modules/services/networking/unifi.nix
+++ b/nixos/modules/services/networking/unifi.nix
@@ -146,6 +146,11 @@ in
         where = where;
       }) mountPoints;
 
+    systemd.tmpfiles.rules = [
+      "e '${stateDir}' 0700 unifi - - -"
+      "d '${stateDir}/data' 0700 unifi - - -"
+    ];
+
     systemd.services.unifi = {
       description = "UniFi controller daemon";
       wantedBy = [ "multi-user.target" ];
@@ -157,14 +162,9 @@ in
       environment.LD_LIBRARY_PATH = with pkgs.stdenv; "${cc.cc.lib}/lib";
 
       preStart = ''
-        # Ensure privacy of state and data.
-        chown unifi "${stateDir}" "${stateDir}/data"
-        chmod 0700 "${stateDir}" "${stateDir}/data"
-
         # Create the volatile webapps
         rm -rf "${stateDir}/webapps"
         mkdir -p "${stateDir}/webapps"
-        chown unifi "${stateDir}/webapps"
         ln -s "${cfg.unifiPackage}/webapps/ROOT" "${stateDir}/webapps/ROOT"
       '';
 
@@ -177,7 +177,6 @@ in
         ExecStart = "${(removeSuffix "\n" cmd)} start";
         ExecStop = "${(removeSuffix "\n" cmd)} stop";
         User = "unifi";
-        PermissionsStartOnly = true;
         UMask = "0077";
         WorkingDirectory = "${stateDir}";
       };
diff --git a/nixos/modules/services/networking/wpa_supplicant.nix b/nixos/modules/services/networking/wpa_supplicant.nix
index 0bd9edf4a41c..63e59e7c8fac 100644
--- a/nixos/modules/services/networking/wpa_supplicant.nix
+++ b/nixos/modules/services/networking/wpa_supplicant.nix
@@ -204,6 +204,7 @@ in {
     environment.systemPackages =  [ pkgs.wpa_supplicant ];
 
     services.dbus.packages = [ pkgs.wpa_supplicant ];
+    services.udev.packages = [ pkgs.crda ];
 
     # FIXME: start a separate wpa_supplicant instance per interface.
     systemd.services.wpa_supplicant = let
diff --git a/nixos/modules/services/networking/zeronet.nix b/nixos/modules/services/networking/zeronet.nix
index 611a51c74ce2..f4988a902685 100644
--- a/nixos/modules/services/networking/zeronet.nix
+++ b/nixos/modules/services/networking/zeronet.nix
@@ -86,20 +86,17 @@ in with lib; {
       '';
     };
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' 750 zeronet zeronet - -"
+      "d '${cfg.logDir}' 750 zeronet zeronet - -"
+    ];
+
     systemd.services.zeronet = {
       description = "zeronet";
       after = [ "network.target" (optionalString cfg.tor "tor.service") ];
       wantedBy = [ "multi-user.target" ];
 
-      preStart = ''
-        # Ensure folder exists or create it and permissions are correct
-        mkdir -p ${escapeShellArg cfg.dataDir} ${escapeShellArg cfg.logDir}
-        chmod 750 ${escapeShellArg cfg.dataDir} ${escapeShellArg cfg.logDir}
-        chown zeronet:zeronet ${escapeShellArg cfg.dataDir} ${escapeShellArg cfg.logDir}
-      '';
-
       serviceConfig = {
-        PermissionsStartOnly = true;
         PrivateTmp = "yes";
         User = "zeronet";
         Group = "zeronet";
diff --git a/nixos/modules/services/security/sshguard.nix b/nixos/modules/services/security/sshguard.nix
index 3892cd5c72b8..25cec5b5b105 100644
--- a/nixos/modules/services/security/sshguard.nix
+++ b/nixos/modules/services/security/sshguard.nix
@@ -107,8 +107,6 @@ in {
       path = with pkgs; [ iptables ipset iproute systemd ];
 
       postStart = ''
-        ${pkgs.ipset}/bin/ipset -quiet create -exist sshguard4 hash:ip family inet
-        ${pkgs.ipset}/bin/ipset -quiet create -exist sshguard6 hash:ip family inet6
         ${pkgs.iptables}/bin/iptables  -I INPUT -m set --match-set sshguard4 src -j DROP
         ${pkgs.iptables}/bin/ip6tables -I INPUT -m set --match-set sshguard6 src -j DROP
       '';
diff --git a/nixos/modules/services/security/tor.nix b/nixos/modules/services/security/tor.nix
index 6f4852c3ba1a..abdc0cd78b4d 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 ${concatStringsSep "," cfg.relay.bridgeTransports} exec ${obfs4}/bin/obfs4proxy managed
+      ServerTransportPlugin ${concatStringsSep "," cfg.relay.bridgeTransports} exec ${pkgs.obfs4}/bin/obfs4proxy managed
       ExtORPort auto
       ${optionalString (cfg.relay.role == "private-bridge") ''
         ExtraInfoStatistics 0
diff --git a/nixos/modules/services/system/kerberos/default.nix b/nixos/modules/services/system/kerberos/default.nix
index 26ac85de402f..c55241c4cff1 100644
--- a/nixos/modules/services/system/kerberos/default.nix
+++ b/nixos/modules/services/system/kerberos/default.nix
@@ -1,4 +1,4 @@
-{pkgs, config, lib, ...}:
+{config, lib, ...}:
 
 let
   inherit (lib) mkOption mkIf types length attrNames;
diff --git a/nixos/modules/services/system/kerberos/heimdal.nix b/nixos/modules/services/system/kerberos/heimdal.nix
index d0f470f836ed..f0e56c7951a4 100644
--- a/nixos/modules/services/system/kerberos/heimdal.nix
+++ b/nixos/modules/services/system/kerberos/heimdal.nix
@@ -2,7 +2,7 @@
 
 let
   inherit (lib) mkIf concatStringsSep concatMapStrings toList mapAttrs
-    mapAttrsToList attrValues;
+    mapAttrsToList;
   cfg = config.services.kerberos_server;
   kerberos = config.krb5.kerberos;
   stateDir = "/var/heimdal";
diff --git a/nixos/modules/services/system/kerberos/mit.nix b/nixos/modules/services/system/kerberos/mit.nix
index a53d9dd0c6b5..25d7d51e808a 100644
--- a/nixos/modules/services/system/kerberos/mit.nix
+++ b/nixos/modules/services/system/kerberos/mit.nix
@@ -2,7 +2,7 @@
 
 let
   inherit (lib) mkIf concatStrings concatStringsSep concatMapStrings toList
-    mapAttrs mapAttrsToList attrValues;
+    mapAttrs mapAttrsToList;
   cfg = config.services.kerberos_server;
   kerberos = config.krb5.kerberos;
   stateDir = "/var/lib/krb5kdc";
diff --git a/nixos/modules/services/system/localtime.nix b/nixos/modules/services/system/localtime.nix
index 8e9286b94078..04595fc82fbb 100644
--- a/nixos/modules/services/system/localtime.nix
+++ b/nixos/modules/services/system/localtime.nix
@@ -28,33 +28,16 @@ in {
       };
     };
 
-    # so polkit will pick up the rules
-    environment.systemPackages = [ pkgs.localtime ];
-
-    users.users = [{
-      name = "localtimed";
-      description = "Taskserver user";
-    }];
+    # We use the 'out' output, since localtime has its 'bin' output
+    # first, so that is what we get if we use the derivation bare.
+    # Install the polkit rules.
+    environment.systemPackages = [ pkgs.localtime.out ];
+    # Install the systemd unit.
+    systemd.packages = [ pkgs.localtime.out ];
 
     systemd.services.localtime = {
-      description = "localtime service";
       wantedBy = [ "multi-user.target" ];
-      partOf = [ "geoclue.service "];
-
-      serviceConfig = {
-        Restart                 = "on-failure";
-        # TODO: make it work with dbus
-        #DynamicUser             = true;
-        Nice                    = 10;
-        User                    = "localtimed";
-        PrivateTmp              = "yes";
-        PrivateDevices          = true;
-        PrivateNetwork          = "yes";
-        NoNewPrivileges         = "yes";
-        ProtectSystem           = "strict";
-        ProtectHome             = true;
-        ExecStart               = "${pkgs.localtime}/bin/localtimed";
-      };
+      serviceConfig.Restart = "on-failure";
     };
   };
 }
diff --git a/nixos/modules/services/system/nscd.conf b/nixos/modules/services/system/nscd.conf
index 603a5d01acce..2b7523a7346d 100644
--- a/nixos/modules/services/system/nscd.conf
+++ b/nixos/modules/services/system/nscd.conf
@@ -7,46 +7,28 @@
 # is not aware of the path in which the nss modules live.  As a workaround, we
 # have `enable-cache yes` with an explicit ttl of 0
 server-user             nscd
-threads                 1
-paranoia                no
-debug-level             0
 
 enable-cache            passwd          yes
 positive-time-to-live   passwd          0
 negative-time-to-live   passwd          0
-suggested-size          passwd          211
-check-files             passwd          yes
-persistent              passwd          no
 shared                  passwd          yes
 
 enable-cache            group           yes
 positive-time-to-live   group           0
 negative-time-to-live   group           0
-suggested-size          group           211
-check-files             group           yes
-persistent              group           no
 shared                  group           yes
 
 enable-cache            netgroup        yes
 positive-time-to-live   netgroup        0
 negative-time-to-live   netgroup        0
-suggested-size          netgroup        211
-check-files             netgroup        yes
-persistent              netgroup        no
 shared                  netgroup        yes
 
 enable-cache            hosts           yes
 positive-time-to-live   hosts           600
 negative-time-to-live   hosts           0
-suggested-size          hosts           211
-check-files             hosts           yes
-persistent              hosts           no
 shared                  hosts           yes
 
 enable-cache            services        yes
 positive-time-to-live   services        0
 negative-time-to-live   services        0
-suggested-size          services        211
-check-files             services        yes
-persistent              services        no
 shared                  services        yes
diff --git a/nixos/modules/services/system/nscd.nix b/nixos/modules/services/system/nscd.nix
index fd1570d11980..e11f7e049d8f 100644
--- a/nixos/modules/services/system/nscd.nix
+++ b/nixos/modules/services/system/nscd.nix
@@ -39,11 +39,6 @@ in
   config = mkIf cfg.enable {
     environment.etc."nscd.conf".text = cfg.config;
 
-    users.users.nscd =
-      { isSystemUser = true;
-        description = "Name service cache daemon user";
-      };
-
     systemd.services.nscd =
       { description = "Name Service Cache Daemon";
 
@@ -51,22 +46,23 @@ in
 
         environment = { LD_LIBRARY_PATH = nssModulesPath; };
 
-        preStart =
-          ''
-            mkdir -m 0755 -p /run/nscd
-            rm -f /run/nscd/nscd.pid
-            mkdir -m 0755 -p /var/db/nscd
-          '';
-
         restartTriggers = [
           config.environment.etc.hosts.source
           config.environment.etc."nsswitch.conf".source
           config.environment.etc."nscd.conf".source
         ];
 
+        # We use DynamicUser because in default configurations nscd doesn't
+        # create any files that need to survive restarts. However, in some
+        # configurations, nscd needs to be started as root; it will drop
+        # privileges after all the NSS modules have read their configuration
+        # files. So prefix the ExecStart command with "!" to prevent systemd
+        # from dropping privileges early. See ExecStart in systemd.service(5).
         serviceConfig =
-          { ExecStart = "@${pkgs.glibc.bin}/sbin/nscd nscd";
+          { ExecStart = "!@${pkgs.glibc.bin}/sbin/nscd nscd";
             Type = "forking";
+            DynamicUser = true;
+            RuntimeDirectory = "nscd";
             PIDFile = "/run/nscd/nscd.pid";
             Restart = "always";
             ExecReload =
@@ -75,15 +71,6 @@ in
                 "${pkgs.glibc.bin}/sbin/nscd --invalidate hosts"
               ];
           };
-
-        # Urgggggh... Nscd forks before opening its socket and writing
-        # its pid. So wait until it's ready.
-        postStart =
-          ''
-            while ! ${pkgs.glibc.bin}/sbin/nscd -g > /dev/null; do
-              sleep 0.2
-            done
-          '';
       };
 
   };
diff --git a/nixos/modules/services/torrent/deluge.nix b/nixos/modules/services/torrent/deluge.nix
index 01a5890a7845..48ec4d692e2f 100644
--- a/nixos/modules/services/torrent/deluge.nix
+++ b/nixos/modules/services/torrent/deluge.nix
@@ -118,36 +118,74 @@ in {
             more informations.
           '';
         };
+
+        user = mkOption {
+          type = types.str;
+          default = "deluge";
+          description = ''
+            User account under which deluge runs.
+          '';
+        };
+
+        group = mkOption {
+          type = types.str;
+          default = "deluge";
+          description = ''
+            Group under which deluge runs.
+          '';
+        };
+
+        extraPackages = mkOption {
+          type = types.listOf types.package;
+          default = [];
+          description = ''
+            Extra packages available at runtime to enable Deluge's plugins. For example,
+            extraction utilities are required for the built-in "Extractor" plugin.
+            This always contains unzip, gnutar, xz, p7zip and bzip2.
+          '';
+        };
       };
 
       deluge.web = {
         enable = mkEnableOption "Deluge Web daemon";
+
         port = mkOption {
-        type = types.port;
+          type = types.port;
           default = 8112;
           description = ''
             Deluge web UI port.
           '';
         };
+
+        openFirewall = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Open ports in the firewall for deluge web daemon
+          '';
+        };
       };
     };
   };
 
   config = mkIf cfg.enable {
 
-    systemd.tmpfiles.rules = [ "d '${configDir}' 0770 deluge deluge" ]
+    # Provide a default set of `extraPackages`.
+    services.deluge.extraPackages = with pkgs; [ unzip gnutar xz p7zip bzip2 ];
+
+    systemd.tmpfiles.rules = [ "d '${configDir}' 0770 ${cfg.user} ${cfg.group}" ]
     ++ optional (cfg.config ? "download_location")
-      "d '${cfg.config.download_location}' 0770 deluge deluge"
+      "d '${cfg.config.download_location}' 0770 ${cfg.user} ${cfg.group}"
     ++ optional (cfg.config ? "torrentfiles_location")
-      "d '${cfg.config.torrentfiles_location}' 0770 deluge deluge"
+      "d '${cfg.config.torrentfiles_location}' 0770 ${cfg.user} ${cfg.group}"
     ++ optional (cfg.config ? "move_completed_path")
-      "d '${cfg.config.move_completed_path}' 0770 deluge deluge";
+      "d '${cfg.config.move_completed_path}' 0770 ${cfg.user} ${cfg.group}";
 
     systemd.services.deluged = {
       after = [ "network.target" ];
       description = "Deluge BitTorrent Daemon";
       wantedBy = [ "multi-user.target" ];
-      path = [ pkgs.deluge ];
+      path = [ pkgs.deluge ] ++ cfg.extraPackages;
       serviceConfig = {
         ExecStart = ''
           ${pkgs.deluge}/bin/deluged \
@@ -157,8 +195,8 @@ in {
         # To prevent "Quit & shutdown daemon" from working; we want systemd to
         # manage it!
         Restart = "on-success";
-        User = "deluge";
-        Group = "deluge";
+        User = cfg.user;
+        Group = cfg.group;
         UMask = "0002";
         LimitNOFILE = cfg.openFilesLimit;
       };
@@ -177,26 +215,37 @@ in {
             --config ${configDir} \
             --port ${toString cfg.web.port}
         '';
-        User = "deluge";
-        Group = "deluge";
+        User = cfg.user;
+        Group = cfg.group;
       };
     };
 
-    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));
-    };
+    networking.firewall = mkMerge [
+      (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));
+      })
+      (mkIf (cfg.web.openFirewall) {
+        allowedTCPPorts = [ cfg.web.port ];
+      })
+    ];
 
     environment.systemPackages = [ pkgs.deluge ];
 
-    users.users.deluge = {
-      group = "deluge";
-      uid = config.ids.uids.deluge;
-      home = cfg.dataDir;
-      createHome = true;
-      description = "Deluge Daemon user";
+    users.users = mkIf (cfg.user == "deluge") {
+      deluge = {
+        group = cfg.group;
+        uid = config.ids.uids.deluge;
+        home = cfg.dataDir;
+        createHome = true;
+        description = "Deluge Daemon user";
+      };
     };
 
-    users.groups.deluge.gid = config.ids.gids.deluge;
+    users.groups = mkIf (cfg.group == "deluge") {
+      deluge = {
+        gid = config.ids.gids.deluge;
+      };
+    };
   };
 }
diff --git a/nixos/modules/services/web-apps/atlassian/confluence.nix b/nixos/modules/services/web-apps/atlassian/confluence.nix
index 15744d90cc79..cf163271d276 100644
--- a/nixos/modules/services/web-apps/atlassian/confluence.nix
+++ b/nixos/modules/services/web-apps/atlassian/confluence.nix
@@ -149,6 +149,17 @@ in
 
     users.groups."${cfg.group}" = {};
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.home}' - ${cfg.user} - - -"
+      "d /run/confluence - - - - -"
+
+      "L+ /run/confluence/home - - - - ${cfg.home}"
+      "L+ /run/confluence/logs - - - - ${cfg.home}/logs"
+      "L+ /run/confluence/temp - - - - ${cfg.home}/temp"
+      "L+ /run/confluence/work - - - - ${cfg.home}/work"
+      "L+ /run/confluence/server.xml - - - - ${cfg.home}/server.xml"
+    ];
+
     systemd.services.confluence = {
       description = "Atlassian Confluence";
 
@@ -167,12 +178,6 @@ in
       preStart = ''
         mkdir -p ${cfg.home}/{logs,work,temp,deploy}
 
-        mkdir -p /run/confluence
-        ln -sf ${cfg.home}/{logs,work,temp,server.xml} /run/confluence
-        ln -sf ${cfg.home} /run/confluence/home
-
-        chown ${cfg.user} ${cfg.home}
-
         sed -e 's,port="8090",port="${toString cfg.listenPort}" address="${cfg.listenAddress}",' \
         '' + (lib.optionalString cfg.proxy.enable ''
           -e 's,protocol="org.apache.coyote.http11.Http11NioProtocol",protocol="org.apache.coyote.http11.Http11NioProtocol" proxyName="${cfg.proxy.name}" proxyPort="${toString cfg.proxy.port}" scheme="${cfg.proxy.scheme}",' \
@@ -184,7 +189,6 @@ in
         User = cfg.user;
         Group = cfg.group;
         PrivateTmp = true;
-        PermissionsStartOnly = true;
         ExecStart = "${pkg}/bin/start-confluence.sh -fg";
         ExecStop = "${pkg}/bin/stop-confluence.sh";
       };
diff --git a/nixos/modules/services/web-apps/atlassian/crowd.nix b/nixos/modules/services/web-apps/atlassian/crowd.nix
index c144b21bdaf2..020ca8d89dbb 100644
--- a/nixos/modules/services/web-apps/atlassian/crowd.nix
+++ b/nixos/modules/services/web-apps/atlassian/crowd.nix
@@ -117,6 +117,16 @@ in
 
     users.groups."${cfg.group}" = {};
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.home}' - ${cfg.user} ${cfg.group} - -"
+      "d /run/atlassian-crowd - - - - -"
+
+      "L+ /run/atlassian-crowd/database - - - - ${cfg.home}/database"
+      "L+ /run/atlassian-crowd/logs - - - - ${cfg.home}/logs"
+      "L+ /run/atlassian-crowd/work - - - - ${cfg.home}/work"
+      "L+ /run/atlassian-crowd/server.xml - - - - ${cfg.home}/server.xml"
+    ];
+
     systemd.services.atlassian-crowd = {
       description = "Atlassian Crowd";
 
@@ -136,12 +146,6 @@ in
         rm -rf ${cfg.home}/work
         mkdir -p ${cfg.home}/{logs,database,work}
 
-        mkdir -p /run/atlassian-crowd
-        ln -sf ${cfg.home}/{database,logs,work,server.xml} /run/atlassian-crowd
-
-        chown ${cfg.user}:${cfg.group} ${cfg.home}
-        chown ${cfg.user}:${cfg.group} ${cfg.home}/{logs,database,work}
-
         sed -e 's,port="8095",port="${toString cfg.listenPort}" address="${cfg.listenAddress}",' \
         '' + (lib.optionalString cfg.proxy.enable ''
           -e 's,compression="on",compression="off" protocol="HTTP/1.1" proxyName="${cfg.proxy.name}" proxyPort="${toString cfg.proxy.port}" scheme="${cfg.proxy.scheme}" secure="${boolToString cfg.proxy.secure}",' \
@@ -153,7 +157,6 @@ in
         User = cfg.user;
         Group = cfg.group;
         PrivateTmp = true;
-        PermissionsStartOnly = true;
         ExecStart = "${pkg}/start_crowd.sh -fg";
       };
     };
diff --git a/nixos/modules/services/web-apps/atlassian/jira.nix b/nixos/modules/services/web-apps/atlassian/jira.nix
index 0b3a5722d6c8..b0019e77ac27 100644
--- a/nixos/modules/services/web-apps/atlassian/jira.nix
+++ b/nixos/modules/services/web-apps/atlassian/jira.nix
@@ -155,6 +155,17 @@ in
 
     users.groups."${cfg.group}" = {};
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.home}' - ${cfg.user} - - -"
+      "d /run/atlassian-jira - - - - -"
+
+      "L+ /run/atlassian-jira/home - - - - ${cfg.home}"
+      "L+ /run/atlassian-jira/logs - - - - ${cfg.home}/logs"
+      "L+ /run/atlassian-jira/work - - - - ${cfg.home}/work"
+      "L+ /run/atlassian-jira/temp - - - - ${cfg.home}/temp"
+      "L+ /run/atlassian-jira/server.xml - - - - ${cfg.home}/server.xml"
+    ];
+
     systemd.services.atlassian-jira = {
       description = "Atlassian JIRA";
 
@@ -174,12 +185,6 @@ in
       preStart = ''
         mkdir -p ${cfg.home}/{logs,work,temp,deploy}
 
-        mkdir -p /run/atlassian-jira
-        ln -sf ${cfg.home}/{logs,work,temp,server.xml} /run/atlassian-jira
-        ln -sf ${cfg.home} /run/atlassian-jira/home
-
-        chown ${cfg.user} ${cfg.home}
-
         sed -e 's,port="8080",port="${toString cfg.listenPort}" address="${cfg.listenAddress}",' \
         '' + (lib.optionalString cfg.proxy.enable ''
           -e 's,protocol="HTTP/1.1",protocol="HTTP/1.1" proxyName="${cfg.proxy.name}" proxyPort="${toString cfg.proxy.port}" scheme="${cfg.proxy.scheme}" secure="${toString cfg.proxy.secure}",' \
@@ -191,7 +196,6 @@ in
         User = cfg.user;
         Group = cfg.group;
         PrivateTmp = true;
-        PermissionsStartOnly = true;
         ExecStart = "${pkg}/bin/start-jira.sh -fg";
         ExecStop = "${pkg}/bin/stop-jira.sh";
       };
diff --git a/nixos/modules/services/web-apps/frab.nix b/nixos/modules/services/web-apps/frab.nix
index fb95e024817c..e885dc69b3c0 100644
--- a/nixos/modules/services/web-apps/frab.nix
+++ b/nixos/modules/services/web-apps/frab.nix
@@ -182,16 +182,16 @@ in
 
     users.groups = [ { name = cfg.group; } ];
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.statePath}/system/attachments' - ${cfg.user} ${cfg.group} - -"
+    ];
+
     systemd.services.frab = {
       after = [ "network.target" "gitlab.service" ];
       wantedBy = [ "multi-user.target" ];
       environment = frabEnv;
 
       preStart = ''
-        mkdir -p ${cfg.statePath}/system/attachments
-        chown ${cfg.user}:${cfg.group} -R ${cfg.statePath}
-
-        mkdir /run/frab -p
         ln -sf ${pkgs.writeText "frab-database.yml" databaseConfig} /run/frab/database.yml
         ln -sf ${cfg.statePath}/system /run/frab/system
 
@@ -204,7 +204,6 @@ in
       '';
 
       serviceConfig = {
-        PermissionsStartOnly = true;
         PrivateTmp = true;
         PrivateDevices = true;
         Type = "simple";
@@ -213,6 +212,7 @@ in
         TimeoutSec = "300s";
         Restart = "on-failure";
         RestartSec = "10s";
+        RuntimeDirectory = "frab";
         WorkingDirectory = "${package}/share/frab";
         ExecStart = "${frab-rake}/bin/frab-bundle exec rails server " +
           "--binding=${cfg.listenAddress} --port=${toString cfg.listenPort}";
diff --git a/nixos/modules/services/web-apps/limesurvey.nix b/nixos/modules/services/web-apps/limesurvey.nix
index f9e12e3642ea..5b2f3875aaa9 100644
--- a/nixos/modules/services/web-apps/limesurvey.nix
+++ b/nixos/modules/services/web-apps/limesurvey.nix
@@ -6,12 +6,12 @@ let
   inherit (lib) mapAttrs optional optionalString types;
 
   cfg = config.services.limesurvey;
+  fpm = config.services.phpfpm.pools.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) // {
@@ -130,7 +130,8 @@ in
         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.
+        Options for the LimeSurvey PHP pool. See the documentation on <literal>php-fpm.conf</literal>
+        for details on configuration directives.
       '';
     };
 
@@ -202,7 +203,6 @@ in
     };
 
     services.phpfpm.pools.limesurvey = {
-      phpPackage = php;
       listen = "/run/phpfpm/limesurvey.sock";
       extraConfig = ''
         listen.owner = ${config.services.httpd.user};
@@ -220,8 +220,8 @@ in
       enable = true;
       adminAddr = mkDefault cfg.virtualHost.adminAddr;
       extraModules = [ "proxy_fcgi" ];
-      virtualHosts = [
-        (cfg.virtualHost // {
+      virtualHosts = [ (mkMerge [
+        cfg.virtualHost {
           documentRoot = mkForce "${pkg}/share/limesurvey";
           extraConfig = ''
             Alias "/tmp" "${stateDir}/tmp"
@@ -241,7 +241,7 @@ in
             <Directory "${pkg}/share/limesurvey">
               <FilesMatch "\.php$">
                 <If "-f %{REQUEST_FILENAME}">
-                  SetHandler "proxy:unix:/run/phpfpm/limesurvey.sock|fcgi://localhost/"
+                  SetHandler "proxy:unix:${fpm.listen}|fcgi://localhost/"
                 </If>
               </FilesMatch>
 
@@ -250,8 +250,8 @@ in
               DirectoryIndex index.php
             </Directory>
           '';
-        })
-      ];
+        }
+      ]) ];
     };
 
     systemd.tmpfiles.rules = [
@@ -270,8 +270,8 @@ in
       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
+        ${pkgs.php}/bin/php ${pkg}/share/limesurvey/application/commands/console.php updatedb || \
+        ${pkgs.php}/bin/php ${pkg}/share/limesurvey/application/commands/console.php install admin password admin admin@example.com verbose
       '';
       serviceConfig = {
         User = user;
diff --git a/nixos/modules/services/web-apps/matomo-doc.xml b/nixos/modules/services/web-apps/matomo-doc.xml
index 021a89be3f63..8485492c51c7 100644
--- a/nixos/modules/services/web-apps/matomo-doc.xml
+++ b/nixos/modules/services/web-apps/matomo-doc.xml
@@ -21,18 +21,18 @@
    passwordless database authentication via the UNIX_SOCKET authentication
    plugin with the following SQL commands:
 <programlisting>
-        # For MariaDB
-        INSTALL PLUGIN unix_socket SONAME 'auth_socket';
-        CREATE DATABASE matomo;
-        CREATE USER 'matomo'@'localhost' IDENTIFIED WITH unix_socket;
-        GRANT ALL PRIVILEGES ON matomo.* TO 'matomo'@'localhost';
+# For MariaDB
+INSTALL PLUGIN unix_socket SONAME 'auth_socket';
+CREATE DATABASE matomo;
+CREATE USER 'matomo'@'localhost' IDENTIFIED WITH unix_socket;
+GRANT ALL PRIVILEGES ON matomo.* TO 'matomo'@'localhost';
 
-        # For MySQL
-        INSTALL PLUGIN auth_socket SONAME 'auth_socket.so';
-        CREATE DATABASE matomo;
-        CREATE USER 'matomo'@'localhost' IDENTIFIED WITH auth_socket;
-        GRANT ALL PRIVILEGES ON matomo.* TO 'matomo'@'localhost';
-   </programlisting>
+# For MySQL
+INSTALL PLUGIN auth_socket SONAME 'auth_socket.so';
+CREATE DATABASE matomo;
+CREATE USER 'matomo'@'localhost' IDENTIFIED WITH auth_socket;
+GRANT ALL PRIVILEGES ON matomo.* TO 'matomo'@'localhost';
+</programlisting>
    Then fill in <literal>matomo</literal> as database user and database name,
    and leave the password field blank. This authentication works by allowing
    only the <literal>matomo</literal> unix user to authenticate as the
diff --git a/nixos/modules/services/web-apps/mediawiki.nix b/nixos/modules/services/web-apps/mediawiki.nix
new file mode 100644
index 000000000000..5bd5977e592b
--- /dev/null
+++ b/nixos/modules/services/web-apps/mediawiki.nix
@@ -0,0 +1,473 @@
+{ config, pkgs, lib, ... }:
+
+let
+
+  inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption;
+  inherit (lib) concatStringsSep literalExample mapAttrsToList optional optionals optionalString types;
+
+  cfg = config.services.mediawiki;
+  fpm = config.services.phpfpm.pools.mediawiki;
+  user = "mediawiki";
+  group = config.services.httpd.group;
+  cacheDir = "/var/cache/mediawiki";
+  stateDir = "/var/lib/mediawiki";
+
+  pkg = pkgs.stdenv.mkDerivation rec {
+    pname = "mediawiki-full";
+    version = src.version;
+    src = cfg.package;
+
+    installPhase = ''
+      mkdir -p $out
+      cp -r * $out/
+
+      rm -rf $out/share/mediawiki/skins/*
+      rm -rf $out/share/mediawiki/extensions/*
+
+      ${concatStringsSep "\n" (mapAttrsToList (k: v: ''
+        ln -s ${v} $out/share/mediawiki/skins/${k}
+      '') cfg.skins)}
+
+      ${concatStringsSep "\n" (mapAttrsToList (k: v: ''
+        ln -s ${v} $out/share/mediawiki/extensions/${k}
+      '') cfg.extensions)}
+    '';
+  };
+
+  mediawikiScripts = pkgs.runCommand "mediawiki-scripts" {
+    buildInputs = [ pkgs.makeWrapper ];
+    preferLocalBuild = true;
+  } ''
+    mkdir -p $out/bin
+    for i in changePassword.php createAndPromote.php userOptions.php edit.php nukePage.php update.php; do
+      makeWrapper ${pkgs.php}/bin/php $out/bin/mediawiki-$(basename $i .php) \
+        --set MEDIAWIKI_CONFIG ${mediawikiConfig} \
+        --add-flags ${pkg}/share/mediawiki/maintenance/$i
+    done
+  '';
+
+  mediawikiConfig = pkgs.writeText "LocalSettings.php" ''
+    <?php
+      # Protect against web entry
+      if ( !defined( 'MEDIAWIKI' ) ) {
+        exit;
+      }
+
+      $wgSitename = "${cfg.name}";
+      $wgMetaNamespace = false;
+
+      ## The URL base path to the directory containing the wiki;
+      ## defaults for all runtime URL paths are based off of this.
+      ## For more information on customizing the URLs
+      ## (like /w/index.php/Page_title to /wiki/Page_title) please see:
+      ## https://www.mediawiki.org/wiki/Manual:Short_URL
+      $wgScriptPath = "";
+
+      ## The protocol and server name to use in fully-qualified URLs
+      $wgServer = "${if cfg.virtualHost.enableSSL then "https" else "http"}://${cfg.virtualHost.hostName}";
+
+      ## The URL path to static resources (images, scripts, etc.)
+      $wgResourceBasePath = $wgScriptPath;
+
+      ## The URL path to the logo.  Make sure you change this from the default,
+      ## or else you'll overwrite your logo when you upgrade!
+      $wgLogo = "$wgResourceBasePath/resources/assets/wiki.png";
+
+      ## UPO means: this is also a user preference option
+
+      $wgEnableEmail = true;
+      $wgEnableUserEmail = true; # UPO
+
+      $wgEmergencyContact = "${if cfg.virtualHost.adminAddr != null then cfg.virtualHost.adminAddr else config.services.httpd.adminAddr}";
+      $wgPasswordSender = $wgEmergencyContact;
+
+      $wgEnotifUserTalk = false; # UPO
+      $wgEnotifWatchlist = false; # UPO
+      $wgEmailAuthentication = true;
+
+      ## Database settings
+      $wgDBtype = "${cfg.database.type}";
+      $wgDBserver = "${cfg.database.host}:${if cfg.database.socket != null then cfg.database.socket else toString cfg.database.port}";
+      $wgDBname = "${cfg.database.name}";
+      $wgDBuser = "${cfg.database.user}";
+      ${optionalString (cfg.database.passwordFile != null) "$wgDBpassword = file_get_contents(\"${cfg.database.passwordFile}\");"}
+
+      ${optionalString (cfg.database.type == "mysql" && cfg.database.tablePrefix != null) ''
+        # MySQL specific settings
+        $wgDBprefix = "${cfg.database.tablePrefix}";
+      ''}
+
+      ${optionalString (cfg.database.type == "mysql") ''
+        # MySQL table options to use during installation or update
+        $wgDBTableOptions = "ENGINE=InnoDB, DEFAULT CHARSET=binary";
+      ''}
+
+      ## Shared memory settings
+      $wgMainCacheType = CACHE_NONE;
+      $wgMemCachedServers = [];
+
+      ${optionalString (cfg.uploadsDir != null) ''
+        $wgEnableUploads = true;
+        $wgUploadDirectory = "${cfg.uploadsDir}";
+      ''}
+
+      $wgUseImageMagick = true;
+      $wgImageMagickConvertCommand = "${pkgs.imagemagick}/bin/convert";
+
+      # InstantCommons allows wiki to use images from https://commons.wikimedia.org
+      $wgUseInstantCommons = false;
+
+      # Periodically send a pingback to https://www.mediawiki.org/ with basic data
+      # about this MediaWiki instance. The Wikimedia Foundation shares this data
+      # with MediaWiki developers to help guide future development efforts.
+      $wgPingback = true;
+
+      ## If you use ImageMagick (or any other shell command) on a
+      ## Linux server, this will need to be set to the name of an
+      ## available UTF-8 locale
+      $wgShellLocale = "C.UTF-8";
+
+      ## Set $wgCacheDirectory to a writable directory on the web server
+      ## to make your wiki go slightly faster. The directory should not
+      ## be publically accessible from the web.
+      $wgCacheDirectory = "${cacheDir}";
+
+      # Site language code, should be one of the list in ./languages/data/Names.php
+      $wgLanguageCode = "en";
+
+      $wgSecretKey = file_get_contents("${stateDir}/secret.key");
+
+      # Changing this will log out all existing sessions.
+      $wgAuthenticationTokenVersion = "";
+
+      ## For attaching licensing metadata to pages, and displaying an
+      ## appropriate copyright notice / icon. GNU Free Documentation
+      ## License and Creative Commons licenses are supported so far.
+      $wgRightsPage = ""; # Set to the title of a wiki page that describes your license/copyright
+      $wgRightsUrl = "";
+      $wgRightsText = "";
+      $wgRightsIcon = "";
+
+      # Path to the GNU diff3 utility. Used for conflict resolution.
+      $wgDiff = "${pkgs.diffutils}/bin/diff";
+      $wgDiff3 = "${pkgs.diffutils}/bin/diff3";
+
+      # Enabled skins.
+      ${concatStringsSep "\n" (mapAttrsToList (k: v: "wfLoadSkin('${k}');") cfg.skins)}
+
+      # Enabled extensions.
+      ${concatStringsSep "\n" (mapAttrsToList (k: v: "wfLoadExtension('${k}');") cfg.extensions)}
+
+
+      # End of automatically generated settings.
+      # Add more configuration options below.
+
+      ${cfg.extraConfig}
+  '';
+
+in
+{
+  # interface
+  options = {
+    services.mediawiki = {
+
+      enable = mkEnableOption "MediaWiki";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.mediawiki;
+        description = "Which MediaWiki package to use.";
+      };
+
+      name = mkOption {
+        default = "MediaWiki";
+        example = "Foobar Wiki";
+        description = "Name of the wiki.";
+      };
+
+      uploadsDir = mkOption {
+        type = types.nullOr types.path;
+        default = "${stateDir}/uploads";
+        description = ''
+          This directory is used for uploads of pictures. The directory passed here is automatically
+          created and permissions adjusted as required.
+        '';
+      };
+
+      passwordFile = mkOption {
+        type = types.path;
+        description = "A file containing the initial password for the admin user.";
+        example = "/run/keys/mediawiki-password";
+      };
+
+      skins = mkOption {
+        default = {};
+        type = types.attrsOf types.path;
+        description = ''
+          List of paths whose content is copied to the 'skins'
+          subdirectory of the MediaWiki installation.
+        '';
+      };
+
+      extensions = mkOption {
+        default = {};
+        type = types.attrsOf types.path;
+        description = ''
+          List of paths whose content is copied to the 'extensions'
+          subdirectory of the MediaWiki installation.
+        '';
+      };
+
+      database = {
+        type = mkOption {
+          type = types.enum [ "mysql" "postgres" "sqlite" "mssql" "oracle" ];
+          default = "mysql";
+          description = "Database engine to use. MySQL/MariaDB is the database of choice by MediaWiki developers.";
+        };
+
+        host = mkOption {
+          type = types.str;
+          default = "localhost";
+          description = "Database host address.";
+        };
+
+        port = mkOption {
+          type = types.port;
+          default = 3306;
+          description = "Database host port.";
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "mediawiki";
+          description = "Database name.";
+        };
+
+        user = mkOption {
+          type = types.str;
+          default = "mediawiki";
+          description = "Database user.";
+        };
+
+        passwordFile = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          example = "/run/keys/mediawiki-dbpassword";
+          description = ''
+            A file containing the password corresponding to
+            <option>database.user</option>.
+          '';
+        };
+
+        tablePrefix = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            If you only have access to a single database and wish to install more than
+            one version of MediaWiki, or have other applications that also use the
+            database, you can give the table names a unique prefix to stop any naming
+            conflicts or confusion.
+            See <link xlink:href='https://www.mediawiki.org/wiki/Manual:$wgDBprefix'/>.
+          '';
+        };
+
+        socket = mkOption {
+          type = types.nullOr types.path;
+          default = if cfg.database.createLocally then "/run/mysqld/mysqld.sock" 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 = literalExample ''
+          {
+            hostName = "mediawiki.example.org";
+            enableSSL = true;
+            adminAddr = "webmaster@example.org";
+            sslServerCert = "/var/lib/acme/mediawiki.example.org/full.pem";
+            sslServerKey = "/var/lib/acme/mediawiki.example.org/key.pem";
+          }
+        '';
+        description = ''
+          Apache configuration can be done by adapting <option>services.httpd.virtualHosts</option>.
+          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 MediaWiki's PHP pool. See the documentation on <literal>php-fpm.conf</literal>
+          for details on configuration directives.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        description = ''
+          Any additional text to be appended to MediaWiki's
+          LocalSettings.php configuration file. For configuration
+          settings, see <link xlink:href="https://www.mediawiki.org/wiki/Manual:Configuration_settings"/>.
+        '';
+        default = "";
+        example = ''
+          $wgEnableEmail = false;
+        '';
+      };
+
+    };
+  };
+
+  # implementation
+  config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion = cfg.database.createLocally -> cfg.database.type == "mysql";
+        message = "services.mediawiki.createLocally is currently only supported for database type 'mysql'";
+      }
+      { assertion = cfg.database.createLocally -> cfg.database.user == user;
+        message = "services.mediawiki.database.user must be set to ${user} if services.mediawiki.database.createLocally is set true";
+      }
+      { assertion = cfg.database.createLocally -> cfg.database.socket != null;
+        message = "services.mediawiki.database.socket must be set if services.mediawiki.database.createLocally is set to true";
+      }
+      { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
+        message = "a password cannot be specified if services.mediawiki.database.createLocally is set to true";
+      }
+    ];
+
+    services.mediawiki.skins = {
+      MonoBook = "${cfg.package}/share/mediawiki/skins/MonoBook";
+      Timeless = "${cfg.package}/share/mediawiki/skins/Timeless";
+      Vector = "${cfg.package}/share/mediawiki/skins/Vector";
+    };
+
+    services.mysql = mkIf cfg.database.createLocally {
+      enable = true;
+      package = mkDefault pkgs.mariadb;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.database.user;
+          ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
+
+    services.phpfpm.pools.mediawiki = {
+      listen = "/run/phpfpm/mediawiki.sock";
+      extraConfig = ''
+        listen.owner = ${config.services.httpd.user}
+        listen.group = ${config.services.httpd.group}
+        user = ${user}
+        group = ${group}
+
+        env[MEDIAWIKI_CONFIG] = ${mediawikiConfig}
+
+        ${cfg.poolConfig}
+      '';
+    };
+
+    services.httpd = {
+      enable = true;
+      adminAddr = mkDefault cfg.virtualHost.adminAddr;
+      extraModules = [ "proxy_fcgi" ];
+      virtualHosts = [ (mkMerge [
+        cfg.virtualHost {
+          documentRoot = mkForce "${pkg}/share/mediawiki";
+          extraConfig = ''
+            <Directory "${pkg}/share/mediawiki">
+              <FilesMatch "\.php$">
+                <If "-f %{REQUEST_FILENAME}">
+                  SetHandler "proxy:unix:${fpm.listen}|fcgi://localhost/"
+                </If>
+              </FilesMatch>
+
+              Require all granted
+              DirectoryIndex index.php
+              AllowOverride All
+            </Directory>
+          '' + optionalString (cfg.uploadsDir != null) ''
+            Alias "/images" "${cfg.uploadsDir}"
+            <Directory "${cfg.uploadsDir}">
+              Require all granted
+            </Directory>
+          '';
+        }
+      ]) ];
+    };
+
+    systemd.tmpfiles.rules = [
+      "d '${stateDir}' 0750 ${user} ${group} - -"
+      "d '${cacheDir}' 0750 ${user} ${group} - -"
+    ] ++ optionals (cfg.uploadsDir != null) [
+      "d '${cfg.uploadsDir}' 0750 ${user} ${group} - -"
+      "Z '${cfg.uploadsDir}' 0750 ${user} ${group} - -"
+    ];
+
+    systemd.services.mediawiki-init = {
+      wantedBy = [ "multi-user.target" ];
+      before = [ "phpfpm-mediawiki.service" ];
+      after = optional cfg.database.createLocally "mysql.service";
+      script = ''
+        if ! test -e "${stateDir}/secret.key"; then
+          tr -dc A-Za-z0-9 </dev/urandom 2>/dev/null | head -c 64 > ${stateDir}/secret.key
+        fi
+
+        echo "exit( wfGetDB( DB_MASTER )->tableExists( 'user' ) ? 1 : 0 );" | \
+        ${pkgs.php}/bin/php ${pkg}/share/mediawiki/maintenance/eval.php --conf ${mediawikiConfig} && \
+        ${pkgs.php}/bin/php ${pkg}/share/mediawiki/maintenance/install.php \
+          --confpath /tmp \
+          --scriptpath / \
+          --dbserver ${cfg.database.host}${optionalString (cfg.database.socket != null) ":${cfg.database.socket}"} \
+          --dbport ${toString cfg.database.port} \
+          --dbname ${cfg.database.name} \
+          ${optionalString (cfg.database.tablePrefix != null) "--dbprefix ${cfg.database.tablePrefix}"} \
+          --dbuser ${cfg.database.user} \
+          ${optionalString (cfg.database.passwordFile != null) "--dbpassfile ${cfg.database.passwordFile}"} \
+          --passfile ${cfg.passwordFile} \
+          ${cfg.name} \
+          admin
+
+        ${pkgs.php}/bin/php ${pkg}/share/mediawiki/maintenance/update.php --conf ${mediawikiConfig} --quick
+      '';
+
+      serviceConfig = {
+        Type = "oneshot";
+        User = user;
+        Group = group;
+        PrivateTmp = true;
+      };
+    };
+
+    systemd.services.httpd.after = optional (cfg.database.createLocally && cfg.database.type == "mysql") "mysql.service";
+
+    users.users.${user}.group = group;
+
+    environment.systemPackages = [ mediawikiScripts ];
+  };
+}
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
index bb39a5d1d714..a0214a75d93e 100644
--- a/nixos/modules/services/web-apps/nextcloud.nix
+++ b/nixos/modules/services/web-apps/nextcloud.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }@args:
+{ config, lib, pkgs, ... }:
 
 with lib;
 
@@ -297,8 +297,23 @@ in {
 
       systemd.services = {
         "nextcloud-setup" = let
+          c = cfg.config;
+          writePhpArrary = a: "[${concatMapStringsSep "," (val: ''"${toString val}"'') a}]";
           overrideConfig = pkgs.writeText "nextcloud-config.php" ''
             <?php
+            ${optionalString (c.dbpassFile != null) ''
+              function nix_read_pwd() {
+                $file = "${c.dbpassFile}";
+                if (!file_exists($file)) {
+                  throw new \RuntimeException(sprintf(
+                    "Cannot start Nextcloud, dbpass file %s set by NixOS doesn't exist!",
+                    $file
+                  ));
+                }
+
+                return trim(file_get_contents($file));
+              }
+            ''}
             $CONFIG = [
               'apps_paths' => [
                 [ 'path' => '${cfg.home}/apps', 'url' => '/apps', 'writable' => false ],
@@ -309,19 +324,27 @@ in {
               ${optionalString cfg.caching.apcu "'memcache.local' => '\\OC\\Memcache\\APCu',"}
               'log_type' => 'syslog',
               'log_level' => '${builtins.toString cfg.logLevel}',
-              ${optionalString (cfg.config.overwriteProtocol != null) "'overwriteprotocol' => '${cfg.config.overwriteProtocol}',"}
+              ${optionalString (c.overwriteProtocol != null) "'overwriteprotocol' => '${c.overwriteProtocol}',"}
+              ${optionalString (c.dbname != null) "'dbname' => '${c.dbname}',"}
+              ${optionalString (c.dbhost != null) "'dbhost' => '${c.dbhost}',"}
+              ${optionalString (c.dbport != null) "'dbport' => '${toString c.dbport}',"}
+              ${optionalString (c.dbuser != null) "'dbuser' => '${c.dbuser}',"}
+              ${optionalString (c.dbtableprefix != null) "'dbtableprefix' => '${toString c.dbtableprefix}',"}
+              ${optionalString (c.dbpass != null) "'dbpassword' => '${c.dbpass}',"}
+              ${optionalString (c.dbpassFile != null) "'dbpassword' => nix_read_pwd(),"}
+              'dbtype' => '${c.dbtype}',
+              'trusted_domains' => ${writePhpArrary ([ cfg.hostName ] ++ c.extraTrustedDomains)},
             ];
           '';
           occInstallCmd = let
-            c = cfg.config;
-            adminpass = if c.adminpassFile != null
-              then ''"$(<"${toString c.adminpassFile}")"''
-              else ''"${toString c.adminpass}"'';
             dbpass = if c.dbpassFile != null
               then ''"$(<"${toString c.dbpassFile}")"''
               else if c.dbpass != null
               then ''"${toString c.dbpass}"''
               else null;
+            adminpass = if c.adminpassFile != null
+              then ''"$(<"${toString c.adminpassFile}")"''
+              else ''"${toString c.adminpass}"'';
             installFlags = concatStringsSep " \\\n    "
               (mapAttrsToList (k: v: "${k} ${toString v}") {
               "--database" = ''"${c.dbtype}"'';
diff --git a/nixos/modules/services/web-apps/nextcloud.xml b/nixos/modules/services/web-apps/nextcloud.xml
index d78d866086a6..d66e0f0c2997 100644
--- a/nixos/modules/services/web-apps/nextcloud.xml
+++ b/nixos/modules/services/web-apps/nextcloud.xml
@@ -42,10 +42,12 @@
 
   services.postgresql = {
     <link linkend="opt-services.postgresql.enable">enable</link> = true;
-    <link linkend="opt-services.postgresql.initialScript">initialScript</link> = pkgs.writeText "psql-init" ''
-      CREATE ROLE nextcloud WITH LOGIN;
-      CREATE DATABASE nextcloud WITH OWNER nextcloud;
-    '';
+    <link linkend="opt-services.postgresql.ensureDatabases">ensureDatabases</link> = [ "nextcloud" ];
+    <link linkend="opt-services.postgresql.ensureUsers">ensureUsers</link> = [
+     { name = "nextcloud";
+       ensurePermissions."DATABASE nextcloud" = "ALL PRIVILEGES";
+     }
+    ];
   };
 
   # ensure that postgres is running *before* running the setup
@@ -63,17 +65,22 @@
    are used internally to configure an HTTP server using
    <literal><link xlink:href="https://php-fpm.org/">PHP-FPM</link></literal>
    and <literal>nginx</literal>. The <literal>config</literal> attribute set is
-   used for the <literal>config.php</literal> which is used for the
-   application's configuration. <emphasis>Beware: this isn't entirely pure
-   since the config is modified by the application's runtime!</emphasis>
+   used by the imperative installer and all values are written to an additional file
+   to ensure that changes can be applied by changing the module's options.
   </para>
 
   <para>
-   In case the application serves multiple hosts (those are checked with
+   In case the application serves multiple domains (those are checked with
    <literal><link xlink:href="http://php.net/manual/en/reserved.variables.server.php">$_SERVER['HTTP_HOST']</link></literal>)
-   those can be added using
+   it's needed to add them to
    <literal><link linkend="opt-services.nextcloud.config.extraTrustedDomains">services.nextcloud.config.extraTrustedDomains</link></literal>.
   </para>
+
+  <para>
+   Auto updates for Nextcloud apps can be enabled using
+   <literal><link linkend="opt-services.nextcloud.autoUpdateApps.enable">services.nextcloud.autoUpdateApps</link></literal>.
+</para>
+
  </section>
  <section xml:id="module-services-nextcloud-pitfalls-during-upgrade">
   <title>Pitfalls</title>
@@ -87,35 +94,24 @@
   </para>
 
   <para>
-   Right now changes to the <literal>services.nextcloud.config</literal>
-   attribute set won't take effect after the first install (except
-   <literal><link linkend="opt-services.nextcloud.config.extraTrustedDomains">services.nextcloud.config.extraTrustedDomains</link></literal>)
-   since the actual configuration file is generated by the NextCloud installer
-   which also sets up critical parts such as the database structure.
+   All configuration parameters are also stored in
+   <literal>/var/lib/nextcloud/config/override.config.php</literal> which is generated by
+   the module and linked from the store to ensure that all values from <literal>config.php</literal>
+   can be modified by the module.
+   However <literal>config.php</literal> manages the application's state and shouldn't be touched
+   manually because of that.
   </para>
 
-  <para>
-   <emphasis>Warning: don't delete <literal>config.php</literal>! This file
+  <warning>
+   <para>Don't delete <literal>config.php</literal>! This file
    tracks the application's state and a deletion can cause unwanted
-   side-effects!</emphasis>
-  </para>
+   side-effects!</para>
+  </warning>
 
-  <para>
-   <emphasis>Warning: don't rerun <literal>nextcloud-occ
+  <warning>
+   <para>Don't rerun <literal>nextcloud-occ
    maintenance:install</literal>! This command tries to install the application
-   and can cause unwanted side-effects!</emphasis>
-  </para>
-
-  <para>
-   The issues are known and reported in
-   <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>
+   and can cause unwanted side-effects!</para>
+  </warning>
  </section>
 </chapter>
diff --git a/nixos/modules/services/web-apps/restya-board.nix b/nixos/modules/services/web-apps/restya-board.nix
index 2e5e0ea6622d..b200a89260ac 100644
--- a/nixos/modules/services/web-apps/restya-board.nix
+++ b/nixos/modules/services/web-apps/restya-board.nix
@@ -178,7 +178,7 @@ in
 
   config = mkIf cfg.enable {
 
-    services.phpfpm.poolConfigs = {
+    services.phpfpm.pools = {
       "${poolName}" = {
         listen = phpfpmSocketName;
         phpOptions = ''
diff --git a/nixos/modules/services/web-apps/tt-rss.nix b/nixos/modules/services/web-apps/tt-rss.nix
index 08297c7275a4..1bd9de93735d 100644
--- a/nixos/modules/services/web-apps/tt-rss.nix
+++ b/nixos/modules/services/web-apps/tt-rss.nix
@@ -15,7 +15,9 @@ let
     else cfg.database.port;
 
   poolName = "tt-rss";
-  phpfpmSocketName = "/run/phpfpm/${poolName}.sock";
+
+  mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql";
+  pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql";
 
   tt-rss-config = pkgs.writeText "config.php" ''
     <?php
@@ -201,6 +203,12 @@ let
             and 3306 for pgsql and mysql respectively).
           '';
         };
+
+        createLocally = mkOption {
+          type = types.bool;
+          default = true;
+          description = "Create the database and database user locally.";
+        };
       };
 
       auth = {
@@ -552,9 +560,13 @@ let
       };
     };
 
-    systemd.services.tt-rss = let
-      dbService = if cfg.database.type == "pgsql" then "postgresql.service" else "mysql.service";
-    in {
+    systemd.tmpfiles.rules = [
+      "d '${cfg.root}' 0755 ${cfg.user} tt_rss - -"
+      "Z '${cfg.root}' 0755 ${cfg.user} tt_rss - -"
+    ];
+
+    systemd.services.tt-rss =
+      {
 
         description = "Tiny Tiny RSS feeds update daemon";
 
@@ -563,14 +575,14 @@ let
               if cfg.database.type == "pgsql" then ''
                   ${optionalString (cfg.database.password != null) "PGPASSWORD=${cfg.database.password}"} \
                   ${optionalString (cfg.database.passwordFile != null) "PGPASSWORD=$(cat ${cfg.database.passwordFile})"} \
-                  ${pkgs.sudo}/bin/sudo -u ${cfg.user} ${config.services.postgresql.package}/bin/psql \
+                  ${config.services.postgresql.package}/bin/psql \
                     -U ${cfg.database.user} \
                     ${optionalString (cfg.database.host != null) "-h ${cfg.database.host} --port ${toString dbPort}"} \
                     -c '${e}' \
                     ${cfg.database.name}''
 
               else if cfg.database.type == "mysql" then ''
-                  echo '${e}' | ${pkgs.sudo}/bin/sudo -u ${cfg.user} ${config.services.mysql.package}/bin/mysql \
+                  echo '${e}' | ${config.services.mysql.package}/bin/mysql \
                     -u ${cfg.database.user} \
                     ${optionalString (cfg.database.password != null) "-p${cfg.database.password}"} \
                     ${optionalString (cfg.database.host != null) "-h ${cfg.database.host} -P ${toString dbPort}"} \
@@ -580,7 +592,6 @@ let
 
         in ''
           rm -rf "${cfg.root}/*"
-          mkdir -m 755 -p "${cfg.root}"
           cp -r "${pkgs.tt-rss}/"* "${cfg.root}"
           ${optionalString (cfg.pluginPackages != []) ''
             for plugin in ${concatStringsSep " " cfg.pluginPackages}; do
@@ -593,19 +604,10 @@ let
             done
           ''}
           ln -sf "${tt-rss-config}" "${cfg.root}/config.php"
-          chown -R "${cfg.user}" "${cfg.root}"
           chmod -R 755 "${cfg.root}"
         ''
 
         + (optionalString (cfg.database.type == "pgsql") ''
-          ${optionalString (cfg.database.host == null && cfg.database.password == null) ''
-            if ! [ -e ${cfg.root}/.db-created ]; then
-              ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} ${config.services.postgresql.package}/bin/createuser ${cfg.database.user}
-              ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} ${config.services.postgresql.package}/bin/createdb -O ${cfg.database.user} ${cfg.database.name}
-              touch ${cfg.root}/.db-created
-            fi
-          ''}
-
           exists=$(${callSql "select count(*) > 0 from pg_tables where tableowner = user"} \
           | tail -n+3 | head -n-2 | sed -e 's/[ \n\t]*//')
 
@@ -629,18 +631,18 @@ let
 
         serviceConfig = {
           User = "${cfg.user}";
+          Group = "tt_rss";
           ExecStart = "${pkgs.php}/bin/php ${cfg.root}/update.php --daemon";
           StandardOutput = "syslog";
           StandardError = "syslog";
-          PermissionsStartOnly = true;
         };
 
         wantedBy = [ "multi-user.target" ];
-        requires = ["${dbService}"];
-        after = ["network.target" "${dbService}"];
+        requires = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
+        after = [ "network.target" ] ++ optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
     };
 
-    services.mysql = optionalAttrs (cfg.database.type == "mysql") {
+    services.mysql = mkIf mysqlLocal {
       enable = true;
       package = mkDefault pkgs.mysql;
       ensureDatabases = [ cfg.database.name ];
@@ -654,17 +656,22 @@ let
       ];
     };
 
-    services.postgresql = optionalAttrs (cfg.database.type == "pgsql") {
+    services.postgresql = mkIf pgsqlLocal {
       enable = mkDefault true;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.user;
+          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+        }
+      ];
     };
 
-    users = optionalAttrs (cfg.user == "tt_rss") {
-      users.tt_rss = {
-        description = "tt-rss service user";
-        isSystemUser = true;
-        group = "tt_rss";
-      };
-      groups.tt_rss = {};
+    users.users.tt_rss = optionalAttrs (cfg.user == "tt_rss") {
+      description = "tt-rss service user";
+      isSystemUser = true;
+      group = "tt_rss";
     };
+
+    users.groups.tt_rss = {};
   };
 }
diff --git a/nixos/modules/services/web-apps/wordpress.nix b/nixos/modules/services/web-apps/wordpress.nix
new file mode 100644
index 000000000000..624b0089a037
--- /dev/null
+++ b/nixos/modules/services/web-apps/wordpress.nix
@@ -0,0 +1,371 @@
+{ config, pkgs, lib, ... }:
+
+let
+  inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption types;
+  inherit (lib) any attrValues concatMapStringsSep flatten literalExample;
+  inherit (lib) mapAttrs' mapAttrsToList nameValuePair optional optionalAttrs optionalString;
+
+  eachSite = config.services.wordpress;
+  user = "wordpress";
+  group = config.services.httpd.group;
+  stateDir = hostName: "/var/lib/wordpress/${hostName}";
+
+  pkg = hostName: cfg: pkgs.stdenv.mkDerivation rec {
+    pname = "wordpress-${hostName}";
+    version = src.version;
+    src = cfg.package;
+
+    installPhase = ''
+      mkdir -p $out
+      cp -r * $out/
+
+      # symlink the wordpress config
+      ln -s ${wpConfig hostName cfg} $out/share/wordpress/wp-config.php
+      # symlink uploads directory
+      ln -s ${cfg.uploadsDir} $out/share/wordpress/wp-content/uploads
+
+      # https://github.com/NixOS/nixpkgs/pull/53399
+      #
+      # Symlinking works for most plugins and themes, but Avada, for instance, fails to
+      # understand the symlink, causing its file path stripping to fail. This results in
+      # requests that look like: https://example.com/wp-content//nix/store/...plugin/path/some-file.js
+      # Since hard linking directories is not allowed, copying is the next best thing.
+
+      # copy additional plugin(s) and theme(s)
+      ${concatMapStringsSep "\n" (theme: "cp -r ${theme} $out/share/wordpress/wp-content/themes/${theme.name}") cfg.themes}
+      ${concatMapStringsSep "\n" (plugin: "cp -r ${plugin} $out/share/wordpress/wp-content/plugins/${plugin.name}") cfg.plugins}
+    '';
+  };
+
+  wpConfig = hostName: cfg: pkgs.writeText "wp-config-${hostName}.php" ''
+    <?php
+      define('DB_NAME', '${cfg.database.name}');
+      define('DB_HOST', '${cfg.database.host}:${if cfg.database.socket != null then cfg.database.socket else toString cfg.database.port}');
+      define('DB_USER', '${cfg.database.user}');
+      ${optionalString (cfg.database.passwordFile != null) "define('DB_PASSWORD', file_get_contents('${cfg.database.passwordFile}'));"}
+      define('DB_CHARSET', 'utf8');
+      $table_prefix  = '${cfg.database.tablePrefix}';
+
+      require_once('${stateDir hostName}/secret-keys.php');
+
+      # wordpress is installed onto a read-only file system
+      define('DISALLOW_FILE_EDIT', true);
+      define('AUTOMATIC_UPDATER_DISABLED', true);
+
+      ${cfg.extraConfig}
+
+      if ( !defined('ABSPATH') )
+        define('ABSPATH', dirname(__FILE__) . '/');
+
+      require_once(ABSPATH . 'wp-settings.php');
+    ?>
+  '';
+
+  siteOpts = { lib, name, ... }:
+    {
+      options = {
+        package = mkOption {
+          type = types.package;
+          default = pkgs.wordpress;
+          description = "Which WordPress package to use.";
+        };
+
+        uploadsDir = mkOption {
+          type = types.path;
+          default = "/var/lib/wordpress/${name}/uploads";
+          description = ''
+            This directory is used for uploads of pictures. The directory passed here is automatically
+            created and permissions adjusted as required.
+          '';
+        };
+
+        plugins = mkOption {
+          type = types.listOf types.path;
+          default = [];
+          description = ''
+            List of path(s) to respective plugin(s) which are copied from the 'plugins' directory.
+            <note><para>These plugins need to be packaged before use, see example.</para></note>
+          '';
+          example = ''
+            # Wordpress plugin 'embed-pdf-viewer' installation example
+            embedPdfViewerPlugin = pkgs.stdenv.mkDerivation {
+              name = "embed-pdf-viewer-plugin";
+              # Download the theme from the wordpress site
+              src = pkgs.fetchurl {
+                url = https://downloads.wordpress.org/plugin/embed-pdf-viewer.2.0.3.zip;
+                sha256 = "1rhba5h5fjlhy8p05zf0p14c9iagfh96y91r36ni0rmk6y891lyd";
+              };
+              # We need unzip to build this package
+              buildInputs = [ pkgs.unzip ];
+              # Installing simply means copying all files to the output directory
+              installPhase = "mkdir -p $out; cp -R * $out/";
+            };
+
+            And then pass this theme to the themes list like this:
+              plugins = [ embedPdfViewerPlugin ];
+          '';
+        };
+
+        themes = mkOption {
+          type = types.listOf types.path;
+          default = [];
+          description = ''
+            List of path(s) to respective theme(s) which are copied from the 'theme' directory.
+            <note><para>These themes need to be packaged before use, see example.</para></note>
+          '';
+          example = ''
+            # For shits and giggles, let's package the responsive theme
+            responsiveTheme = pkgs.stdenv.mkDerivation {
+              name = "responsive-theme";
+              # Download the theme from the wordpress site
+              src = pkgs.fetchurl {
+                url = https://downloads.wordpress.org/theme/responsive.3.14.zip;
+                sha256 = "0rjwm811f4aa4q43r77zxlpklyb85q08f9c8ns2akcarrvj5ydx3";
+              };
+              # We need unzip to build this package
+              buildInputs = [ pkgs.unzip ];
+              # Installing simply means copying all files to the output directory
+              installPhase = "mkdir -p $out; cp -R * $out/";
+            };
+
+            And then pass this theme to the themes list like this:
+              themes = [ responsiveTheme ];
+          '';
+        };
+
+        database = rec {
+          host = mkOption {
+            type = types.str;
+            default = "localhost";
+            description = "Database host address.";
+          };
+
+          port = mkOption {
+            type = types.port;
+            default = 3306;
+            description = "Database host port.";
+          };
+
+          name = mkOption {
+            type = types.str;
+            default = "wordpress";
+            description = "Database name.";
+          };
+
+          user = mkOption {
+            type = types.str;
+            default = "wordpress";
+            description = "Database user.";
+          };
+
+          passwordFile = mkOption {
+            type = types.nullOr types.path;
+            default = null;
+            example = "/run/keys/wordpress-dbpassword";
+            description = ''
+              A file containing the password corresponding to
+              <option>database.user</option>.
+            '';
+          };
+
+          tablePrefix = mkOption {
+            type = types.str;
+            default = "wp_";
+            description = ''
+              The $table_prefix is the value placed in the front of your database tables.
+              Change the value if you want to use something other than wp_ for your database
+              prefix. Typically this is changed if you are installing multiple WordPress blogs
+              in the same database.
+
+              See <link xlink:href='https://codex.wordpress.org/Editing_wp-config.php#table_prefix'/>.
+            '';
+          };
+
+          socket = mkOption {
+            type = types.nullOr types.path;
+            default = null;
+            defaultText = "/run/mysqld/mysqld.sock";
+            description = "Path to the unix socket file to use for authentication.";
+          };
+
+          createLocally = mkOption {
+            type = types.bool;
+            default = true;
+            description = "Create the database and database user locally.";
+          };
+        };
+
+        virtualHost = mkOption {
+          type = types.submodule ({
+            options = import ../web-servers/apache-httpd/per-server-options.nix {
+              inherit lib;
+              forMainServer = false;
+            };
+          });
+          example = literalExample ''
+            {
+              enableSSL = true;
+              adminAddr = "webmaster@example.org";
+              sslServerCert = "/var/lib/acme/wordpress.example.org/full.pem";
+              sslServerKey = "/var/lib/acme/wordpress.example.org/key.pem";
+            }
+          '';
+          description = ''
+            Apache configuration can be done by adapting <option>services.httpd.virtualHosts</option>.
+          '';
+        };
+
+        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 the WordPress PHP pool. See the documentation on <literal>php-fpm.conf</literal>
+            for details on configuration directives.
+          '';
+        };
+
+        extraConfig = mkOption {
+          type = types.lines;
+          default = "";
+          description = ''
+            Any additional text to be appended to the wp-config.php
+            configuration file. This is a PHP script. For configuration
+            settings, see <link xlink:href='https://codex.wordpress.org/Editing_wp-config.php'/>.
+          '';
+          example = ''
+            define( 'AUTOSAVE_INTERVAL', 60 ); // Seconds
+          '';
+        };
+      };
+
+      config.virtualHost.hostName = mkDefault name;
+    };
+in
+{
+  # interface
+  options = {
+    services.wordpress = mkOption {
+      type = types.attrsOf (types.submodule siteOpts);
+      default = {};
+      description = "Specification of one or more WordPress sites to serve via Apache.";
+    };
+  };
+
+  # implementation
+  config = mkIf (eachSite != {}) {
+
+    assertions = mapAttrsToList (hostName: cfg:
+      { assertion = cfg.database.createLocally -> cfg.database.user == user;
+        message = "services.wordpress.${hostName}.database.user must be ${user} if the database is to be automatically provisioned";
+      }
+    ) eachSite;
+
+    services.mysql = mkIf (any (v: v.database.createLocally) (attrValues eachSite)) {
+      enable = true;
+      package = mkDefault pkgs.mariadb;
+      ensureDatabases = mapAttrsToList (hostName: cfg: cfg.database.name) eachSite;
+      ensureUsers = mapAttrsToList (hostName: cfg:
+        { name = cfg.database.user;
+          ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+        }
+      ) eachSite;
+    };
+
+    services.phpfpm.pools = mapAttrs' (hostName: cfg: (
+      nameValuePair "wordpress-${hostName}" {
+        listen = "/run/phpfpm/wordpress-${hostName}.sock";
+        extraConfig = ''
+          listen.owner = ${config.services.httpd.user}
+          listen.group = ${config.services.httpd.group}
+          user = ${user}
+          group = ${group}
+
+          ${cfg.poolConfig}
+        '';
+      }
+    )) eachSite;
+
+    services.httpd = {
+      enable = true;
+      extraModules = [ "proxy_fcgi" ];
+      virtualHosts = mapAttrsToList (hostName: cfg:
+        (mkMerge [
+          cfg.virtualHost {
+            documentRoot = mkForce "${pkg hostName cfg}/share/wordpress";
+            extraConfig = ''
+              <Directory "${pkg hostName cfg}/share/wordpress">
+                <FilesMatch "\.php$">
+                  <If "-f %{REQUEST_FILENAME}">
+                    SetHandler "proxy:unix:/run/phpfpm/wordpress-${hostName}.sock|fcgi://localhost/"
+                  </If>
+                </FilesMatch>
+
+                # standard wordpress .htaccess contents
+                <IfModule mod_rewrite.c>
+                  RewriteEngine On
+                  RewriteBase /
+                  RewriteRule ^index\.php$ - [L]
+                  RewriteCond %{REQUEST_FILENAME} !-f
+                  RewriteCond %{REQUEST_FILENAME} !-d
+                  RewriteRule . /index.php [L]
+                </IfModule>
+
+                DirectoryIndex index.php
+                Require all granted
+                Options +FollowSymLinks
+              </Directory>
+
+              # https://wordpress.org/support/article/hardening-wordpress/#securing-wp-config-php
+              <Files wp-config.php>
+                Require all denied
+              </Files>
+            '';
+          }
+        ])
+      ) eachSite;
+    };
+
+    systemd.tmpfiles.rules = flatten (mapAttrsToList (hostName: cfg: [
+      "d '${stateDir hostName}' 0750 ${user} ${group} - -"
+      "d '${cfg.uploadsDir}' 0750 ${user} ${group} - -"
+      "Z '${cfg.uploadsDir}' 0750 ${user} ${group} - -"
+    ]) eachSite);
+
+    systemd.services = mkMerge [
+      (mapAttrs' (hostName: cfg: (
+        nameValuePair "wordpress-init-${hostName}" {
+          wantedBy = [ "multi-user.target" ];
+          before = [ "phpfpm-wordpress-${hostName}.service" ];
+          after = optional cfg.database.createLocally "mysql.service";
+          script = ''
+            if ! test -e "${stateDir hostName}/secret-keys.php"; then
+              echo "<?php" >> "${stateDir hostName}/secret-keys.php"
+              ${pkgs.curl}/bin/curl -s https://api.wordpress.org/secret-key/1.1/salt/ >> "${stateDir hostName}/secret-keys.php"
+              echo "?>" >> "${stateDir hostName}/secret-keys.php"
+              chmod 440 "${stateDir hostName}/secret-keys.php"
+            fi
+          '';
+
+          serviceConfig = {
+            Type = "oneshot";
+            User = user;
+            Group = group;
+          };
+      })) eachSite)
+
+      (optionalAttrs (any (v: v.database.createLocally) (attrValues eachSite)) {
+        httpd.after = [ "mysql.service" ];
+      })
+    ];
+
+    users.users.${user}.group = group;
+
+  };
+}
diff --git a/nixos/modules/services/web-apps/zabbix.nix b/nixos/modules/services/web-apps/zabbix.nix
new file mode 100644
index 000000000000..4b5334579a99
--- /dev/null
+++ b/nixos/modules/services/web-apps/zabbix.nix
@@ -0,0 +1,225 @@
+{ config, lib, pkgs, ... }:
+
+let
+
+  inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption types;
+  inherit (lib) literalExample mapAttrs optionalString;
+
+  cfg = config.services.zabbixWeb;
+  fpm = config.services.phpfpm.pools.zabbix;
+
+  user = "zabbix";
+  group = "zabbix";
+  stateDir = "/var/lib/zabbix";
+
+  zabbixConfig = pkgs.writeText "zabbix.conf.php" ''
+    <?php
+    // Zabbix GUI configuration file.
+    global $DB;
+    $DB['TYPE'] = '${ { "mysql" = "MYSQL"; "pgsql" = "POSTGRESQL"; "oracle" = "ORACLE"; }.${cfg.database.type} }';
+    $DB['SERVER'] = '${cfg.database.host}';
+    $DB['PORT'] = '${toString cfg.database.port}';
+    $DB['DATABASE'] = '${cfg.database.name}';
+    $DB['USER'] = '${cfg.database.user}';
+    $DB['PASSWORD'] = ${if cfg.database.passwordFile != null then "file_get_contents('${cfg.database.passwordFile}')" else "''"};
+    // Schema name. Used for IBM DB2 and PostgreSQL.
+    $DB['SCHEMA'] = ''';
+    $ZBX_SERVER = '${cfg.server.address}';
+    $ZBX_SERVER_PORT = '${toString cfg.server.port}';
+    $ZBX_SERVER_NAME = ''';
+    $IMAGE_FORMAT_DEFAULT = IMAGE_FORMAT_PNG;
+  '';
+
+in
+{
+  # interface
+
+  options.services = {
+    zabbixWeb = {
+      enable = mkEnableOption "the Zabbix web interface";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.zabbix.web;
+        defaultText = "zabbix.web";
+        description = "Which Zabbix package to use.";
+      };
+
+      server = {
+        port = mkOption {
+          type = types.int;
+          description = "The port of the Zabbix server to connect to.";
+          default = 10051;
+        };
+
+        address = mkOption {
+          type = types.str;
+          description = "The IP address or hostname of the Zabbix server to connect to.";
+          default = "localhost";
+        };
+      };
+
+      database = {
+        type = mkOption {
+          type = types.enum [ "mysql" "pgsql" "oracle" ];
+          example = "mysql";
+          default = "pgsql";
+          description = "Database engine to use.";
+        };
+
+        host = mkOption {
+          type = types.str;
+          default = "";
+          description = "Database host address.";
+        };
+
+        port = mkOption {
+          type = types.int;
+          default =
+            if cfg.database.type == "mysql" then config.services.mysql.port
+            else if cfg.database.type == "pgsql" then config.services.postgresql.port
+            else 1521;
+          description = "Database host port.";
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "zabbix";
+          description = "Database name.";
+        };
+
+        user = mkOption {
+          type = types.str;
+          default = "zabbix";
+          description = "Database user.";
+        };
+
+        passwordFile = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          example = "/run/keys/zabbix-dbpassword";
+          description = ''
+            A file containing the password corresponding to
+            <option>database.user</option>.
+          '';
+        };
+
+        socket = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          example = "/run/postgresql";
+          description = "Path to the unix socket file to use for authentication.";
+        };
+      };
+
+      virtualHost = mkOption {
+        type = types.submodule ({
+          options = import ../web-servers/apache-httpd/per-server-options.nix {
+            inherit lib;
+            forMainServer = false;
+          };
+        });
+        example = {
+          hostName = "zabbix.example.org";
+          enableSSL = true;
+          adminAddr = "webmaster@example.org";
+          sslServerCert = "/var/lib/acme/zabbix.example.org/full.pem";
+          sslServerKey = "/var/lib/acme/zabbix.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 the Zabbix PHP pool. See the documentation on <literal>php-fpm.conf</literal> for details on configuration directives.
+        '';
+      };
+
+    };
+  };
+
+  # implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.tmpfiles.rules = [
+      "d '${stateDir}' 0750 ${user} ${group} - -"
+      "d '${stateDir}/session' 0750 ${user} ${config.services.httpd.group} - -"
+    ];
+
+    services.phpfpm.pools.zabbix = {
+      phpOptions = ''
+        # https://www.zabbix.com/documentation/current/manual/installation/install
+        memory_limit = 128M
+        post_max_size = 16M
+        upload_max_filesize = 2M
+        max_execution_time = 300
+        max_input_time = 300
+        session.auto_start = 0
+        mbstring.func_overload = 0
+        always_populate_raw_post_data = -1
+        # https://bbs.archlinux.org/viewtopic.php?pid=1745214#p1745214
+        session.save_path = ${stateDir}/session
+      '' + optionalString (config.time.timeZone != null) ''
+        date.timezone = "${config.time.timeZone}"
+      '' + optionalString (cfg.database.type == "oracle") ''
+        extension=${pkgs.phpPackages.oci8}/lib/php/extensions/oci8.so
+      '';
+      listen = "/run/phpfpm/zabbix.sock";
+      extraConfig = ''
+        listen.owner = ${config.services.httpd.user};
+        listen.group = ${config.services.httpd.group};
+        user = ${user};
+        group = ${config.services.httpd.group};
+        env[ZABBIX_CONFIG] = ${zabbixConfig}
+        ${cfg.poolConfig}
+      '';
+    };
+
+    services.httpd = {
+      enable = true;
+      adminAddr = mkDefault cfg.virtualHost.adminAddr;
+      extraModules = [ "proxy_fcgi" ];
+      virtualHosts = [ (mkMerge [
+        cfg.virtualHost {
+          documentRoot = mkForce "${cfg.package}/share/zabbix";
+          extraConfig = ''
+            <Directory "${cfg.package}/share/zabbix">
+              <FilesMatch "\.php$">
+                <If "-f %{REQUEST_FILENAME}">
+                  SetHandler "proxy:unix:${fpm.listen}|fcgi://localhost/"
+                </If>
+              </FilesMatch>
+              AllowOverride all
+              Options -Indexes
+              DirectoryIndex index.php
+            </Directory>
+          '';
+        }
+      ]) ];
+    };
+
+    users.users.${user} = mapAttrs (name: mkDefault) {
+      description = "Zabbix daemon user";
+      uid = config.ids.uids.zabbix;
+      inherit group;
+    };
+
+    users.groups.${group} = mapAttrs (name: mkDefault) {
+      gid = config.ids.gids.zabbix;
+    };
+
+  };
+}
diff --git a/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixos/modules/services/web-servers/apache-httpd/default.nix
index bf99f6c132af..ea9476a7c915 100644
--- a/nixos/modules/services/web-servers/apache-httpd/default.nix
+++ b/nixos/modules/services/web-servers/apache-httpd/default.nix
@@ -21,10 +21,9 @@ let
     else [{ip = "*"; port = 80;}];
 
   getListen = cfg:
-    let list = (lib.optional (cfg.port != 0) {ip = "*"; port = cfg.port;}) ++ cfg.listen;
-    in if list == []
-        then defaultListen cfg
-        else list;
+    if cfg.listen == []
+      then defaultListen cfg
+      else cfg.listen;
 
   listenToString = l: "${l.ip}:${toString l.port}";
 
@@ -638,7 +637,7 @@ in
                      message = "SSL is enabled for httpd, but sslServerCert and/or sslServerKey haven't been specified."; }
                  ];
 
-    warnings = map (cfg: ''apache-httpd's port option is deprecated. Use listen = [{/*ip = "*"; */ port = ${toString cfg.port};}]; instead'' ) (lib.filter (cfg: cfg.port != 0) allHosts);
+    warnings = map (cfg: "apache-httpd's extraSubservices option is deprecated. Most existing subservices have been ported to the NixOS module system. Please update your configuration accordingly.") (lib.filter (cfg: cfg.extraSubservices != []) allHosts);
 
     users.users = optionalAttrs (mainCfg.user == "wwwrun") (singleton
       { name = "wwwrun";
@@ -672,7 +671,7 @@ in
 
         wantedBy = [ "multi-user.target" ];
         wants = [ "keys.target" ];
-        after = [ "network.target" "fs.target" "postgresql.service" "keys.target" ];
+        after = [ "network.target" "fs.target" "keys.target" ];
 
         path =
           [ httpd pkgs.coreutils pkgs.gnugrep ]
diff --git a/nixos/modules/services/web-servers/apache-httpd/foswiki.nix b/nixos/modules/services/web-servers/apache-httpd/foswiki.nix
deleted file mode 100644
index 8c1ac8935a47..000000000000
--- a/nixos/modules/services/web-servers/apache-httpd/foswiki.nix
+++ /dev/null
@@ -1,78 +0,0 @@
-{ config, pkgs, lib, serverInfo, ... }:
-let
-  inherit (pkgs) foswiki;
-  inherit (serverInfo.serverConfig) user group;
-  inherit (config) vardir;
-in
-{
-  options.vardir = lib.mkOption {
-    type = lib.types.path;
-    default = "/var/www/foswiki";
-    description = "The directory where variable foswiki data will be stored and served from.";
-  };
-
-  # TODO: this will probably need to be better customizable
-  extraConfig =
-    let httpd-conf = pkgs.runCommand "foswiki-httpd.conf"
-      { preferLocalBuild = true; }
-      ''
-        substitute '${foswiki}/foswiki_httpd_conf.txt' "$out" \
-          --replace /var/www/foswiki/ "${vardir}/"
-      '';
-    in
-      ''
-        RewriteEngine on
-        RewriteRule /foswiki/(.*) ${vardir}/$1
-
-        <Directory "${vardir}">
-          Require all granted
-        </Directory>
-
-        Include ${httpd-conf}
-        <Directory "${vardir}/pub">
-          Options FollowSymlinks
-        </Directory>
-      '';
-
-  /** This handles initial setup and updates.
-      It will probably need some tweaking, maybe per-site.  */
-  startupScript = pkgs.writeScript "foswiki_startup.sh" (
-    let storeLink = "${vardir}/package"; in
-    ''
-      [ -e '${storeLink}' ] || needs_setup=1
-      mkdir -p '${vardir}'
-      cd '${vardir}'
-      ln -sf -T '${foswiki}' '${storeLink}'
-
-      if [ -n "$needs_setup" ]; then # do initial setup
-        mkdir -p bin lib
-        # setup most of data/ as copies only
-        cp -r '${foswiki}'/data '${vardir}/'
-        rm -r '${vardir}'/data/{System,mime.types}
-        ln -sr -t '${vardir}/data/' '${storeLink}'/data/{System,mime.types}
-
-        ln -sr '${storeLink}/locale' .
-
-        mkdir pub
-        ln -sr '${storeLink}/pub/System' pub/
-
-        mkdir templates
-        ln -sr '${storeLink}'/templates/* templates/
-
-        ln -sr '${storeLink}/tools' .
-
-        mkdir -p '${vardir}'/working/{logs,tmp}
-        ln -sr '${storeLink}/working/README' working/ # used to check dir validity
-
-        chown -R '${user}:${group}' .
-        chmod +w -R .
-      fi
-
-      # bin/* and lib/* shall always be overwritten, in case files are added
-      ln -srf '${storeLink}'/bin/* '${vardir}/bin/'
-      ln -srf '${storeLink}'/lib/* '${vardir}/lib/'
-    ''
-    /* Symlinking bin/ one-by-one ensures that ${vardir}/lib/LocalSite.cfg
-        is used instead of ${foswiki}/... */
-  );
-}
diff --git a/nixos/modules/services/web-servers/apache-httpd/mediawiki.nix b/nixos/modules/services/web-servers/apache-httpd/mediawiki.nix
deleted file mode 100644
index 6234478014ce..000000000000
--- a/nixos/modules/services/web-servers/apache-httpd/mediawiki.nix
+++ /dev/null
@@ -1,349 +0,0 @@
-{ config, lib, pkgs, serverInfo, php, ... }:
-
-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
-  '';
-
-  mediawikiConfig = pkgs.writeText "LocalSettings.php"
-    ''
-      <?php
-        # Copied verbatim from the default (generated) LocalSettings.php.
-        if( defined( 'MW_INSTALL_PATH' ) ) {
-                $IP = MW_INSTALL_PATH;
-        } else {
-                $IP = dirname( __FILE__ );
-        }
-
-        $path = array( $IP, "$IP/includes", "$IP/languages" );
-        set_include_path( implode( PATH_SEPARATOR, $path ) . PATH_SEPARATOR . get_include_path() );
-
-        require_once( "$IP/includes/DefaultSettings.php" );
-
-        if ( $wgCommandLineMode ) {
-                if ( isset( $_SERVER ) && array_key_exists( 'REQUEST_METHOD', $_SERVER ) ) {
-                        die( "This script must be run from the command line\n" );
-                }
-        }
-
-        $wgScriptPath = "${config.urlPrefix}";
-
-        # We probably need to set $wgSecretKey and $wgCacheEpoch.
-
-        # Paths to external programs.
-        $wgDiff3 = "${pkgs.diffutils}/bin/diff3";
-        $wgDiff = "${pkgs.diffutils}/bin/diff";
-        $wgImageMagickConvertCommand = "${pkgs.imagemagick.out}/bin/convert";
-
-        #$wgDebugLogFile = "/tmp/mediawiki_debug_log.txt";
-
-        # Database configuration.
-        $wgDBtype = "${config.dbType}";
-        $wgDBserver = "${config.dbServer}";
-        $wgDBuser = "${config.dbUser}";
-        $wgDBpassword = "${config.dbPassword}";
-        $wgDBname = "${config.dbName}";
-
-        # E-mail.
-        $wgEmergencyContact = "${config.emergencyContact}";
-        $wgPasswordSender = "${config.passwordSender}";
-
-        $wgSitename = "${config.siteName}";
-
-        ${optionalString (config.logo != "") ''
-          $wgLogo = "${config.logo}";
-        ''}
-
-        ${optionalString (config.articleUrlPrefix != "") ''
-          $wgArticlePath = "${config.articleUrlPrefix}/$1";
-        ''}
-
-        ${optionalString config.enableUploads ''
-          $wgEnableUploads = true;
-          $wgUploadDirectory = "${config.uploadDir}";
-        ''}
-
-        ${optionalString (config.defaultSkin != "") ''
-          $wgDefaultSkin = "${config.defaultSkin}";
-        ''}
-
-        ${config.extraConfig}
-      ?>
-    '';
-
-  # Unpack Mediawiki and put the config file in its root directory.
-  mediawikiRoot = pkgs.stdenv.mkDerivation rec {
-    name= "mediawiki-1.31.1";
-
-    src = pkgs.fetchurl {
-      url = "https://releases.wikimedia.org/mediawiki/1.31/${name}.tar.gz";
-      sha256 = "13x48clij21cmysjkpnx68vggchrdasqp7b290j87xlfgjhdhnnf";
-    };
-
-    skins = config.skins;
-    extensions = config.extensions;
-
-    buildPhase =
-      ''
-        for skin in $skins; do
-          cp -prvd $skin/* skins/
-        done
-        for extension in $extensions; do
-          cp -prvd $extension/* extensions/
-        done
-      ''; # */
-
-    installPhase =
-      ''
-        mkdir -p $out
-        cp -r * $out
-        cp ${mediawikiConfig} $out/LocalSettings.php
-        sed -i \
-        -e 's|/bin/bash|${pkgs.bash}/bin/bash|g' \
-        -e 's|/usr/bin/timeout|${pkgs.coreutils}/bin/timeout|g' \
-          $out/includes/shell/limit.sh \
-          $out/includes/GlobalFunctions.php
-      '';
-  };
-
-  mediawikiScripts = pkgs.runCommand "mediawiki-${config.id}-scripts" {
-      buildInputs = [ pkgs.makeWrapper ];
-      preferLocalBuild = true;
-    } ''
-      mkdir -p $out/bin
-      for i in changePassword.php createAndPromote.php userOptions.php edit.php nukePage.php update.php; do
-        makeWrapper ${php}/bin/php $out/bin/mediawiki-${config.id}-$(basename $i .php) \
-          --add-flags ${mediawikiRoot}/maintenance/$i
-      done
-    '';
-
-in
-
-{
-
-  extraConfig =
-    ''
-      ${optionalString config.enableUploads ''
-        Alias ${config.urlPrefix}/images ${config.uploadDir}
-
-        <Directory ${config.uploadDir}>
-            ${allGranted}
-            Options -Indexes
-        </Directory>
-      ''}
-
-      ${if config.urlPrefix != "" then "Alias ${config.urlPrefix} ${mediawikiRoot}" else ''
-        RewriteEngine On
-        RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-f
-        RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-d
-        ${concatMapStringsSep "\n" (u: "RewriteCond %{REQUEST_URI} !^${u.urlPath}") serverInfo.vhostConfig.servedDirs}
-        ${concatMapStringsSep "\n" (u: "RewriteCond %{REQUEST_URI} !^${u.urlPath}") serverInfo.vhostConfig.servedFiles}
-        RewriteRule ${if config.enableUploads
-          then "!^/images"
-          else "^.*\$"
-        } %{DOCUMENT_ROOT}/${if config.articleUrlPrefix == ""
-          then ""
-          else "${config.articleUrlPrefix}/"
-        }index.php [L]
-      ''}
-
-      <Directory ${mediawikiRoot}>
-          ${allGranted}
-          DirectoryIndex index.php
-      </Directory>
-
-      ${optionalString (config.articleUrlPrefix != "") ''
-        Alias ${config.articleUrlPrefix} ${mediawikiRoot}/index.php
-      ''}
-    '';
-
-  documentRoot = if config.urlPrefix == "" then mediawikiRoot else null;
-
-  enablePHP = true;
-
-  options = {
-
-    id = mkOption {
-      default = "main";
-      description = ''
-        A unique identifier necessary to keep multiple MediaWiki server
-        instances on the same machine apart.  This is used to
-        disambiguate the administrative scripts, which get names like
-        mediawiki-$id-change-password.
-      '';
-    };
-
-    dbType = mkOption {
-      default = "postgres";
-      example = "mysql";
-      description = "Database type.";
-    };
-
-    dbName = mkOption {
-      default = "mediawiki";
-      description = "Name of the database that holds the MediaWiki data.";
-    };
-
-    dbServer = mkOption {
-      default = ""; # use a Unix domain socket
-      example = "10.0.2.2";
-      description = ''
-        The location of the database server.  Leave empty to use a
-        database server running on the same machine through a Unix
-        domain socket.
-      '';
-    };
-
-    dbUser = mkOption {
-      default = "mediawiki";
-      description = "The user name for accessing the database.";
-    };
-
-    dbPassword = mkOption {
-      default = "";
-      example = "foobar";
-      description = ''
-        The password of the database user.  Warning: this is stored in
-        cleartext in the Nix store!
-      '';
-    };
-
-    emergencyContact = mkOption {
-      default = serverInfo.serverConfig.adminAddr;
-      example = "admin@example.com";
-      description = ''
-        Emergency contact e-mail address.  Defaults to the Apache
-        admin address.
-      '';
-    };
-
-    passwordSender = mkOption {
-      default = serverInfo.serverConfig.adminAddr;
-      example = "password@example.com";
-      description = ''
-        E-mail address from which password confirmations originate.
-        Defaults to the Apache admin address.
-      '';
-    };
-
-    siteName = mkOption {
-      default = "MediaWiki";
-      example = "Foobar Wiki";
-      description = "Name of the wiki";
-    };
-
-    logo = mkOption {
-      default = "";
-      example = "/images/logo.png";
-      description = "The URL of the site's logo (which should be a 135x135px image).";
-    };
-
-    urlPrefix = mkOption {
-      default = "/w";
-      description = ''
-        The URL prefix under which the Mediawiki service appears.
-      '';
-    };
-
-    articleUrlPrefix = mkOption {
-      default = "/wiki";
-      example = "";
-      description = ''
-        The URL prefix under which article pages appear,
-        e.g. http://server/wiki/Page.  Leave empty to use the main URL
-        prefix, e.g. http://server/w/index.php?title=Page.
-      '';
-    };
-
-    enableUploads = mkOption {
-      default = false;
-      description = "Whether to enable file uploads.";
-    };
-
-    uploadDir = mkOption {
-      default = throw "You must specify `uploadDir'.";
-      example = "/data/mediawiki-upload";
-      description = "The directory that stores uploaded files.";
-    };
-
-    defaultSkin = mkOption {
-      default = "";
-      example = "nostalgia";
-      description = "Set this value to change the default skin used by MediaWiki.";
-    };
-
-    skins = mkOption {
-      default = [];
-      type = types.listOf types.path;
-      description =
-        ''
-          List of paths whose content is copied to the ‘skins’
-          subdirectory of the MediaWiki installation.
-        '';
-    };
-
-    extensions = mkOption {
-      default = [];
-      type = types.listOf types.path;
-      description =
-        ''
-          List of paths whose content is copied to the 'extensions'
-          subdirectory of the MediaWiki installation.
-        '';
-    };
-
-    extraConfig = mkOption {
-      type = types.lines;
-      default = "";
-      example =
-        ''
-          $wgEnableEmail = false;
-        '';
-      description = ''
-        Any additional text to be appended to MediaWiki's
-        configuration file.  This is a PHP script.  For configuration
-        settings, see <link xlink:href='https://www.mediawiki.org/wiki/Manual:Configuration_settings'/>.
-      '';
-    };
-
-  };
-
-  extraPath = [ mediawikiScripts ];
-
-  # !!! Need to specify that Apache has a dependency on PostgreSQL!
-
-  startupScript = pkgs.writeScript "mediawiki_startup.sh"
-    # Initialise the database automagically if we're using a Postgres
-    # server on localhost.
-    (optionalString (config.dbType == "postgres" && config.dbServer == "") ''
-      if ! ${pkgs.postgresql}/bin/psql -l | grep -q ' ${config.dbName} ' ; then
-          ${pkgs.postgresql}/bin/createuser --no-superuser --no-createdb --no-createrole "${config.dbUser}" || true
-          ${pkgs.postgresql}/bin/createdb "${config.dbName}" -O "${config.dbUser}"
-          ( echo 'CREATE LANGUAGE plpgsql;'
-            cat ${mediawikiRoot}/maintenance/postgres/tables.sql
-            echo 'CREATE TEXT SEARCH CONFIGURATION public.default ( COPY = pg_catalog.english );'
-            echo COMMIT
-          ) | ${pkgs.postgresql}/bin/psql -U "${config.dbUser}" "${config.dbName}"
-      fi
-      ${php}/bin/php ${mediawikiRoot}/maintenance/update.php
-    '');
-
-  robotsEntries = optionalString (config.articleUrlPrefix != "")
-    ''
-      User-agent: *
-      Disallow: ${config.urlPrefix}/
-      Disallow: ${config.articleUrlPrefix}/Special:Search
-      Disallow: ${config.articleUrlPrefix}/Special:Random
-    '';
-
-}
diff --git a/nixos/modules/services/web-servers/apache-httpd/mercurial.nix b/nixos/modules/services/web-servers/apache-httpd/mercurial.nix
deleted file mode 100644
index 4b8ee2b17ea7..000000000000
--- a/nixos/modules/services/web-servers/apache-httpd/mercurial.nix
+++ /dev/null
@@ -1,75 +0,0 @@
-{ config, pkgs, lib, ... }:
-
-let
-  inherit (pkgs) mercurial;
-  inherit (lib) mkOption;
-
-  urlPrefix = config.urlPrefix;
-
-  cgi = pkgs.stdenv.mkDerivation {
-    name = "mercurial-cgi";
-    buildCommand = ''
-      mkdir -p $out
-      cp -v ${mercurial}/share/cgi-bin/hgweb.cgi $out
-      sed -i "s|/path/to/repo/or/config|$out/hgweb.config|" $out/hgweb.cgi
-      echo "
-      [collections]
-      ${config.dataDir} = ${config.dataDir}
-      [web]
-      style = gitweb
-      allow_push = *
-      " > $out/hgweb.config
-    '';
-  };
-
-in {
-
-  extraConfig = ''
-    RewriteEngine on
-    RewriteRule /(.*) ${cgi}/hgweb.cgi/$1
-
-    <Location "${urlPrefix}">
-        AuthType Basic
-        AuthName "Mercurial repositories"
-        AuthUserFile ${config.dataDir}/hgusers
-        <LimitExcept GET>
-            Require valid-user
-        </LimitExcept>
-    </Location>
-    <Directory "${cgi}">
-        Order allow,deny
-        Allow from all
-        AllowOverride All
-        Options ExecCGI
-        AddHandler cgi-script .cgi
-        PassEnv PYTHONPATH
-    </Directory>
-  '';
-
-  robotsEntries = ''
-    User-agent: *
-    Disallow: ${urlPrefix}
-  '';
-
-  extraServerPath = [ pkgs.python ];
-
-  globalEnvVars = [ { name = "PYTHONPATH"; value = "${mercurial}/lib/${pkgs.python.libPrefix}/site-packages"; } ];
-
-  options = {
-    urlPrefix = mkOption {
-      default = "/hg";
-      description = "
-        The URL prefix under which the Mercurial service appears.
-        Use the empty string to have it appear in the server root.
-      ";
-    };
-
-    dataDir = mkOption {
-      example = "/data/mercurial";
-      description = "
-        Path to the directory that holds the repositories.
-      ";
-    };
-  };
-
-}
diff --git a/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix b/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix
index 4bbd041b6e04..536e707137c6 100644
--- a/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix
+++ b/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix
@@ -24,14 +24,6 @@ with lib;
     '';
   };
 
-  port = mkOption {
-    type = types.int;
-    default = 0;
-    description = ''
-      Port for the server. Option will be removed, use <option>listen</option> instead.
-  '';
-  };
-
   listen = mkOption {
      type = types.listOf (types.submodule (
           {
diff --git a/nixos/modules/services/web-servers/apache-httpd/phabricator.nix b/nixos/modules/services/web-servers/apache-httpd/phabricator.nix
deleted file mode 100644
index efd4a7b5f0fb..000000000000
--- a/nixos/modules/services/web-servers/apache-httpd/phabricator.nix
+++ /dev/null
@@ -1,50 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  phabricatorRoot = pkgs.phabricator;
-in {
-
-  enablePHP = true;
-  extraApacheModules = [ "mod_rewrite" ];
-  DocumentRoot = "${phabricatorRoot}/phabricator/webroot";
-
-  options = {
-      git = mkOption {
-          default = true;
-          description = "Enable git repositories.";
-      };
-      mercurial = mkOption {
-          default = true;
-          description = "Enable mercurial repositories.";
-      };
-      subversion = mkOption {
-          default = true;
-          description = "Enable subversion repositories.";
-      };
-  };
-
-  extraConfig = ''
-      DocumentRoot ${phabricatorRoot}/phabricator/webroot
-
-      RewriteEngine on
-      RewriteRule ^/rsrc/(.*) - [L,QSA]
-      RewriteRule ^/favicon.ico - [L,QSA]
-      RewriteRule ^(.*)$ /index.php?__path__=$1 [B,L,QSA]
-  '';
-
-  extraServerPath = [
-      "${pkgs.which}"
-      "${pkgs.diffutils}"
-      ] ++
-      (if config.mercurial then ["${pkgs.mercurial}"] else []) ++
-      (if config.subversion then ["${pkgs.subversion.out}"] else []) ++
-      (if config.git then ["${pkgs.git}"] else []);
-
-  startupScript = pkgs.writeScript "activatePhabricator" ''
-      mkdir -p /var/repo
-      chown wwwrun /var/repo
-  '';
-
-}
diff --git a/nixos/modules/services/web-servers/apache-httpd/tomcat-connector.nix b/nixos/modules/services/web-servers/apache-httpd/tomcat-connector.nix
deleted file mode 100644
index a883bb2b3433..000000000000
--- a/nixos/modules/services/web-servers/apache-httpd/tomcat-connector.nix
+++ /dev/null
@@ -1,103 +0,0 @@
-{ config, pkgs, serverInfo, lib, ... }:
-
-let
-  extraWorkersProperties = lib.optionalString (config ? extraWorkersProperties) config.extraWorkersProperties;
-  
-  workersProperties = pkgs.writeText "workers.properties" ''
-# Define list of workers that will be used
-# for mapping requests
-# The configuration directives are valid
-# for the mod_jk version 1.2.18 and later
-#
-worker.list=loadbalancer,status
-
-# Define Node1
-# modify the host as your host IP or DNS name.
-worker.node1.port=8009
-worker.node1.host=localhost
-worker.node1.type=ajp13
-worker.node1.lbfactor=1
-
-# Load-balancing behaviour
-worker.loadbalancer.type=lb
-worker.loadbalancer.balance_workers=node1
-
-# Status worker for managing load balancer
-worker.status.type=status
-
-${extraWorkersProperties}
-  '';
-in
-{
-
-  options = {
-    extraWorkersProperties = lib.mkOption {
-      default = "";
-      description = "Additional configuration for the workers.properties file.";
-    };
-  };
-
-  extraModules = [
-    { name = "jk"; path = "${pkgs.tomcat_connectors}/modules/mod_jk.so"; }
-  ];
-
-  extraConfig = ''
-# Where to find workers.properties
-JkWorkersFile ${workersProperties}
-
-# Where to put jk logs
-JkLogFile ${serverInfo.serverConfig.logDir}/mod_jk.log
-
-# Set the jk log level [debug/error/info]
-JkLogLevel info
-
-# Select the log format
-JkLogStampFormat "[%a %b %d %H:%M:%S %Y]"
-
-# JkOptions indicates to send SSK KEY SIZE
-# Note: Changed from +ForwardURICompat.
-# See http://tomcat.apache.org/security-jk.html
-JkOptions +ForwardKeySize +ForwardURICompatUnparsed -ForwardDirectories
-
-# JkRequestLogFormat
-JkRequestLogFormat "%w %V %T"
-
-# Mount your applications
-JkMount /__application__/* loadbalancer
-
-# You can use external file for mount points.
-# It will be checked for updates each 60 seconds.
-# The format of the file is: /url=worker
-# /examples/*=loadbalancer
-#JkMountFile uriworkermap.properties
-
-# Add shared memory.
-# This directive is present with 1.2.10 and
-# later versions of mod_jk, and is needed for
-# for load balancing to work properly
-# Note: Replaced JkShmFile logs/jk.shm due to SELinux issues. Refer to
-# https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=225452
-JkShmFile ${serverInfo.serverConfig.stateDir}/jk.shm
-
-# Static files in all Tomcat webapp context directories are served by apache
-JkAutoAlias /var/tomcat/webapps
-
-# All requests go to worker by default
-JkMount /* loadbalancer
-# Serve some static files using httpd
-#JkUnMount /*.html loadbalancer
-#JkUnMount /*.jpg  loadbalancer
-#JkUnMount /*.gif  loadbalancer
-#JkUnMount /*.css  loadbalancer
-#JkUnMount /*.png  loadbalancer
-#JkUnMount /*.js  loadbalancer
-
-# Add jkstatus for managing runtime data
-<Location /jkstatus/>
-JkMount status
-Order deny,allow
-Deny from all
-Allow from 127.0.0.1
-</Location>
-  '';
-}
diff --git a/nixos/modules/services/web-servers/apache-httpd/trac.nix b/nixos/modules/services/web-servers/apache-httpd/trac.nix
deleted file mode 100644
index 28b411a64b6f..000000000000
--- a/nixos/modules/services/web-servers/apache-httpd/trac.nix
+++ /dev/null
@@ -1,121 +0,0 @@
-{ config, lib, pkgs, serverInfo, ... }:
-
-with lib;
-
-let
-
-  # Build a Subversion instance with Apache modules and Swig/Python bindings.
-  subversion = pkgs.subversion.override {
-    bdbSupport = true;
-    httpServer = true;
-    pythonBindings = true;
-    apacheHttpd = httpd;
-  };
-
-  httpd = serverInfo.serverConfig.package;
-
-  versionPre24 = versionOlder httpd.version "2.4";
-
-in
-
-{
-
-  options = {
-
-    projectsLocation = mkOption {
-      description = "URL path in which Trac projects can be accessed";
-      default = "/projects";
-    };
-
-    projects = mkOption {
-      description = "List of projects that should be provided by Trac. If they are not defined yet empty projects are created.";
-      default = [];
-      example =
-        [ { identifier = "myproject";
-            name = "My Project";
-            databaseURL="postgres://root:password@/tracdb";
-            subversionRepository="/data/subversion/myproject";
-          }
-        ];
-    };
-
-    user = mkOption {
-      default = "wwwrun";
-      description = "User account under which Trac runs.";
-    };
-
-    group = mkOption {
-      default = "wwwrun";
-      description = "Group under which Trac runs.";
-    };
-
-    ldapAuthentication = {
-      enable = mkOption {
-        default = false;
-        description = "Enable the ldap authentication in trac";
-      };
-
-      url = mkOption {
-        default = "ldap://127.0.0.1/dc=example,dc=co,dc=ke?uid?sub?(objectClass=inetOrgPerson)";
-        description = "URL of the LDAP authentication";
-      };
-
-      name = mkOption {
-        default = "Trac server";
-        description = "AuthName";
-      };
-    };
-
-  };
-
-  extraModules = singleton
-    { name = "python"; path = "${pkgs.mod_python}/modules/mod_python.so"; };
-
-  extraConfig = ''
-    <Location ${config.projectsLocation}>
-      SetHandler mod_python
-      PythonHandler trac.web.modpython_frontend
-      PythonOption TracEnvParentDir /var/trac/projects
-      PythonOption TracUriRoot ${config.projectsLocation}
-      PythonOption PYTHON_EGG_CACHE /var/trac/egg-cache
-    </Location>
-    ${if config.ldapAuthentication.enable then ''
-      <LocationMatch "^${config.projectsLocation}[^/]+/login$">
-        AuthType Basic
-        AuthName "${config.ldapAuthentication.name}"
-        AuthBasicProvider "ldap"
-        AuthLDAPURL "${config.ldapAuthentication.url}"
-        ${if versionPre24 then "authzldapauthoritative Off" else ""}
-        require valid-user
-      </LocationMatch>
-    '' else ""}
-  '';
-
-  globalEnvVars = singleton
-    { name = "PYTHONPATH";
-      value =
-        makeSearchPathOutput "lib" "lib/${pkgs.python.libPrefix}/site-packages"
-          [ pkgs.mod_python
-            pkgs.pythonPackages.trac
-            pkgs.pythonPackages.setuptools
-            pkgs.pythonPackages.genshi
-            pkgs.pythonPackages.psycopg2
-            subversion
-          ];
-    };
-
-  startupScript = pkgs.writeScript "activateTrac" ''
-    mkdir -p /var/trac
-    chown ${config.user}:${config.group} /var/trac
-
-    ${concatMapStrings (project:
-      ''
-        if [ ! -d /var/trac/${project.identifier} ]
-        then
-            export PYTHONPATH=${pkgs.pythonPackages.psycopg2}/lib/${pkgs.python.libPrefix}/site-packages
-            ${pkgs.pythonPackages.trac}/bin/trac-admin /var/trac/${project.identifier} initenv "${project.name}" "${project.databaseURL}" svn "${project.subversionRepository}"
-        fi
-      '' ) (config.projects)}
-  '';
-
-}
diff --git a/nixos/modules/services/web-servers/apache-httpd/wordpress.nix b/nixos/modules/services/web-servers/apache-httpd/wordpress.nix
deleted file mode 100644
index 3dddda138fed..000000000000
--- a/nixos/modules/services/web-servers/apache-httpd/wordpress.nix
+++ /dev/null
@@ -1,285 +0,0 @@
-{ config, lib, pkgs, serverInfo, ... }:
-# http://codex.wordpress.org/Hardening_WordPress
-
-with lib;
-
-let
-  # Our bare-bones wp-config.php file using the above settings
-  wordpressConfig = pkgs.writeText "wp-config.php" ''
-    <?php
-    define('DB_NAME',     '${config.dbName}');
-    define('DB_USER',     '${config.dbUser}');
-    define('DB_PASSWORD', file_get_contents('${config.dbPasswordFile}'));
-    define('DB_HOST',     '${config.dbHost}');
-    define('DB_CHARSET',  'utf8');
-    $table_prefix  = '${config.tablePrefix}';
-    define('AUTOMATIC_UPDATER_DISABLED', true);
-    ${config.extraConfig}
-    if ( !defined('ABSPATH') )
-    	define('ABSPATH', dirname(__FILE__) . '/');
-    require_once(ABSPATH . 'wp-settings.php');
-  '';
-
-  # .htaccess to support pretty URLs
-  htaccess = pkgs.writeText "htaccess" ''
-    <IfModule mod_rewrite.c>
-    RewriteEngine On
-    RewriteBase /
-    RewriteRule ^index\.php$ - [L]
-
-    # add a trailing slash to /wp-admin
-    RewriteRule ^wp-admin$ wp-admin/ [R=301,L]
-
-    RewriteCond %{REQUEST_FILENAME} -f [OR]
-    RewriteCond %{REQUEST_FILENAME} -d
-    RewriteRule ^ - [L]
-    RewriteRule ^(wp-(content|admin|includes).*) $1 [L]
-    RewriteRule ^(.*\.php)$ $1 [L]
-    RewriteRule . index.php [L]
-    </IfModule>
-
-    ${config.extraHtaccess}
-  '';
-
-  # WP translation can be found here:
-  #   https://github.com/nixcloud/wordpress-translations
-  supportedLanguages = {
-    en_GB = { revision="d6c005372a5318fd758b710b77a800c86518be13"; sha256="0qbbsi87k47q4rgczxx541xz4z4f4fr49hw4lnaxkdsf5maz8p9p"; };
-    de_DE = { revision="3c62955c27baaae98fd99feb35593d46562f4736"; sha256="1shndgd11dk836dakrjlg2arwv08vqx6j4xjh4jshvwmjab6ng6p"; };
-    zh_ZN = { revision="12b9f811e8cae4b6ee41de343d35deb0a8fdda6d"; sha256="1339ggsxh0g6lab37jmfxicsax4h702rc3fsvv5azs7mcznvwh47"; };
-    fr_FR = { revision="688c8b1543e3d38d9e8f57e0a6f2a2c3c8b588bd"; sha256="1j41iak0i6k7a4wzyav0yrllkdjjskvs45w53db8vfm8phq1n014"; };
-  };
-
-  downloadLanguagePack = language: revision: sha256s:
-    pkgs.stdenv.mkDerivation rec {
-      name = "wp_${language}";
-      src = pkgs.fetchFromGitHub {
-        owner = "nixcloud";
-        repo = "wordpress-translations";
-        rev = revision;
-        sha256 = sha256s;
-      };
-      installPhase = "mkdir -p $out; cp -R * $out/";
-    };
-
-  selectedLanguages = map (lang: downloadLanguagePack lang supportedLanguages.${lang}.revision supportedLanguages.${lang}.sha256) (config.languages);
-
-  # The wordpress package itself
-  wordpressRoot = pkgs.stdenv.mkDerivation rec {
-    name = "wordpress";
-    src = config.package;
-    installPhase = ''
-      mkdir -p $out
-      # copy all the wordpress files we downloaded
-      cp -R * $out/
-
-      # symlink the wordpress config
-      ln -s ${wordpressConfig} $out/wp-config.php
-      # symlink custom .htaccess
-      ln -s ${htaccess} $out/.htaccess
-      # symlink uploads directory
-      ln -s ${config.wordpressUploads} $out/wp-content/uploads
-
-      # remove bundled plugins(s) coming with wordpress
-      rm -Rf $out/wp-content/plugins/*
-      # remove bundled themes(s) coming with wordpress
-      rm -Rf $out/wp-content/themes/*
-
-      # copy additional theme(s)
-      ${concatMapStrings (theme: "cp -r ${theme} $out/wp-content/themes/${theme.name}\n") config.themes}
-      # copy additional plugin(s)
-      ${concatMapStrings (plugin: "cp -r ${plugin} $out/wp-content/plugins/${plugin.name}\n") (config.plugins) }
-
-      # symlink additional translation(s)
-      mkdir -p $out/wp-content/languages
-      ${concatMapStrings (language: "ln -s ${language}/*.mo ${language}/*.po $out/wp-content/languages/\n") (selectedLanguages) }
-    '';
-  };
-
-in
-
-{
-
-  # And some httpd extraConfig to make things work nicely
-  extraConfig = ''
-    <Directory ${wordpressRoot}>
-      DirectoryIndex index.php
-      Allow from *
-      Options FollowSymLinks
-      AllowOverride All
-    </Directory>
-  '';
-
-  enablePHP = true;
-
-  options = {
-    package = mkOption {
-      type = types.path;
-      default = pkgs.wordpress;
-      description = ''
-        Path to the wordpress sources.
-        Upgrading? We have a test! nix-build ./nixos/tests/wordpress.nix
-      '';
-    };
-    dbHost = mkOption {
-      default = "localhost";
-      description = "The location of the database server.";
-      example = "localhost";
-    };
-    dbName = mkOption {
-      default = "wordpress";
-      description = "Name of the database that holds the Wordpress data.";
-      example = "localhost";
-    };
-    dbUser = mkOption {
-      default = "wordpress";
-      description = "The dbUser, read: the username, for the database.";
-      example = "wordpress";
-    };
-    dbPassword = mkOption {
-      default = "wordpress";
-      description = ''
-        The mysql password to the respective dbUser.
-
-        Warning: this password is stored in the world-readable Nix store. It's
-        recommended to use the $dbPasswordFile option since that gives you control over
-        the security of the password. $dbPasswordFile also takes precedence over $dbPassword.
-      '';
-      example = "wordpress";
-    };
-    dbPasswordFile = mkOption {
-      type = types.str;
-      default = toString (pkgs.writeTextFile {
-        name = "wordpress-dbpassword";
-        text = config.dbPassword;
-      });
-      example = "/run/keys/wordpress-dbpassword";
-      description = ''
-        Path to a file that contains the mysql password to the respective dbUser.
-        The file should be readable by the user: config.services.httpd.user.
-
-        $dbPasswordFile takes precedence over the $dbPassword option.
-
-        This defaults to a file in the world-readable Nix store that contains the value
-        of the $dbPassword option. It's recommended to override this with a path not in
-        the Nix store. Tip: use nixops key management:
-        <link xlink:href='https://nixos.org/nixops/manual/#idm140737318306400'/>
-      '';
-    };
-    tablePrefix = mkOption {
-      default = "wp_";
-      description = ''
-        The $table_prefix is the value placed in the front of your database tables. Change the value if you want to use something other than wp_ for your database prefix. Typically this is changed if you are installing multiple WordPress blogs in the same database. See <link xlink:href='http://codex.wordpress.org/Editing_wp-config.php#table_prefix'/>.
-      '';
-    };
-    wordpressUploads = mkOption {
-    default = "/data/uploads";
-      description = ''
-        This directory is used for uploads of pictures and must be accessible (read: owned) by the httpd running user. The directory passed here is automatically created and permissions are given to the httpd running user.
-      '';
-    };
-    plugins = mkOption {
-      default = [];
-      type = types.listOf types.path;
-      description =
-        ''
-          List of path(s) to respective plugin(s) which are symlinked from the 'plugins' directory. Note: These plugins need to be packaged before use, see example.
-        '';
-      example = ''
-        # Wordpress plugin 'akismet' installation example
-        akismetPlugin = pkgs.stdenv.mkDerivation {
-          name = "akismet-plugin";
-          # Download the theme from the wordpress site
-          src = pkgs.fetchurl {
-            url = https://downloads.wordpress.org/plugin/akismet.3.1.zip;
-            sha256 = "1i4k7qyzna08822ncaz5l00wwxkwcdg4j9h3z2g0ay23q640pclg";
-          };
-          # We need unzip to build this package
-          buildInputs = [ pkgs.unzip ];
-          # Installing simply means copying all files to the output directory
-          installPhase = "mkdir -p $out; cp -R * $out/";
-        };
-
-        And then pass this theme to the themes list like this:
-          plugins = [ akismetPlugin ];
-      '';
-    };
-    themes = mkOption {
-      default = [];
-      type = types.listOf types.path;
-      description =
-        ''
-          List of path(s) to respective theme(s) which are symlinked from the 'theme' directory. Note: These themes need to be packaged before use, see example.
-        '';
-      example = ''
-        # For shits and giggles, let's package the responsive theme
-        responsiveTheme = pkgs.stdenv.mkDerivation {
-          name = "responsive-theme";
-          # Download the theme from the wordpress site
-          src = pkgs.fetchurl {
-            url = http://wordpress.org/themes/download/responsive.1.9.7.6.zip;
-            sha256 = "06i26xlc5kdnx903b1gfvnysx49fb4kh4pixn89qii3a30fgd8r8";
-          };
-          # We need unzip to build this package
-          buildInputs = [ pkgs.unzip ];
-          # Installing simply means copying all files to the output directory
-          installPhase = "mkdir -p $out; cp -R * $out/";
-        };
-
-        And then pass this theme to the themes list like this:
-          themes = [ responsiveTheme ];
-      '';
-    };
-    languages = mkOption {
-          default = [];
-          description = "Installs wordpress language packs based on the list, see wordpress.nix for possible translations.";
-          example = "[ \"en_GB\" \"de_DE\" ];";
-    };
-    extraConfig = mkOption {
-      type = types.lines;
-      default = "";
-      example =
-        ''
-          define( 'AUTOSAVE_INTERVAL', 60 ); // Seconds
-        '';
-      description = ''
-        Any additional text to be appended to Wordpress's wp-config.php
-        configuration file.  This is a PHP script.  For configuration
-        settings, see <link xlink:href='http://codex.wordpress.org/Editing_wp-config.php'/>.
-      '';
-    };
-    extraHtaccess = mkOption {
-      default = "";
-      example =
-        ''
-          php_value upload_max_filesize 20M
-          php_value post_max_size 20M
-        '';
-      description = ''
-        Any additional text to be appended to Wordpress's .htaccess file.
-      '';
-    };
-  };
-
-  documentRoot = wordpressRoot;
-
-  # FIXME adding the user has to be done manually for the time being
-  startupScript = pkgs.writeScript "init-wordpress.sh" ''
-    #!/bin/sh
-    mkdir -p ${config.wordpressUploads}
-    chown ${serverInfo.serverConfig.user} ${config.wordpressUploads}
-
-    # we should use systemd dependencies here
-    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 [ ! -S /run/mysqld/mysqld.sock ]; do
-        sleep 1
-      done
-      ${pkgs.mysql}/bin/mysql -e 'CREATE DATABASE ${config.dbName};'
-      ${pkgs.mysql}/bin/mysql -e "GRANT ALL ON ${config.dbName}.* TO ${config.dbUser}@localhost IDENTIFIED BY \"$(cat ${config.dbPasswordFile})\";"
-    else
-      echo "Good, no need to do anything database related."
-    fi
-  '';
-}
diff --git a/nixos/modules/services/web-servers/apache-httpd/zabbix.nix b/nixos/modules/services/web-servers/apache-httpd/zabbix.nix
deleted file mode 100644
index cab16593bcbc..000000000000
--- a/nixos/modules/services/web-servers/apache-httpd/zabbix.nix
+++ /dev/null
@@ -1,84 +0,0 @@
-{ config, lib, pkgs, serverInfo, ... }:
-
-with lib;
-
-let
-
-  # The Zabbix PHP frontend needs to be able to write its
-  # configuration settings (the connection info to the database) to
-  # the "conf" subdirectory.  So symlink $out/conf to some directory
-  # outside of the Nix store where we want to keep this stateful info.
-  # Note that different instances of the frontend will therefore end
-  # up with their own copies of the PHP sources.  !!! Alternatively,
-  # we could generate zabbix.conf.php declaratively.
-  zabbixPHP = pkgs.runCommand "${pkgs.zabbix.server.name}-php" {}
-    ''
-      cp -rs ${pkgs.zabbix.server}/share/zabbix/php "$out"
-      chmod -R u+w $out
-      ln -s "${if config.configFile == null
-               then "${config.stateDir}/zabbix.conf.php"
-               else config.configFile}" "$out/conf/zabbix.conf.php"
-    '';
-
-in
-
-{
-
-  enablePHP = true;
-
-  phpOptions =
-    ''
-      post_max_size = 32M
-      max_execution_time = 300
-      max_input_time = 300
-    '';
-
-  extraConfig = ''
-    Alias ${config.urlPrefix}/ ${zabbixPHP}/
-
-    <Directory ${zabbixPHP}>
-      DirectoryIndex index.php
-      Order deny,allow
-      Allow from *
-    </Directory>
-  '';
-
-  startupScript = pkgs.writeScript "zabbix-startup-hook" ''
-    mkdir -p ${config.stateDir}
-    chown -R ${serverInfo.serverConfig.user} ${config.stateDir}
-  '';
-
-  # The frontend needs "ps" to find out whether zabbix_server is running.
-  extraServerPath = [ pkgs.procps ];
-
-  options = {
-
-    urlPrefix = mkOption {
-      default = "/zabbix";
-      description = "
-        The URL prefix under which the Zabbix service appears.
-        Use the empty string to have it appear in the server root.
-      ";
-    };
-
-    configFile = mkOption {
-      default = null;
-      type = types.nullOr types.path;
-      description = ''
-        The configuration file (zabbix.conf.php) which contains the database
-        connection settings. If not set, the configuration settings will created
-        by the web installer.
-      '';
-    };
-
-    stateDir = mkOption {
-      default = "/var/lib/zabbix/frontend";
-      description = "
-        Directory where the dynamically generated configuration data
-        of the PHP frontend will be stored.
-      ";
-    };
-
-  };
-
-}
diff --git a/nixos/modules/services/web-servers/lighttpd/cgit.nix b/nixos/modules/services/web-servers/lighttpd/cgit.nix
index 4ec4a5a3359e..9f25dc34f3f0 100644
--- a/nixos/modules/services/web-servers/lighttpd/cgit.nix
+++ b/nixos/modules/services/web-servers/lighttpd/cgit.nix
@@ -42,10 +42,10 @@ in
     configText = mkOption {
       default = "";
       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
+        cache-size=1000
+        scan-path=/srv/git
       '';
       type = types.lines;
       description = ''
diff --git a/nixos/modules/services/web-servers/lighttpd/collectd.nix b/nixos/modules/services/web-servers/lighttpd/collectd.nix
index e70c980d5243..3f262451c2cb 100644
--- a/nixos/modules/services/web-servers/lighttpd/collectd.nix
+++ b/nixos/modules/services/web-servers/lighttpd/collectd.nix
@@ -12,7 +12,7 @@ let
 
   defaultCollectionCgi = config.services.collectd.package.overrideDerivation(old: {
     name = "collection.cgi";
-    configurePhase = "true";
+    dontConfigure = true;
     buildPhase = "true";
     installPhase = ''
       substituteInPlace contrib/collection.cgi --replace '"/etc/collection.conf"' '$ENV{COLLECTION_CONF}'
diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix
index d6653a65a95a..2b7fcb314041 100644
--- a/nixos/modules/services/web-servers/nginx/default.nix
+++ b/nixos/modules/services/web-servers/nginx/default.nix
@@ -269,17 +269,6 @@ let
       ${optionalString (config.proxyPass != null && cfg.recommendedProxySettings) "include ${recommendedProxyConfig};"}
     }
   '') (sortProperties (mapAttrsToList (k: v: v // { location = k; }) locations)));
-  mkBasicAuth = vhostName: authDef: let
-    htpasswdFile = pkgs.writeText "${vhostName}.htpasswd" (
-      concatStringsSep "\n" (mapAttrsToList (user: password: ''
-        ${user}:{PLAIN}${password}
-      '') authDef)
-    );
-  in ''
-    auth_basic secured;
-    auth_basic_user_file ${htpasswdFile};
-  '';
-
   mkHtpasswd = vhostName: authDef: pkgs.writeText "${vhostName}.htpasswd" (
     concatStringsSep "\n" (mapAttrsToList (user: password: ''
       ${user}:{PLAIN}${password}
diff --git a/nixos/modules/services/x11/compton.nix b/nixos/modules/services/x11/compton.nix
index d4357324c870..c02c9bfd94e8 100644
--- a/nixos/modules/services/x11/compton.nix
+++ b/nixos/modules/services/x11/compton.nix
@@ -7,57 +7,35 @@ 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;
+  pairOf = x: with types; addCheck (listOf x) (y: length y == 2);
 
   floatBetween = a: b: with lib; with types;
     addCheck str (x: versionAtLeast x a && versionOlder x b);
 
-  pairOf = x: with types; addCheck (listOf x) (y: length y == 2);
-
-  opacityRules = optionalString (length cfg.opacityRules != 0)
-    (concatMapStringsSep ",\n" (rule: ''"${rule}"'') cfg.opacityRules);
-
-  configFile = pkgs.writeText "compton.conf"
-    (optionalString cfg.fade ''
-      # fading
-      fading = true;
-      fade-delta    = ${toString cfg.fadeDelta};
-      fade-in-step  = ${elemAt cfg.fadeSteps 0};
-      fade-out-step = ${elemAt cfg.fadeSteps 1};
-      fade-exclude  = ${toJSON cfg.fadeExclude};
-    '' + optionalString cfg.shadow ''
-
-      # shadows
-      shadow = true;
-      shadow-offset-x = ${toString (elemAt cfg.shadowOffsets 0)};
-      shadow-offset-y = ${toString (elemAt cfg.shadowOffsets 1)};
-      shadow-opacity  = ${cfg.shadowOpacity};
-      shadow-exclude  = ${toJSON cfg.shadowExclude};
-    '' + ''
-
-      # opacity
-      active-opacity   = ${cfg.activeOpacity};
-      inactive-opacity = ${cfg.inactiveOpacity};
-
-      wintypes:
-      ${literalAttrs cfg.wintypes};
-
-      opacity-rule = [
-        ${opacityRules}
-      ];
-
-      # other options
-      backend = ${toJSON cfg.backend};
-      vsync = ${boolToString cfg.vSync};
-      refresh-rate = ${toString cfg.refreshRate};
-    '' + cfg.extraOptions);
+  toConf = attrs: concatStringsSep "\n"
+    (mapAttrsToList
+      (k: v: let
+        sep = if isAttrs v then ":" else "=";
+        # Basically a tinkered lib.generators.mkKeyValueDefault
+        mkValueString = v:
+          if isBool v        then boolToString v
+          else if isInt v    then toString v
+          else if isFloat v  then toString v
+          else if isString v then ''"${escape [ ''"'' ] v}"''
+          else if isList v   then "[ "
+            + concatMapStringsSep " , " mkValueString v
+            + " ]"
+          else if isAttrs v  then "{ "
+            + concatStringsSep " "
+              (mapAttrsToList
+                (key: value: "${toString key}=${mkValueString value};")
+                v)
+            + " }"
+          else abort "compton.mkValueString: unexpected type (v = ${v})";
+      in "${escape [ sep ] k}${sep}${mkValueString v};")
+      attrs);
+
+  configFile = pkgs.writeText "compton.conf" (toConf cfg.settings);
 
 in {
 
@@ -236,23 +214,13 @@ in {
       '';
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.compton;
-      defaultText = "pkgs.compton";
-      example = literalExample "pkgs.compton";
-      description = ''
-        Compton derivation to use.
-      '';
-    };
-
-    extraOptions = mkOption {
-      type = types.lines;
-      default = "";
-      example = ''
-        unredir-if-possible = true;
-        dbe = true;
-      '';
+    settings = let
+      configTypes = with types; either bool (either int (either float str));
+      # types.loaOf converts lists to sets
+      loaOf = t: with types; either (listOf t) (attrsOf t);
+    in mkOption {
+      type = loaOf (types.either configTypes (loaOf (types.either configTypes (loaOf configTypes))));
+      default = {};
       description = ''
         Additional Compton configuration.
       '';
@@ -260,6 +228,42 @@ in {
   };
 
   config = mkIf cfg.enable {
+    services.compton.settings = let
+      # Hard conversion to float, literally lib.toInt but toFloat
+      toFloat = str: let
+        may_be_float = builtins.fromJSON str;
+      in if builtins.isFloat may_be_float
+        then may_be_float
+        else throw "Could not convert ${str} to float.";
+    in {
+      # fading
+      fading           = mkDefault cfg.fade;
+      fade-delta       = mkDefault cfg.fadeDelta;
+      fade-in-step     = mkDefault (toFloat (elemAt cfg.fadeSteps 0));
+      fade-out-step    = mkDefault (toFloat (elemAt cfg.fadeSteps 1));
+      fade-exclude     = mkDefault cfg.fadeExclude;
+
+      # shadows
+      shadow           = mkDefault cfg.shadow;
+      shadow-offset-x  = mkDefault (elemAt cfg.shadowOffsets 0);
+      shadow-offset-y  = mkDefault (elemAt cfg.shadowOffsets 1);
+      shadow-opacity   = mkDefault (toFloat cfg.shadowOpacity);
+      shadow-exclude   = mkDefault cfg.shadowExclude;
+
+      # opacity
+      active-opacity   = mkDefault (toFloat cfg.activeOpacity);
+      inactive-opacity = mkDefault (toFloat cfg.inactiveOpacity);
+
+      wintypes         = mkDefault cfg.wintypes;
+
+      opacity-rule     = mkDefault cfg.opacityRules;
+
+      # other options
+      backend          = mkDefault cfg.backend;
+      vsync            = mkDefault cfg.vSync;
+      refresh-rate     = mkDefault cfg.refreshRate;
+    };
+
     systemd.user.services.compton = {
       description = "Compton composite manager";
       wantedBy = [ "graphical-session.target" ];
@@ -271,13 +275,13 @@ in {
       };
 
       serviceConfig = {
-        ExecStart = "${cfg.package}/bin/compton --config ${configFile}";
+        ExecStart = "${pkgs.compton}/bin/compton --config ${configFile}";
         RestartSec = 3;
         Restart = "always";
       };
     };
 
-    environment.systemPackages = [ cfg.package ];
+    environment.systemPackages = [ pkgs.compton ];
   };
 
 }
diff --git a/nixos/modules/services/x11/desktop-managers/default.nix b/nixos/modules/services/x11/desktop-managers/default.nix
index 2b1e9169e5f6..671a959cdde1 100644
--- a/nixos/modules/services/x11/desktop-managers/default.nix
+++ b/nixos/modules/services/x11/desktop-managers/default.nix
@@ -20,7 +20,7 @@ in
   imports = [
     ./none.nix ./xterm.nix ./xfce.nix ./plasma5.nix ./lumina.nix
     ./lxqt.nix ./enlightenment.nix ./gnome3.nix ./kodi.nix ./maxx.nix
-    ./mate.nix ./pantheon.nix
+    ./mate.nix ./pantheon.nix ./surf-display.nix
   ];
 
   options = {
diff --git a/nixos/modules/services/x11/desktop-managers/enlightenment.nix b/nixos/modules/services/x11/desktop-managers/enlightenment.nix
index 4da146940648..527e4b18045b 100644
--- a/nixos/modules/services/x11/desktop-managers/enlightenment.nix
+++ b/nixos/modules/services/x11/desktop-managers/enlightenment.nix
@@ -34,7 +34,7 @@ in
       pkgs.gtk2 # To get GTK+'s themes.
       pkgs.tango-icon-theme
 
-      pkgs.gnome2.gnomeicontheme
+      pkgs.gnome2.gnome_icon_theme
       pkgs.xorg.xcursorthemes
     ];
 
diff --git a/nixos/modules/services/x11/desktop-managers/gnome3.nix b/nixos/modules/services/x11/desktop-managers/gnome3.nix
index ef6820d33260..cd7502421255 100644
--- a/nixos/modules/services/x11/desktop-managers/gnome3.nix
+++ b/nixos/modules/services/x11/desktop-managers/gnome3.nix
@@ -154,7 +154,8 @@ in {
     services.hardware.bolt.enable = mkDefault true;
     services.xserver.libinput.enable = mkDefault true; # for controlling touchpad settings via gnome control center
     systemd.packages = [ pkgs.gnome3.vino ];
-    services.flatpak.extraPortals = [ pkgs.xdg-desktop-portal-gtk ];
+    xdg.portal.enable = true;
+    xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-gtk ];
 
     # If gnome3 is installed, build vim for gtk3 too.
     nixpkgs.config.vim.gui = "gtk3";
@@ -229,7 +230,7 @@ in {
 
     # Use the correct gnome3 packageSet
     networking.networkmanager.basePackages =
-      { inherit (pkgs) networkmanager modemmanager wpa_supplicant;
+      { inherit (pkgs) networkmanager modemmanager wpa_supplicant crda;
         inherit (pkgs.gnome3) networkmanager-openvpn networkmanager-vpnc
                               networkmanager-openconnect networkmanager-fortisslvpn
                               networkmanager-iodine networkmanager-l2tp; };
diff --git a/nixos/modules/services/x11/desktop-managers/pantheon.nix b/nixos/modules/services/x11/desktop-managers/pantheon.nix
index 9a0f77a856a7..8e1272f3c92b 100644
--- a/nixos/modules/services/x11/desktop-managers/pantheon.nix
+++ b/nixos/modules/services/x11/desktop-managers/pantheon.nix
@@ -102,6 +102,10 @@ in
           # Makes qt applications look less alien
           export QT_QPA_PLATFORMTHEME=gtk3
           export QT_STYLE_OVERRIDE=adwaita
+
+          # Settings from elementary-default-settings
+          export GTK_CSD=1
+          export GTK_MODULES=$GTK_MODULES:pantheon-filechooser-module
       fi
     '';
 
@@ -147,7 +151,7 @@ in
 
     networking.networkmanager.enable = mkDefault true;
     networking.networkmanager.basePackages =
-      { inherit (pkgs) networkmanager modemmanager wpa_supplicant;
+      { inherit (pkgs) networkmanager modemmanager wpa_supplicant crda;
         inherit (pkgs.gnome3) networkmanager-openvpn networkmanager-vpnc
                               networkmanager-openconnect networkmanager-fortisslvpn
                               networkmanager-iodine networkmanager-l2tp; };
@@ -195,7 +199,7 @@ in
       ]);
 
     fonts.fonts = with pkgs; [
-      opensans-ttf
+      open-sans
       roboto-mono
       pantheon.elementary-redacted-script # needed by screenshot-tool
     ];
diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix
index dc8bfc7dc172..94a307ae1007 100644
--- a/nixos/modules/services/x11/desktop-managers/plasma5.nix
+++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix
@@ -21,6 +21,13 @@ in
         description = "Enable the Plasma 5 (KDE 5) desktop environment.";
       };
 
+      phononBackend = mkOption {
+        type = types.enum [ "gstreamer" "vlc" ];
+        default = "gstreamer";
+        example = "vlc";
+        description = "Phonon audio backend to install.";
+      };
+
       enableQt4Support = mkOption {
         type = types.bool;
         default = true;
@@ -64,8 +71,8 @@ in
       };
 
       security.wrappers = {
-        kcheckpass.source = "${lib.getBin plasma5.kscreenlocker}/lib/libexec/kcheckpass";
-        "start_kdeinit".source = "${lib.getBin pkgs.kinit}/lib/libexec/kf5/start_kdeinit";
+        kcheckpass.source = "${lib.getBin plasma5.kscreenlocker}/libexec/kcheckpass";
+        "start_kdeinit".source = "${lib.getBin pkgs.kinit}/libexec/kf5/start_kdeinit";
         kwin_wayland = {
           source = "${lib.getBin plasma5.kwin}/bin/kwin_wayland";
           capabilities = "cap_sys_nice+ep";
@@ -161,15 +168,17 @@ in
 
           qtvirtualkeyboard
 
-          libsForQt5.phonon-backend-gstreamer
-
           xdg-user-dirs # Update user dirs as described in https://freedesktop.org/wiki/Software/xdg-user-dirs/
         ]
 
-        ++ lib.optionals cfg.enableQt4Support [ pkgs.phonon-backend-gstreamer ]
+        # Phonon audio backend
+        ++ lib.optional (cfg.phononBackend == "gstreamer") libsForQt5.phonon-backend-gstreamer
+        ++ lib.optional (cfg.phononBackend == "gstreamer" && cfg.enableQt4Support) pkgs.phonon-backend-gstreamer
+        ++ lib.optional (cfg.phononBackend == "vlc") libsForQt5.phonon-backend-vlc
+        ++ lib.optional (cfg.phononBackend == "vlc" && cfg.enableQt4Support) pkgs.phonon-backend-vlc
 
         # Optional hardware support features
-        ++ lib.optional config.hardware.bluetooth.enable bluedevil
+        ++ lib.optionals config.hardware.bluetooth.enable [ bluedevil bluez-qt ]
         ++ lib.optional config.networking.networkmanager.enable plasma-nm
         ++ lib.optional config.hardware.pulseaudio.enable plasma-pa
         ++ lib.optional config.powerManagement.enable powerdevil
@@ -224,6 +233,9 @@ in
       security.pam.services.sddm.enableKwallet = true;
       security.pam.services.slim.enableKwallet = true;
 
+      xdg.portal.enable = true;
+      xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-kde ];
+
       # Update the start menu for each user that is currently logged in
       system.userActivationScripts.plasmaSetup = ''
         # The KDE icon cache is supposed to update itself
diff --git a/nixos/modules/services/x11/desktop-managers/surf-display.nix b/nixos/modules/services/x11/desktop-managers/surf-display.nix
new file mode 100644
index 000000000000..232bbf5c55d4
--- /dev/null
+++ b/nixos/modules/services/x11/desktop-managers/surf-display.nix
@@ -0,0 +1,127 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.desktopManager.surf-display;
+
+  surfDisplayConf = ''
+    # Surf Kiosk Display: Wrap around surf browser and turn your
+    # system into a browser screen in KIOSK-mode.
+
+    # default download URI for all display screens if not configured individually
+    DEFAULT_WWW_URI="${cfg.defaultWwwUri}"
+
+    # Enforce fixed resolution for all displays (default: not set):
+    #DEFAULT_RESOLUTION="1920x1080"
+
+    # HTTP proxy URL, if needed (default: not set).
+    #HTTP_PROXY_URL="http://webcache:3128"
+
+    # Setting for internal inactivity timer to restart surf-display
+    # if the user goes inactive/idle.
+    INACTIVITY_INTERVAL="${builtins.toString cfg.inactivityInterval}"
+
+    # log to syslog instead of .xsession-errors
+    LOG_TO_SYSLOG="yes"
+
+    # Launch pulseaudio daemon if not already running.
+    WITH_PULSEAUDIO="yes"
+
+    # screensaver settings, see "man 1 xset" for possible options
+    SCREENSAVER_SETTINGS="${cfg.screensaverSettings}"
+
+    # disable right and middle pointer device click in browser sessions while keeping
+    # scrolling wheels' functionality intact... (consider "pointer" subcommand on
+    # xmodmap man page for details).
+    POINTER_BUTTON_MAP="${cfg.pointerButtonMap}"
+
+    # Hide idle mouse pointer.
+    HIDE_IDLE_POINTER="${cfg.hideIdlePointer}"
+
+    ${cfg.extraConfig}
+  '';
+
+in {
+  options = {
+    services.xserver.desktopManager.surf-display = {
+      enable = mkEnableOption "surf-display as a kiosk browser session";
+
+      defaultWwwUri = mkOption {
+        type = types.string;
+        default = "${pkgs.surf-display}/share/surf-display/empty-page.html";
+        example = "https://www.example.com/";
+        description = "Default URI to display.";
+      };
+
+      inactivityInterval = mkOption {
+        type = types.int;
+        default = 300;
+        example = "0";
+        description = ''
+          Setting for internal inactivity timer to restart surf-display if the
+          user goes inactive/idle to get a fresh session for the next user of
+          the kiosk.
+
+          If this value is set to zero, the whole feature of restarting due to
+          inactivity is disabled.
+        '';
+      };
+
+      screensaverSettings = mkOption {
+        type = types.string;
+        default = "";
+        description = ''
+          Screensaver settings, see <literal>man 1 xset</literal> for possible options.
+        '';
+      };
+
+      pointerButtonMap = mkOption {
+        type = types.string;
+        default = "1 0 0 4 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0";
+        description = ''
+          Disable right and middle pointer device click in browser sessions
+          while keeping scrolling wheels' functionality intact. See pointer
+          subcommand on <literal>man xmodmap</literal> for details.
+        '';
+      };
+
+      hideIdlePointer = mkOption {
+        type = types.string;
+        default = "yes";
+        example = "no";
+        description = "Hide idle mouse pointer.";
+      };
+
+      extraConfig = mkOption {
+        type = types.string;
+        default = "";
+        example = ''
+          # Enforce fixed resolution for all displays (default: not set):
+          DEFAULT_RESOLUTION="1920x1080"
+
+          # HTTP proxy URL, if needed (default: not set).
+          HTTP_PROXY_URL="http://webcache:3128"
+
+          # Configure individual display screens with host specific parameters:
+          DISPLAYS['display-host-0']="www_uri=https://www.displayserver.comany.net/display-1/index.html"
+          DISPLAYS['display-host-1']="www_uri=https://www.displayserver.comany.net/display-2/index.html"
+          DISPLAYS['display-host-2']="www_uri=https://www.displayserver.comany.net/display-3/index.html|res=1920x1280"
+          DISPLAYS['display-host-3']="www_uri=https://www.displayserver.comany.net/display-4/index.html"|res=1280x1024"
+          DISPLAYS['display-host-local-file']="www_uri=file:///usr/share/doc/surf-display/empty-page.html"
+        '';
+        description = ''
+          Extra configuration options to append to <literal>/etc/default/surf-display</literal>.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.xserver.displayManager.extraSessionFilePackages = [
+      pkgs.surf-display
+    ];
+
+    environment.etc."default/surf-display".text = surfDisplayConf;
+  };
+}
diff --git a/nixos/modules/services/x11/desktop-managers/xterm.nix b/nixos/modules/services/x11/desktop-managers/xterm.nix
index f386ebc4d3c1..ea441fbbe715 100644
--- a/nixos/modules/services/x11/desktop-managers/xterm.nix
+++ b/nixos/modules/services/x11/desktop-managers/xterm.nix
@@ -5,6 +5,7 @@ with lib;
 let
 
   cfg = config.services.xserver.desktopManager.xterm;
+  xserverEnabled = config.services.xserver.enable;
 
 in
 
@@ -13,7 +14,8 @@ in
 
     services.xserver.desktopManager.xterm.enable = mkOption {
       type = types.bool;
-      default = true;
+      default = xserverEnabled;
+      defaultText = "config.services.xserver.enable";
       description = "Enable a xterm terminal as a desktop manager.";
     };
 
diff --git a/nixos/modules/services/x11/display-managers/gdm.nix b/nixos/modules/services/x11/display-managers/gdm.nix
index ff4b91923e02..3f1669d08516 100644
--- a/nixos/modules/services/x11/display-managers/gdm.nix
+++ b/nixos/modules/services/x11/display-managers/gdm.nix
@@ -96,6 +96,14 @@ in
         type = types.bool;
       };
 
+      autoSuspend = mkOption {
+        default = true;
+        description = ''
+          Suspend the machine after inactivity.
+        '';
+        type = types.bool;
+      };
+
     };
 
   };
@@ -176,10 +184,40 @@ in
 
     systemd.user.services.dbus.wantedBy = [ "default.target" ];
 
-    programs.dconf.profiles.gdm = pkgs.writeText "dconf-gdm-profile" ''
-      system-db:local
-      ${gdm}/share/dconf/profile/gdm
-    '';
+    programs.dconf.profiles.gdm =
+    let
+      customDconf = pkgs.writeTextFile {
+        name = "gdm-dconf";
+        destination = "/dconf/gdm-custom";
+        text = ''
+          ${optionalString (!cfg.gdm.autoSuspend) ''
+            [org/gnome/settings-daemon/plugins/power]
+            sleep-inactive-ac-type='nothing'
+            sleep-inactive-battery-type='nothing'
+            sleep-inactive-ac-timeout=0
+            sleep-inactive-battery-timeout=0
+          ''}
+        '';
+      };
+
+      customDconfDb = pkgs.stdenv.mkDerivation {
+        name = "gdm-dconf-db";
+        buildCommand = ''
+          ${pkgs.gnome3.dconf}/bin/dconf compile $out ${customDconf}/dconf
+        '';
+      };
+    in pkgs.stdenv.mkDerivation {
+      name = "dconf-gdm-profile";
+      buildCommand = ''
+        # Check that the GDM profile starts with what we expect.
+        if [ $(head -n 1 ${gdm}/share/dconf/profile/gdm) != "user-db:user" ]; then
+          echo "GDM dconf profile changed, please update gdm.nix"
+          exit 1
+        fi
+        # Insert our custom DB behind it.
+        sed '2ifile-db:${customDconfDb}' ${gdm}/share/dconf/profile/gdm > $out
+      '';
+    };
 
     # Use AutomaticLogin if delay is zero, because it's immediate.
     # Otherwise with TimedLogin with zero seconds the prompt is still
diff --git a/nixos/modules/services/x11/extra-layouts.nix b/nixos/modules/services/x11/extra-layouts.nix
new file mode 100644
index 000000000000..5523dd2bf023
--- /dev/null
+++ b/nixos/modules/services/x11/extra-layouts.nix
@@ -0,0 +1,165 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  layouts = config.services.xserver.extraLayouts;
+
+  layoutOpts = {
+    options = {
+      description = mkOption {
+        type = types.str;
+        description = "A short description of the layout.";
+      };
+
+      languages = mkOption {
+        type = types.listOf types.str;
+        description =
+        ''
+          A list of languages provided by the layout.
+          (Use ISO 639-2 codes, for example: "eng" for english)
+        '';
+      };
+
+      compatFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          The path to the xkb compat file.
+          This file sets the compatibility state, used to preserve
+          compatibility with xkb-unaware programs.
+          It must contain a <literal>xkb_compat "name" { ... }</literal> block.
+        '';
+      };
+
+      geometryFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          The path to the xkb geometry file.
+          This (completely optional) file describes the physical layout of
+          keyboard, which maybe be used by programs to depict it.
+          It must contain a <literal>xkb_geometry "name" { ... }</literal> block.
+        '';
+      };
+
+      keycodesFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          The path to the xkb keycodes file.
+          This file specifies the range and the interpretation of the raw
+          keycodes sent by the keyboard.
+          It must contain a <literal>xkb_keycodes "name" { ... }</literal> block.
+        '';
+      };
+
+      symbolsFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          The path to the xkb symbols file.
+          This is the most important file: it defines which symbol or action
+          maps to each key and must contain a
+          <literal>xkb_symbols "name" { ... }</literal> block.
+        '';
+      };
+
+      typesFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          The path to the xkb types file.
+          This file specifies the key types that can be associated with
+          the various keyboard keys.
+          It must contain a <literal>xkb_types "name" { ... }</literal> block.
+        '';
+      };
+
+    };
+  };
+
+in
+
+{
+
+  ###### interface
+
+  options.services.xserver = {
+    extraLayouts = mkOption {
+      type = types.attrsOf (types.submodule layoutOpts);
+      default = {};
+      example = literalExample
+      ''
+        {
+          mine = {
+            description = "My custom xkb layout.";
+            languages = [ "eng" ];
+            symbolsFile = /path/to/my/layout;
+          };
+        }
+      '';
+      description = ''
+        Extra custom layouts that will be included in the xkb configuration.
+        Information on how to create a new layout can be found here:
+        <link xlink:href="https://www.x.org/releases/current/doc/xorg-docs/input/XKB-Enhancing.html#Defining_New_Layouts"></link>.
+        For more examples see
+        <link xlink:href="https://wiki.archlinux.org/index.php/X_KeyBoard_extension#Basic_examples"></link>
+      '';
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf (layouts != { }) {
+
+    # We don't override xkeyboard_config directly to
+    # reduce the amount of packages to be recompiled.
+    # Only the following packages are necessary to set
+    # a custom layout anyway:
+    nixpkgs.overlays = lib.singleton (self: super: {
+
+      xkb_patched = self.xorg.xkeyboardconfig_custom {
+        layouts = config.services.xserver.extraLayouts;
+      };
+
+      xorg = super.xorg // {
+        xorgserver = super.xorg.xorgserver.overrideAttrs (old: {
+          configureFlags = old.configureFlags ++ [
+            "--with-xkb-bin-directory=${self.xorg.xkbcomp}/bin"
+            "--with-xkb-path=${self.xkb_patched}/share/X11/xkb"
+          ];
+        });
+
+        setxkbmap = super.xorg.setxkbmap.overrideAttrs (old: {
+          postInstall =
+            ''
+              mkdir -p $out/share
+              ln -sfn ${self.xkb_patched}/etc/X11 $out/share/X11
+            '';
+        });
+
+        xkbcomp = super.xorg.xkbcomp.overrideAttrs (old: {
+          configureFlags = "--with-xkb-config-root=${self.xkb_patched}/share/X11/xkb";
+        });
+
+      };
+
+      ckbcomp = super.ckbcomp.override {
+        xkeyboard_config = self.xkb_patched;
+      };
+
+      xkbvalidate = super.xkbvalidate.override {
+        libxkbcommon = self.libxkbcommon.override {
+          xkeyboard_config = self.xkb_patched;
+        };
+      };
+
+    });
+
+    services.xserver.xkbDir = "${pkgs.xkb_patched}/etc/X11/xkb";
+
+  };
+
+}
diff --git a/nixos/modules/services/x11/gdk-pixbuf.nix b/nixos/modules/services/x11/gdk-pixbuf.nix
index 2dc8eabd95a7..9ad926369ec7 100644
--- a/nixos/modules/services/x11/gdk-pixbuf.nix
+++ b/nixos/modules/services/x11/gdk-pixbuf.nix
@@ -5,21 +5,21 @@ with lib;
 let
   cfg = config.services.xserver.gdk-pixbuf;
 
-  # Get packages to generate the cache for. We always include gdk_pixbuf.
-  effectivePackages = unique ([pkgs.gdk_pixbuf] ++ cfg.modulePackages);
+  # Get packages to generate the cache for. We always include gdk-pixbuf.
+  effectivePackages = unique ([pkgs.gdk-pixbuf] ++ cfg.modulePackages);
 
   # Generate the cache file by running gdk-pixbuf-query-loaders for each
   # package and concatenating the results.
   loadersCache = pkgs.runCommand "gdk-pixbuf-loaders.cache" { preferLocalBuild = true; } ''
     (
       for package in ${concatStringsSep " " effectivePackages}; do
-        module_dir="$package/${pkgs.gdk_pixbuf.moduleDir}"
+        module_dir="$package/${pkgs.gdk-pixbuf.moduleDir}"
         if [[ ! -d $module_dir ]]; then
           echo "Warning (services.xserver.gdk-pixbuf): missing module directory $module_dir" 1>&2
           continue
         fi
         GDK_PIXBUF_MODULEDIR="$module_dir" \
-          ${pkgs.gdk_pixbuf.dev}/bin/gdk-pixbuf-query-loaders
+          ${pkgs.gdk-pixbuf.dev}/bin/gdk-pixbuf-query-loaders
       done
     ) > "$out"
   '';
diff --git a/nixos/modules/services/x11/hardware/libinput.nix b/nixos/modules/services/x11/hardware/libinput.nix
index 58fe702d4969..a0a5e2656852 100644
--- a/nixos/modules/services/x11/hardware/libinput.nix
+++ b/nixos/modules/services/x11/hardware/libinput.nix
@@ -178,7 +178,7 @@ in {
       };
 
       additionalOptions = mkOption {
-        type = types.str;
+        type = types.lines;
         default = "";
         example =
         ''
diff --git a/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix
index 0f057c4ab730..b1a316706976 100644
--- a/nixos/modules/services/x11/xserver.nix
+++ b/nixos/modules/services/x11/xserver.nix
@@ -4,8 +4,6 @@ with lib;
 
 let
 
-  kernelPackages = config.boot.kernelPackages;
-
   # Abbreviations.
   cfg = config.services.xserver;
   xorg = pkgs.xorg;
@@ -16,6 +14,9 @@ let
     # Alias so people can keep using "virtualbox" instead of "vboxvideo".
     virtualbox = { modules = [ xorg.xf86videovboxvideo ]; driverName = "vboxvideo"; };
 
+    # Alias so that "radeon" uses the xf86-video-ati driver.
+    radeon = { modules = [ xorg.xf86videoati ]; driverName = "ati"; };
+
     # modesetting does not have a xf86videomodesetting package as it is included in xorgserver
     modesetting = {};
   };
@@ -243,7 +244,7 @@ in
       videoDrivers = mkOption {
         type = types.listOf types.str;
         # !!! We'd like "nv" here, but it segfaults the X server.
-        default = [ "ati" "cirrus" "vesa" "vmware" "modesetting" ];
+        default = [ "radeon" "cirrus" "vesa" "vmware" "modesetting" ];
         example = [
           "ati_unfree" "amdgpu" "amdgpu-pro"
           "nv" "nvidia" "nvidiaLegacy390" "nvidiaLegacy340" "nvidiaLegacy304"
@@ -664,10 +665,9 @@ in
         restartIfChanged = false;
 
         environment =
-          {
-            LD_LIBRARY_PATH = concatStringsSep ":" ([ "/run/opengl-driver/lib" ]
-              ++ concatLists (catAttrs "libPath" cfg.drivers));
-          } // cfg.displayManager.job.environment;
+          optionalAttrs config.hardware.opengl.setLdLibraryPath
+            { LD_LIBRARY_PATH = pkgs.addOpenGLRunpath.driverLink; }
+          // cfg.displayManager.job.environment;
 
         preStart =
           ''