about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rwxr-xr-xnixos/doc/manual/development/releases.xml24
-rw-r--r--nixos/doc/manual/release-notes/rl-1909.xml32
-rw-r--r--nixos/modules/config/update-users-groups.pl1
-rw-r--r--nixos/modules/config/vte.nix2
-rw-r--r--nixos/modules/misc/ids.nix6
-rw-r--r--nixos/modules/module-list.nix5
-rw-r--r--nixos/modules/programs/environment.nix1
-rw-r--r--nixos/modules/programs/gnupg.nix3
-rw-r--r--nixos/modules/programs/zsh/zsh.nix12
-rw-r--r--nixos/modules/rename.nix7
-rw-r--r--nixos/modules/services/databases/memcached.nix1
-rw-r--r--nixos/modules/services/desktops/gnome3/gnome-online-accounts.nix4
-rw-r--r--nixos/modules/services/desktops/gnome3/gnome-user-share.nix20
-rw-r--r--nixos/modules/services/desktops/gnome3/tracker-miners.nix6
-rw-r--r--nixos/modules/services/desktops/gnome3/tracker.nix6
-rw-r--r--nixos/modules/services/hardware/trezord.nix18
-rw-r--r--nixos/modules/services/mail/rmilter.nix252
-rw-r--r--nixos/modules/services/mail/roundcube.nix42
-rw-r--r--nixos/modules/services/misc/nix-daemon.nix7
-rw-r--r--nixos/modules/services/misc/zoneminder.nix43
-rw-r--r--nixos/modules/services/networking/dnschain.nix14
-rw-r--r--nixos/modules/services/networking/jormungandr.nix97
-rw-r--r--nixos/modules/services/networking/ofono.nix44
-rw-r--r--nixos/modules/services/networking/pdns-recursor.nix83
-rw-r--r--nixos/modules/services/networking/unifi.nix1
-rw-r--r--nixos/modules/services/security/usbguard.nix14
-rw-r--r--nixos/modules/services/torrent/transmission.nix36
-rw-r--r--nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix41
-rw-r--r--nixos/modules/services/web-apps/limesurvey.nix37
-rw-r--r--nixos/modules/services/web-apps/matomo.nix24
-rw-r--r--nixos/modules/services/web-apps/mediawiki.nix39
-rw-r--r--nixos/modules/services/web-apps/moodle.nix300
-rw-r--r--nixos/modules/services/web-apps/nextcloud.nix34
-rw-r--r--nixos/modules/services/web-apps/restya-board.nix32
-rw-r--r--nixos/modules/services/web-apps/selfoss.nix32
-rw-r--r--nixos/modules/services/web-apps/tt-rss.nix29
-rw-r--r--nixos/modules/services/web-apps/wordpress.nix34
-rw-r--r--nixos/modules/services/web-apps/zabbix.nix34
-rw-r--r--nixos/modules/services/web-servers/darkhttpd.nix77
-rw-r--r--nixos/modules/services/web-servers/phpfpm/default.nix252
-rw-r--r--nixos/modules/services/web-servers/phpfpm/pool-options.nix57
-rw-r--r--nixos/modules/services/x11/desktop-managers/gnome3.nix313
-rw-r--r--nixos/modules/system/boot/systemd.nix6
-rw-r--r--nixos/modules/virtualisation/libvirtd.nix13
-rw-r--r--nixos/tests/all-tests.nix5
-rw-r--r--nixos/tests/gnome-photos.nix42
-rw-r--r--nixos/tests/jormungandr.nix77
-rw-r--r--nixos/tests/libxmlb.nix17
-rw-r--r--nixos/tests/moodle.nix22
-rw-r--r--nixos/tests/systemd.nix7
-rw-r--r--nixos/tests/trezord.nix20
-rw-r--r--nixos/tests/xmpp/ejabberd.nix4
52 files changed, 1528 insertions, 801 deletions
diff --git a/nixos/doc/manual/development/releases.xml b/nixos/doc/manual/development/releases.xml
index 3cb16d33cd48..1cdec64f69b0 100755
--- a/nixos/doc/manual/development/releases.xml
+++ b/nixos/doc/manual/development/releases.xml
@@ -220,6 +220,30 @@
    </itemizedlist>
   </section>
  </section>
+ <section xml:id="release-managers">
+  <title>Release Management Team</title>
+  <para>
+   For each release there are two release managers. After each release the
+   release manager having managed two releases steps down and the release
+   management team of the last release appoints a new release manager.
+  </para>
+  <para>
+   This makes sure a release management team always consists of one release
+   manager who already has managed one release and one release manager being
+   introduced to their role, making it easier to pass on knowledge and
+   experience.
+  </para>
+  <para>
+   A release manager's role and responsibilities are:
+  </para>
+  <itemizedlist>
+   <listitem><para>manage the release process</para></listitem>
+   <listitem><para>start discussions about features and changes for a given release</para></listitem>
+   <listitem><para>create a roadmap</para></listitem>
+   <listitem><para>release in cooperation with Eelco Dolstra</para></listitem>
+   <listitem><para>decide which bug fixes, features, etc... get backported after a release</para></listitem>
+  </itemizedlist>
+ </section>
  <section xml:id="release-schedule">
   <title>Release schedule</title>
 
diff --git a/nixos/doc/manual/release-notes/rl-1909.xml b/nixos/doc/manual/release-notes/rl-1909.xml
index 7d2d98ddfee1..6493bb995967 100644
--- a/nixos/doc/manual/release-notes/rl-1909.xml
+++ b/nixos/doc/manual/release-notes/rl-1909.xml
@@ -237,6 +237,12 @@
     </para>
    </listitem>
    <listitem>
+    <para>
+     The <literal>shibboleth-sp</literal> package has been updated to version 3.
+     It is largely backward compatible, for further information refer to the
+     <link xlink:href="https://wiki.shibboleth.net/confluence/display/SP3/ReleaseNotes">release notes</link>
+     and <link xlink:href="https://wiki.shibboleth.net/confluence/display/SP3/UpgradingFromV2">upgrade guide</link>.
+    </para>
      <para>
        Nodejs 8 is scheduled EOL under the lifetime of 19.09 and has been dropped.
      </para>
@@ -273,6 +279,18 @@
        The <option>services.mantisbt</option> module has been removed from nixpkgs due to lack of maintainer.
      </para>
    </listitem>
+   <listitem>
+     <para>
+       Squid 3 has been removed and the <option>squid</option> derivation now refers to Squid 4.
+     </para>
+   </listitem>
+   <listitem>
+     <para>
+       The <option>services.pdns-recursor.extraConfig</option> option has been replaced by
+       <option>services.pdns-recursor.settings</option>. The new option allows setting extra
+       configuration while being better type-checked and mergeable.
+     </para>
+   </listitem>
   </itemizedlist>
  </section>
 
@@ -495,6 +513,20 @@
     been removed.
    </para>
   </listitem>
+  <listitem>
+   <para>
+    The <literal>rmilter</literal> package was removed with associated module and options due deprecation by upstream developer.
+    Use <literal>rspamd</literal> in proxy mode instead.
+   </para>
+  </listitem>
+  <listitem>
+   <para>
+     systemd cgroup accounting via the
+     <link linkend="opt-systemd.enableCgroupAccounting">systemd.enableCgroupAccounting</link>
+     option is now enabled by default. It now also enables the more recent Block IO and IP accounting
+     features.
+   </para>
+  </listitem>
   </itemizedlist>
  </section>
 </section>
diff --git a/nixos/modules/config/update-users-groups.pl b/nixos/modules/config/update-users-groups.pl
index ef5e6346f02e..59cea51c611b 100644
--- a/nixos/modules/config/update-users-groups.pl
+++ b/nixos/modules/config/update-users-groups.pl
@@ -267,6 +267,7 @@ foreach my $line (-f "/etc/shadow" ? read_file("/etc/shadow") : ()) {
     next if !defined $u;
     $hashedPassword = "!" if !$spec->{mutableUsers};
     $hashedPassword = $u->{hashedPassword} if defined $u->{hashedPassword} && !$spec->{mutableUsers}; # FIXME
+    chomp $hashedPassword;
     push @shadowNew, join(":", $name, $hashedPassword, @rest) . "\n";
     $shadowSeen{$name} = 1;
 }
diff --git a/nixos/modules/config/vte.nix b/nixos/modules/config/vte.nix
index 9024ceddaee8..d4a8c926fef2 100644
--- a/nixos/modules/config/vte.nix
+++ b/nixos/modules/config/vte.nix
@@ -42,7 +42,7 @@ in
 
   config = mkMerge [
     (mkIf config.programs.bash.vteIntegration {
-      programs.bash.interactiveShellInit = vteInitSnippet;
+      programs.bash.interactiveShellInit = mkBefore vteInitSnippet;
     })
 
     (mkIf config.programs.zsh.vteIntegration {
diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix
index 1047df95cdf5..efd8544d6a21 100644
--- a/nixos/modules/misc/ids.nix
+++ b/nixos/modules/misc/ids.nix
@@ -251,7 +251,7 @@
       gale = 223;
       matrix-synapse = 224;
       rspamd = 225;
-      rmilter = 226;
+      # rmilter = 226; # unused, removed 2019-08-22
       cfdyndns = 227;
       gammu-smsd = 228;
       pdnsd = 229;
@@ -340,6 +340,7 @@
       cockroachdb = 313;
       zoneminder = 314;
       paperless = 315;
+      mailman = 316;
 
       # When adding a uid, make sure it doesn't match an existing gid. And don't use uids above 399!
 
@@ -559,7 +560,7 @@
       gale = 223;
       matrix-synapse = 224;
       rspamd = 225;
-      rmilter = 226;
+      # rmilter = 226; # unused, removed 2019-08-22
       cfdyndns = 227;
       pdnsd = 229;
       octoprint = 230;
@@ -640,6 +641,7 @@
       cockroachdb = 313;
       zoneminder = 314;
       paperless = 315;
+      mailman = 316;
 
       # When adding a gid, make sure it doesn't match an existing
       # uid. Users and groups with the same name should have equal
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 5335ba3abd18..6331e76c648f 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -387,7 +387,6 @@
   ./services/mail/spamassassin.nix
   ./services/mail/rspamd.nix
   ./services/mail/rss2email.nix
-  ./services/mail/rmilter.nix
   ./services/mail/roundcube.nix
   ./services/mail/nullmailer.nix
   ./services/misc/airsonic.nix
@@ -609,6 +608,7 @@
   ./services/networking/iodine.nix
   ./services/networking/iperf3.nix
   ./services/networking/ircd-hybrid/default.nix
+  ./services/networking/jormungandr.nix
   ./services/networking/iwd.nix
   ./services/networking/keepalived/default.nix
   ./services/networking/keybase.nix
@@ -648,6 +648,7 @@
   ./services/networking/nullidentdmod.nix
   ./services/networking/nylon.nix
   ./services/networking/ocserv.nix
+  ./services/networking/ofono.nix
   ./services/networking/oidentd.nix
   ./services/networking/openfire.nix
   ./services/networking/openntpd.nix
@@ -788,6 +789,7 @@
   ./services/web-apps/mattermost.nix
   ./services/web-apps/mediawiki.nix
   ./services/web-apps/miniflux.nix
+  ./services/web-apps/moodle.nix
   ./services/web-apps/nextcloud.nix
   ./services/web-apps/nexus.nix
   ./services/web-apps/pgpkeyserver-lite.nix
@@ -801,6 +803,7 @@
   ./services/web-apps/zabbix.nix
   ./services/web-servers/apache-httpd/default.nix
   ./services/web-servers/caddy.nix
+  ./services/web-servers/darkhttpd.nix
   ./services/web-servers/fcgiwrap.nix
   ./services/web-servers/hitch/default.nix
   ./services/web-servers/hydron.nix
diff --git a/nixos/modules/programs/environment.nix b/nixos/modules/programs/environment.nix
index 4d762314298d..66eb83482664 100644
--- a/nixos/modules/programs/environment.nix
+++ b/nixos/modules/programs/environment.nix
@@ -21,6 +21,7 @@ in
         PAGER = mkDefault "less -R";
         EDITOR = mkDefault "nano";
         XCURSOR_PATH = [ "$HOME/.icons" ];
+        XDG_CONFIG_DIRS = [ "/etc/xdg" ]; # needs to be before profile-relative paths to allow changes through environment.etc
       };
 
     environment.profiles = mkAfter
diff --git a/nixos/modules/programs/gnupg.nix b/nixos/modules/programs/gnupg.nix
index 9618d61a1f5b..bcbc994efe9b 100644
--- a/nixos/modules/programs/gnupg.nix
+++ b/nixos/modules/programs/gnupg.nix
@@ -83,7 +83,8 @@ in
     systemd.user.sockets.dirmngr = mkIf cfg.dirmngr.enable {
       wantedBy = [ "sockets.target" ];
     };
-
+    
+    environment.systemPackages = with pkgs; [ cfg.package ];
     systemd.packages = [ cfg.package ];
 
     environment.interactiveShellInit = ''
diff --git a/nixos/modules/programs/zsh/zsh.nix b/nixos/modules/programs/zsh/zsh.nix
index 3cbfd8fa4d32..6e9eefd74d18 100644
--- a/nixos/modules/programs/zsh/zsh.nix
+++ b/nixos/modules/programs/zsh/zsh.nix
@@ -69,9 +69,7 @@ in
 
       promptInit = mkOption {
         default = ''
-          if [ "$TERM" != dumb ]; then
-              autoload -U promptinit && promptinit && prompt walters && setopt prompt_sp
-          fi
+          autoload -U promptinit && promptinit && prompt walters && setopt prompt_sp
         '';
         description = ''
           Shell script code used to initialise the zsh prompt.
@@ -213,6 +211,14 @@ in
 
         ${cfg.promptInit}
 
+        # Need to disable features to support TRAMP
+        if [ "$TERM" = dumb ]; then
+            unsetopt zle prompt_cr prompt_subst
+            unset RPS1 RPROMPT
+            PS1='$ '
+            PROMPT='$ '
+        fi
+
         # Read system-wide modifications.
         if test -f /etc/zshrc.local; then
             . /etc/zshrc.local
diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix
index e32fa6fded4a..348ad094e5ad 100644
--- a/nixos/modules/rename.nix
+++ b/nixos/modules/rename.nix
@@ -72,8 +72,8 @@ with lib;
     # PAM
     (mkRenamedOptionModule [ "security" "pam" "enableU2F" ] [ "security" "pam" "u2f" "enable" ])
 
-    (mkRemovedOptionModule [ "services" "rmilter" "bindInetSockets" ] "Use services.rmilter.bindSocket.* instead")
-    (mkRemovedOptionModule [ "services" "rmilter" "bindUnixSockets" ] "Use services.rmilter.bindSocket.* instead")
+    # rmilter/rspamd
+    (mkRemovedOptionModule [ "services" "rmilter" ] "Use services.rspamd.* instead to set up milter service")
 
     # Xsession script
     (mkRenamedOptionModule [ "services" "xserver" "displayManager" "job" "logsXsession" ] [ "services" "xserver" "displayManager" "job" "logToFile" ])
@@ -178,6 +178,9 @@ with lib;
        The starting time can be configured via <literal>services.postgresqlBackup.startAt</literal>.
     '')
 
+    # phpfpm
+    (mkRemovedOptionModule [ "services" "phpfpm" "poolConfigs" ] "Use services.phpfpm.pools instead.")
+
     # zabbixServer
     (mkRenamedOptionModule [ "services" "zabbixServer" "dbServer" ] [ "services" "zabbixServer" "database" "host" ])
 
diff --git a/nixos/modules/services/databases/memcached.nix b/nixos/modules/services/databases/memcached.nix
index f9e403dfc0c2..84d2c8674f4e 100644
--- a/nixos/modules/services/databases/memcached.nix
+++ b/nixos/modules/services/databases/memcached.nix
@@ -103,7 +103,6 @@ in
         LockPersonality = true;
         RestrictRealtime = true;
         PrivateMounts = true;
-        PrivateUsers = true;
         MemoryDenyWriteExecute = true;
       };
     };
diff --git a/nixos/modules/services/desktops/gnome3/gnome-online-accounts.nix b/nixos/modules/services/desktops/gnome3/gnome-online-accounts.nix
index 4286251357f7..748a025414a7 100644
--- a/nixos/modules/services/desktops/gnome3/gnome-online-accounts.nix
+++ b/nixos/modules/services/desktops/gnome3/gnome-online-accounts.nix
@@ -30,9 +30,9 @@ with lib;
 
   config = mkIf config.services.gnome3.gnome-online-accounts.enable {
 
-    environment.systemPackages = [ pkgs.gnome3.gnome-online-accounts ];
+    environment.systemPackages = [ pkgs.gnome-online-accounts ];
 
-    services.dbus.packages = [ pkgs.gnome3.gnome-online-accounts ];
+    services.dbus.packages = [ pkgs.gnome-online-accounts ];
 
   };
 
diff --git a/nixos/modules/services/desktops/gnome3/gnome-user-share.nix b/nixos/modules/services/desktops/gnome3/gnome-user-share.nix
index 1f6ce2ae968e..f83962877700 100644
--- a/nixos/modules/services/desktops/gnome3/gnome-user-share.nix
+++ b/nixos/modules/services/desktops/gnome3/gnome-user-share.nix
@@ -12,14 +12,7 @@ with lib;
 
     services.gnome3.gnome-user-share = {
 
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to enable GNOME User Share, a service that exports the
-          contents of the Public folder in your home directory on the local network.
-        '';
-      };
+      enable = mkEnableOption "GNOME User Share, a user-level file sharing service for GNOME";
 
     };
 
@@ -30,12 +23,13 @@ with lib;
 
   config = mkIf config.services.gnome3.gnome-user-share.enable {
 
-    environment.systemPackages = [ pkgs.gnome3.gnome-user-share ];
+    environment.systemPackages = [
+      pkgs.gnome3.gnome-user-share
+    ];
 
-    services.xserver.displayManager.sessionCommands = with pkgs.gnome3; ''
-      # Don't let gnome-control-center depend upon gnome-user-share
-      export XDG_DATA_DIRS=$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}${gnome-user-share}/share/gsettings-schemas/${gnome-user-share.name}
-    '';
+    systemd.packages = [
+      pkgs.gnome3.gnome-user-share
+    ];
 
   };
 
diff --git a/nixos/modules/services/desktops/gnome3/tracker-miners.nix b/nixos/modules/services/desktops/gnome3/tracker-miners.nix
index 20154fc2fed3..b390d8368c65 100644
--- a/nixos/modules/services/desktops/gnome3/tracker-miners.nix
+++ b/nixos/modules/services/desktops/gnome3/tracker-miners.nix
@@ -30,11 +30,11 @@ with lib;
 
   config = mkIf config.services.gnome3.tracker-miners.enable {
 
-    environment.systemPackages = [ pkgs.gnome3.tracker-miners ];
+    environment.systemPackages = [ pkgs.tracker-miners ];
 
-    services.dbus.packages = [ pkgs.gnome3.tracker-miners ];
+    services.dbus.packages = [ pkgs.tracker-miners ];
 
-    systemd.packages = [ pkgs.gnome3.tracker-miners ];
+    systemd.packages = [ pkgs.tracker-miners ];
 
   };
 
diff --git a/nixos/modules/services/desktops/gnome3/tracker.nix b/nixos/modules/services/desktops/gnome3/tracker.nix
index c061f7890499..2e8292742264 100644
--- a/nixos/modules/services/desktops/gnome3/tracker.nix
+++ b/nixos/modules/services/desktops/gnome3/tracker.nix
@@ -30,11 +30,11 @@ with lib;
 
   config = mkIf config.services.gnome3.tracker.enable {
 
-    environment.systemPackages = [ pkgs.gnome3.tracker ];
+    environment.systemPackages = [ pkgs.tracker ];
 
-    services.dbus.packages = [ pkgs.gnome3.tracker ];
+    services.dbus.packages = [ pkgs.tracker ];
 
-    systemd.packages = [ pkgs.gnome3.tracker ];
+    systemd.packages = [ pkgs.tracker ];
 
   };
 
diff --git a/nixos/modules/services/hardware/trezord.nix b/nixos/modules/services/hardware/trezord.nix
index 20bcbf83109e..62824ed7350a 100644
--- a/nixos/modules/services/hardware/trezord.nix
+++ b/nixos/modules/services/hardware/trezord.nix
@@ -22,6 +22,22 @@ in {
           Enable Trezor bridge daemon, for use with Trezor hardware bitcoin wallets.
         '';
       };
+
+      emulator.enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable Trezor emulator support.
+          '';
+       };
+
+      emulator.port = mkOption {
+        type = types.port;
+        default = 21324;
+        description = ''
+          Listening port for the Trezor emulator.
+          '';
+      };
     };
   };
   
@@ -50,7 +66,7 @@ in {
       path = [];
       serviceConfig = {
         Type = "simple";
-        ExecStart = "${pkgs.trezord}/bin/trezord-go";
+        ExecStart = "${pkgs.trezord}/bin/trezord-go ${optionalString cfg.emulator.enable "-e ${builtins.toString cfg.emulator.port}"}";
         User = "trezord";
       };
     };
diff --git a/nixos/modules/services/mail/rmilter.nix b/nixos/modules/services/mail/rmilter.nix
deleted file mode 100644
index 466365b6b305..000000000000
--- a/nixos/modules/services/mail/rmilter.nix
+++ /dev/null
@@ -1,252 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  rspamdCfg = config.services.rspamd;
-  postfixCfg = config.services.postfix;
-  cfg = config.services.rmilter;
-
-  inetSocket = addr: port: "inet:${addr}:${toString port}";
-  unixSocket = sock: "unix:${sock}";
-
-  systemdSocket = if cfg.bindSocket.type == "unix" then cfg.bindSocket.path
-    else "${cfg.bindSocket.address}:${toString cfg.bindSocket.port}";
-  rmilterSocket = if cfg.bindSocket.type == "unix" then unixSocket cfg.bindSocket.path
-    else inetSocket cfg.bindSocket.address cfg.bindSocket.port;
-
-  rmilterConf = ''
-    pidfile = /run/rmilter/rmilter.pid;
-    bind_socket = ${if cfg.socketActivation then "fd:3" else rmilterSocket};
-    tempdir = /tmp;
-  '' + (with cfg.rspamd; if enable then ''
-    spamd {
-      servers = ${concatStringsSep ", " servers};
-      connect_timeout = 1s;
-      results_timeout = 20s;
-      error_time = 10;
-      dead_time = 300;
-      maxerrors = 10;
-      reject_message = "${rejectMessage}";
-      ${optionalString (length whitelist != 0)  "whitelist = ${concatStringsSep ", " whitelist};"}
-
-      # rspamd_metric - metric for using with rspamd
-      # Default: "default"
-      rspamd_metric = "default";
-      ${extraConfig}
-    };
-  '' else "") + cfg.extraConfig;
-
-  rmilterConfigFile = pkgs.writeText "rmilter.conf" rmilterConf;
-
-in
-
-{
-
-  ###### interface
-
-  options = {
-
-    services.rmilter = {
-
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Whether to run the rmilter daemon.";
-      };
-
-      debug = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Whether to run the rmilter daemon in debug mode.";
-      };
-
-      user = mkOption {
-        type = types.string;
-        default = "rmilter";
-        description = ''
-          User to use when no root privileges are required.
-        '';
-       };
-
-      group = mkOption {
-        type = types.string;
-        default = "rmilter";
-        description = ''
-          Group to use when no root privileges are required.
-        '';
-       };
-
-      bindSocket.type = mkOption {
-        type = types.enum [ "unix" "inet" ];
-        default = "unix";
-        description = ''
-          What kind of socket rmilter should listen on. Either "unix"
-          for an Unix domain socket or "inet" for a TCP socket.
-        '';
-      };
-
-      bindSocket.path = mkOption {
-       type = types.str;
-       default = "/run/rmilter.sock";
-       description = ''
-          Path to Unix domain socket to listen on.
-        '';
-      };
-
-      bindSocket.address = mkOption {
-        type = types.str;
-        default = "[::1]";
-        example = "0.0.0.0";
-        description = ''
-          Inet address to listen on.
-        '';
-      };
-
-      bindSocket.port = mkOption {
-        type = types.int;
-        default = 11990;
-        description = ''
-          Inet port to listen on.
-        '';
-      };
-
-      socketActivation = mkOption {
-        type = types.bool;
-        default = true;
-        description = ''
-          Enable systemd socket activation for rmilter.
-
-          Disabling socket activation is not recommended when a Unix
-          domain socket is used and could lead to incorrect
-          permissions.
-        '';
-      };
-
-      rspamd = {
-        enable = mkOption {
-          type = types.bool;
-          default = rspamdCfg.enable;
-          description = "Whether to use rspamd to filter mails";
-        };
-
-        servers = mkOption {
-          type = types.listOf types.str;
-          default = ["r:/run/rspamd/rspamd.sock"];
-          description = ''
-            Spamd socket definitions.
-            Is server name is prefixed with r: it is rspamd server.
-          '';
-        };
-
-        whitelist = mkOption {
-          type = types.listOf types.str;
-          default = [ ];
-          description = "list of ips or nets that should be not checked with spamd";
-        };
-
-        rejectMessage = mkOption {
-          type = types.str;
-          default = "Spam message rejected; If this is not spam contact abuse";
-          description = "reject message for spam";
-        };
-
-        extraConfig = mkOption {
-          type = types.lines;
-          default = "";
-          description = "Custom snippet to append to end of `spamd' section";
-        };
-      };
-
-      extraConfig = mkOption {
-        type = types.lines;
-        default = "";
-        description = "Custom snippet to append to rmilter config";
-      };
-
-      postfix = {
-        enable = mkOption {
-          type = types.bool;
-          default = false;
-          description = "Add rmilter to postfix main.conf";
-        };
-
-        configFragment = mkOption {
-          type = types.str;
-          description = "Addon to postfix configuration";
-          default = ''
-            smtpd_milters = ${rmilterSocket}
-            milter_protocol = 6
-            milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}
-          '';
-        };
-      };
-
-    };
-
-  };
-
-
-  ###### implementation
-
-  config = mkMerge [
-
-    (mkIf cfg.enable {
-      warnings = [
-        ''`config.services.rmilter' is deprecated, `rmilter' deprecated and unsupported by upstream, and will be removed from next releases. Use built-in rspamd milter instead.''
-      ];
-
-      users.users = singleton {
-        name = cfg.user;
-        description = "rmilter daemon";
-        uid = config.ids.uids.rmilter;
-        group = cfg.group;
-      };
-
-      users.groups = singleton {
-        name = cfg.group;
-        gid = config.ids.gids.rmilter;
-      };
-
-      systemd.services.rmilter = {
-        description = "Rmilter Service";
-
-        wantedBy = [ "multi-user.target" ];
-        after = [ "network.target" ];
-
-        serviceConfig = {
-          ExecStart = "${pkgs.rmilter}/bin/rmilter ${optionalString cfg.debug "-d"} -n -c ${rmilterConfigFile}";
-          ExecReload = "${pkgs.coreutils}/bin/kill -USR1 $MAINPID";
-          User = cfg.user;
-          Group = cfg.group;
-          PermissionsStartOnly = true;
-          Restart = "always";
-          RuntimeDirectory = "rmilter";
-          RuntimeDirectoryMode = "0750";
-        };
-
-      };
-
-      systemd.sockets.rmilter = mkIf cfg.socketActivation {
-        description = "Rmilter service socket";
-        wantedBy = [ "sockets.target" ];
-        socketConfig = {
-          ListenStream = systemdSocket;
-          SocketUser = cfg.user;
-          SocketGroup = cfg.group;
-          SocketMode = "0660";
-        };
-      };
-    })
-
-    (mkIf (cfg.enable && cfg.rspamd.enable && rspamdCfg.enable) {
-      users.users.${cfg.user}.extraGroups = [ rspamdCfg.group ];
-    })
-
-    (mkIf (cfg.enable && cfg.postfix.enable) {
-      services.postfix.extraConfig = cfg.postfix.configFragment;
-      users.users.${postfixCfg.user}.extraGroups = [ cfg.group ];
-    })
-  ];
-}
diff --git a/nixos/modules/services/mail/roundcube.nix b/nixos/modules/services/mail/roundcube.nix
index e8b2e11bf726..bdedfa1bb701 100644
--- a/nixos/modules/services/mail/roundcube.nix
+++ b/nixos/modules/services/mail/roundcube.nix
@@ -4,6 +4,7 @@ with lib;
 
 let
   cfg = config.services.roundcube;
+  fpm = config.services.phpfpm.pools.roundcube;
 in
 {
   options.services.roundcube = {
@@ -105,7 +106,7 @@ in
             extraConfig = ''
               location ~* \.php$ {
                 fastcgi_split_path_info ^(.+\.php)(/.+)$;
-                fastcgi_pass unix:/run/phpfpm/roundcube;
+                fastcgi_pass unix:${fpm.socket};
                 include ${pkgs.nginx}/conf/fastcgi_params;
                 include ${pkgs.nginx}/conf/fastcgi.conf;
               }
@@ -119,24 +120,27 @@ in
       enable = true;
     };
 
-    services.phpfpm.poolConfigs.roundcube = ''
-      listen = /run/phpfpm/roundcube
-      listen.owner = nginx
-      listen.group = nginx
-      listen.mode = 0660
-      user = nginx
-      pm = dynamic
-      pm.max_children = 75
-      pm.start_servers = 2
-      pm.min_spare_servers = 1
-      pm.max_spare_servers = 20
-      pm.max_requests = 500
-      php_admin_value[error_log] = 'stderr'
-      php_admin_flag[log_errors] = on
-      php_admin_value[post_max_size] = 25M
-      php_admin_value[upload_max_filesize] = 25M
-      catch_workers_output = yes
-    '';
+    services.phpfpm.pools.roundcube = {
+      user = "nginx";
+      phpOptions = ''
+        error_log = 'stderr'
+        log_errors = on
+        post_max_size = 25M
+        upload_max_filesize = 25M
+      '';
+      settings = mapAttrs (name: mkDefault) {
+        "listen.owner" = "nginx";
+        "listen.group" = "nginx";
+        "listen.mode" = "0660";
+        "pm" = "dynamic";
+        "pm.max_children" = 75;
+        "pm.start_servers" = 2;
+        "pm.min_spare_servers" = 1;
+        "pm.max_spare_servers" = 20;
+        "pm.max_requests" = 500;
+        "catch_workers_output" = true;
+      };
+    };
     systemd.services.phpfpm-roundcube.after = [ "roundcube-setup.service" ];
 
     systemd.services.roundcube-setup = let
diff --git a/nixos/modules/services/misc/nix-daemon.nix b/nixos/modules/services/misc/nix-daemon.nix
index 6bc88c66dc19..088dfd71860b 100644
--- a/nixos/modules/services/misc/nix-daemon.nix
+++ b/nixos/modules/services/misc/nix-daemon.nix
@@ -8,7 +8,9 @@ let
 
   nix = cfg.package.out;
 
-  isNix20 = versionAtLeast (getVersion nix) "2.0pre";
+  nixVersion = getVersion nix;
+
+  isNix20 = versionAtLeast nixVersion "2.0pre";
 
   makeNixBuildUser = nr:
     { name = "nixbld${toString nr}";
@@ -61,6 +63,9 @@ let
           builders =
         ''}
         system-features = ${toString cfg.systemFeatures}
+        ${optionalString (versionAtLeast nixVersion "2.3pre") ''
+          sandbox-fallback = false
+        ''}
         $extraOptions
         END
       '' + optionalString cfg.checkConfig (
diff --git a/nixos/modules/services/misc/zoneminder.nix b/nixos/modules/services/misc/zoneminder.nix
index cf56ae89b399..bf38b9ad7a2d 100644
--- a/nixos/modules/services/misc/zoneminder.nix
+++ b/nixos/modules/services/misc/zoneminder.nix
@@ -2,6 +2,7 @@
 
 let
   cfg = config.services.zoneminder;
+  fpm = config.services.phpfpm.pools.zoneminder;
   pkg = pkgs.zoneminder;
 
   dirName = pkg.dirName;
@@ -19,8 +20,6 @@ let
 
   useCustomDir = cfg.storageDir != null;
 
-  socket = "/run/phpfpm/${dirName}.sock";
-
   zms = "/cgi-bin/zms";
 
   dirs = dirList: [ dirName ] ++ map (e: "${dirName}/${e}") dirList;
@@ -201,7 +200,10 @@ in {
       "zoneminder/80-nixos.conf".source    = configFile;
     };
 
-    networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.port ];
+    networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [
+      cfg.port
+      6802 # zmtrigger
+    ];
 
     services = {
       fcgiwrap = lib.mkIf useNginx {
@@ -274,7 +276,7 @@ in {
                   fastcgi_param SCRIPT_FILENAME $request_filename;
                   fastcgi_param HTTP_PROXY "";
 
-                  fastcgi_pass unix:${socket};
+                  fastcgi_pass unix:${fpm.socket};
                 }
               }
             '';
@@ -284,30 +286,27 @@ in {
 
       phpfpm = lib.mkIf useNginx {
         pools.zoneminder = {
-          listen = socket;
+          inherit user group;
           phpOptions = ''
             date.timezone = "${config.time.timeZone}"
 
             ${lib.concatStringsSep "\n" (map (e:
             "extension=${e.pkg}/lib/php/extensions/${e.name}.so") phpExtensions)}
           '';
-          extraConfig = ''
-            user = ${user}
-            group = ${group}
-
-            listen.owner = ${user}
-            listen.group = ${group}
-            listen.mode = 0660
-
-            pm = dynamic
-            pm.start_servers = 1
-            pm.min_spare_servers = 1
-            pm.max_spare_servers = 2
-            pm.max_requests = 500
-            pm.max_children = 5
-            pm.status_path = /$pool-status
-            ping.path = /$pool-ping
-          '';
+          settings = lib.mapAttrs (name: lib.mkDefault) {
+            "listen.owner" = user;
+            "listen.group" = group;
+            "listen.mode" = "0660";
+
+            "pm" = "dynamic";
+            "pm.start_servers" = 1;
+            "pm.min_spare_servers" = 1;
+            "pm.max_spare_servers" = 2;
+            "pm.max_requests" = 500;
+            "pm.max_children" = 5;
+            "pm.status_path" = "/$pool-status";
+            "ping.path" = "/$pool-ping";
+          };
         };
       };
     };
diff --git a/nixos/modules/services/networking/dnschain.nix b/nixos/modules/services/networking/dnschain.nix
index 0c2add424bac..5b58ea9b0c91 100644
--- a/nixos/modules/services/networking/dnschain.nix
+++ b/nixos/modules/services/networking/dnschain.nix
@@ -136,10 +136,16 @@ in
         "/.dns/127.0.0.1#${toString cfg.dns.port}"
       ];
 
-    services.pdns-recursor.forwardZones = mkIf cfgs.pdns-recursor.resolveDNSChainQueries
-      { bit = "127.0.0.1:${toString cfg.dns.port}";
-        dns = "127.0.0.1:${toString cfg.dns.port}";
-      };
+    services.pdns-recursor = mkIf cfgs.pdns-recursor.resolveDNSChainQueries {
+      forwardZones =
+        { bit = "127.0.0.1:${toString cfg.dns.port}";
+          dns = "127.0.0.1:${toString cfg.dns.port}";
+        };
+      luaConfig =''
+        addNTA("bit", "namecoin doesn't support DNSSEC")
+        addNTA("dns", "namecoin doesn't support DNSSEC")
+      '';
+    };
 
     users.users = singleton {
       name = username;
diff --git a/nixos/modules/services/networking/jormungandr.nix b/nixos/modules/services/networking/jormungandr.nix
new file mode 100644
index 000000000000..0c66b85fe8a5
--- /dev/null
+++ b/nixos/modules/services/networking/jormungandr.nix
@@ -0,0 +1,97 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.jormungandr;
+
+  inherit (lib) mkEnableOption mkIf mkOption;
+  inherit (lib) optionalString types;
+
+  dataDir = "/var/lib/jormungandr";
+
+  # Default settings so far, as the service matures we will
+  # move these out as separate settings
+  configSettings = {
+    storage = dataDir;
+    p2p = {
+      public_address = "/ip4/127.0.0.1/tcp/8299";
+      messages = "high";
+      blocks = "high";
+    };
+    rest = {
+      listen = "127.0.0.1:8607";
+    };
+  };
+
+  configFile = if cfg.configFile == null then
+    pkgs.writeText "jormungandr.yaml" (builtins.toJSON configSettings)
+  else cfg.configFile;
+
+in {
+
+  options = {
+
+    services.jormungandr = {
+      enable = mkEnableOption "jormungandr service";
+
+      configFile = mkOption {
+       type = types.nullOr types.path;
+       default = null;
+       example = "/var/lib/jormungandr/node.yaml";
+       description = ''
+         The path of the jormungandr blockchain configuration file in YAML format.
+         If no file is specified, a file is generated using the other options.
+       '';
+     };
+
+      secretFile = mkOption {
+       type = types.nullOr types.path;
+       default = null;
+       example = "/etc/secret/jormungandr.yaml";
+       description = ''
+         The path of the jormungandr blockchain secret node configuration file in
+         YAML format. Do not store this in nix store!
+       '';
+     };
+
+      genesisBlockHash = mkOption {
+        type = types.nullOr types.string;
+        default = null;
+        example = "d70495af81ae8600aca3e642b2427327cb6001ec4d7a0037e96a00dabed163f9";
+        description = ''
+          Set the genesis block hash (the hash of the block0) so we can retrieve
+          the genesis block (and the blockchain configuration) from the existing
+          storage or from the network.
+        '';
+      };
+
+      genesisBlockFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/var/lib/jormungandr/block-0.bin";
+        description = ''
+          The path of the genesis block file if we are hosting it locally.
+        '';
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.services.jormungandr = {
+      description = "jormungandr server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" ];
+      serviceConfig = {
+        DynamicUser = true;
+        StateDirectory = baseNameOf dataDir;
+        ExecStart = ''
+          ${pkgs.jormungandr}/bin/jormungandr --config ${configFile} \
+            ${optionalString (cfg.secretFile != null) " --secret ${cfg.secretFile}"} \
+            ${optionalString (cfg.genesisBlockHash != null) " --genesis-block-hash ${cfg.genesisBlockHash}"} \
+            ${optionalString (cfg.genesisBlockFile != null) " --genesis-block ${cfg.genesisBlockFile}"}
+        '';
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/ofono.nix b/nixos/modules/services/networking/ofono.nix
new file mode 100644
index 000000000000..40ef9433de0f
--- /dev/null
+++ b/nixos/modules/services/networking/ofono.nix
@@ -0,0 +1,44 @@
+# Ofono daemon.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.ofono;
+
+  plugin_path =
+    lib.concatMapStringsSep ":"
+      (plugin: "${plugin}/lib/ofono/plugins")
+      cfg.plugins
+    ;
+
+in
+
+{
+  ###### interface
+  options = {
+    services.ofono = {
+      enable = mkEnableOption "Ofono";
+
+      plugins = mkOption {
+        type = types.listOf types.package;
+        default = [];
+        example = literalExample "[ pkgs.modem-manager-gui ]";
+        description = ''
+          The list of plugins to install.
+        '';
+      };
+    };
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    services.dbus.packages = [ pkgs.ofono ];
+
+    systemd.packages = [ pkgs.ofono ];
+
+    systemd.services.ofono.environment.OFONO_PLUGIN_PATH = mkIf (cfg.plugins != []) plugin_path;
+
+  };
+}
diff --git a/nixos/modules/services/networking/pdns-recursor.nix b/nixos/modules/services/networking/pdns-recursor.nix
index d07deb9dcc67..ec69cc838da9 100644
--- a/nixos/modules/services/networking/pdns-recursor.nix
+++ b/nixos/modules/services/networking/pdns-recursor.nix
@@ -6,25 +6,27 @@ let
   dataDir  = "/var/lib/pdns-recursor";
   username = "pdns-recursor";
 
-  cfg   = config.services.pdns-recursor;
-  zones = mapAttrsToList (zone: uri: "${zone}.=${uri}") cfg.forwardZones;
+  cfg = config.services.pdns-recursor;
 
-  configFile = pkgs.writeText "recursor.conf" ''
-    local-address=${cfg.dns.address}
-    local-port=${toString cfg.dns.port}
-    allow-from=${concatStringsSep "," cfg.dns.allowFrom}
+  oneOrMore  = type: with types; either type (listOf type);
+  valueType  = with types; oneOf [ int str bool path ];
+  configType = with types; attrsOf (nullOr (oneOrMore valueType));
 
-    webserver-address=${cfg.api.address}
-    webserver-port=${toString cfg.api.port}
-    webserver-allow-from=${concatStringsSep "," cfg.api.allowFrom}
+  toBool    = val: if val then "yes" else "no";
+  serialize = val: with types;
+         if str.check       val then val
+    else if int.check       val then toString val
+    else if path.check      val then toString val
+    else if bool.check      val then toBool val
+    else if builtins.isList val then (concatMapStringsSep "," serialize val)
+    else "";
 
-    forward-zones=${concatStringsSep "," zones}
-    export-etc-hosts=${if cfg.exportHosts then "yes" else "no"}
-    dnssec=${cfg.dnssecValidation}
-    serve-rfc1918=${if cfg.serveRFC1918 then "yes" else "no"}
+  configFile = pkgs.writeText "recursor.conf"
+    (concatStringsSep "\n"
+      (flip mapAttrsToList cfg.settings
+        (name: val: "${name}=${serialize val}")));
 
-    ${cfg.extraConfig}
-  '';
+  mkDefaultAttrs = mapAttrs (n: v: mkDefault v);
 
 in {
   options.services.pdns-recursor = {
@@ -117,17 +119,55 @@ in {
       '';
     };
 
-    extraConfig = mkOption {
+    settings = mkOption {
+      type = configType;
+      default = { };
+      example = literalExample ''
+        {
+          loglevel = 8;
+          log-common-errors = true;
+        }
+      '';
+      description = ''
+        PowerDNS Recursor settings. Use this option to configure Recursor
+        settings not exposed in a NixOS option or to bypass one.
+        See the full documentation at
+        <link xlink:href="https://doc.powerdns.com/recursor/settings.html"/>
+        for the available options.
+      '';
+    };
+
+    luaConfig = mkOption {
       type = types.lines;
       default = "";
       description = ''
-        Extra options to be appended to the configuration file.
+        The content Lua configuration file for PowerDNS Recursor. See
+        <link xlink:href="https://doc.powerdns.com/recursor/lua-config/index.html"/>.
       '';
     };
   };
 
   config = mkIf cfg.enable {
 
+    services.pdns-recursor.settings = mkDefaultAttrs {
+      local-address = cfg.dns.address;
+      local-port    = cfg.dns.port;
+      allow-from    = cfg.dns.allowFrom;
+
+      webserver-address    = cfg.api.address;
+      webserver-port       = cfg.api.port;
+      webserver-allow-from = cfg.api.allowFrom;
+
+      forward-zones    = mapAttrsToList (zone: uri: "${zone}.=${uri}") cfg.forwardZones;
+      export-etc-hosts = cfg.exportHosts;
+      dnssec           = cfg.dnssecValidation;
+      serve-rfc1918    = cfg.serveRFC1918;
+      lua-config-file  = pkgs.writeText "recursor.lua" cfg.luaConfig;
+
+      log-timestamp  = false;
+      disable-syslog = true;
+    };
+
     users.users."${username}" = {
       home = dataDir;
       createHome = true;
@@ -150,8 +190,7 @@ in {
         AmbientCapabilities = "cap_net_bind_service";
         ExecStart = ''${pkgs.pdns-recursor}/bin/pdns_recursor \
           --config-dir=${dataDir} \
-          --socket-dir=${dataDir} \
-          --disable-syslog
+          --socket-dir=${dataDir}
         '';
       };
 
@@ -165,4 +204,10 @@ in {
       '';
     };
   };
+
+  imports = [
+   (mkRemovedOptionModule [ "services" "pdns-recursor" "extraConfig" ]
+     "To change extra Recursor settings use services.pdns-recursor.settings instead.")
+  ];
+
 }
diff --git a/nixos/modules/services/networking/unifi.nix b/nixos/modules/services/networking/unifi.nix
index 6239c88b7e41..c922ba15960f 100644
--- a/nixos/modules/services/networking/unifi.nix
+++ b/nixos/modules/services/networking/unifi.nix
@@ -176,6 +176,7 @@ in
         Type = "simple";
         ExecStart = "${(removeSuffix "\n" cmd)} start";
         ExecStop = "${(removeSuffix "\n" cmd)} stop";
+        Restart = "on-failure";
         User = "unifi";
         UMask = "0077";
         WorkingDirectory = "${stateDir}";
diff --git a/nixos/modules/services/security/usbguard.nix b/nixos/modules/services/security/usbguard.nix
index 88d2f69db572..20d5e3b28eb9 100644
--- a/nixos/modules/services/security/usbguard.nix
+++ b/nixos/modules/services/security/usbguard.nix
@@ -39,6 +39,16 @@ in {
     services.usbguard = {
       enable = mkEnableOption "USBGuard daemon";
 
+      package = mkOption {
+        type = types.package;
+        default = pkgs.usbguard;
+        defaultText = "pkgs.usbguard";
+        description = ''
+          The usbguard package to use. If you do not need the Qt GUI, use
+          <literal>pkgs.usbguard-nox</literal> to save disk space.
+        '';
+      };
+
       ruleFile = mkOption {
         type = types.path;
         default = "/var/lib/usbguard/rules.conf";
@@ -179,7 +189,7 @@ in {
 
   config = mkIf cfg.enable {
 
-    environment.systemPackages = [ pkgs.usbguard ];
+    environment.systemPackages = [ cfg.package ];
 
     systemd.services.usbguard = {
       description = "USBGuard daemon";
@@ -195,7 +205,7 @@ in {
 
       serviceConfig = {
         Type = "simple";
-        ExecStart = ''${pkgs.usbguard}/bin/usbguard-daemon -P -k -c ${daemonConfFile}'';
+        ExecStart = ''${cfg.package}/bin/usbguard-daemon -P -k -c ${daemonConfFile}'';
         Restart = "on-failure";
       };
     };
diff --git a/nixos/modules/services/torrent/transmission.nix b/nixos/modules/services/torrent/transmission.nix
index f544928fb6b1..a94a471361ef 100644
--- a/nixos/modules/services/torrent/transmission.nix
+++ b/nixos/modules/services/torrent/transmission.nix
@@ -84,6 +84,18 @@ in
           The directory where transmission will create files.
         '';
       };
+
+      user = mkOption {
+        type = types.str;
+        default = "transmission";
+        description = "User account under which Transmission runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "transmission";
+        description = "Group account under which Transmission runs.";
+      };
     };
   };
 
@@ -99,7 +111,8 @@ in
       serviceConfig.ExecStartPre = preStart;
       serviceConfig.ExecStart = "${pkgs.transmission}/bin/transmission-daemon -f --port ${toString config.services.transmission.port}";
       serviceConfig.ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
-      serviceConfig.User = "transmission";
+      serviceConfig.User = cfg.user;
+      serviceConfig.Group = cfg.group;
       # NOTE: transmission has an internal umask that also must be set (in settings.json)
       serviceConfig.UMask = "0002";
     };
@@ -107,14 +120,19 @@ in
     # It's useful to have transmission in path, e.g. for remote control
     environment.systemPackages = [ pkgs.transmission ];
 
-    users.groups.transmission.gid = config.ids.gids.transmission;
-    users.users.transmission = {
-      group = "transmission";
-      uid = config.ids.uids.transmission;
-      description = "Transmission BitTorrent user";
-      home = homeDir;
-      createHome = true;
-    };
+    users.users = optionalAttrs (cfg.user == "transmission") (singleton
+      { name = "transmission";
+        group = cfg.group;
+        uid = config.ids.uids.transmission;
+        description = "Transmission BitTorrent user";
+        home = homeDir;
+        createHome = true;
+      });
+
+    users.groups = optionalAttrs (cfg.group == "transmission") (singleton
+      { name = "transmission";
+        gid = config.ids.gids.transmission;
+      });
 
     # AppArmor profile
     security.apparmor.profiles = mkIf apparmor [
diff --git a/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix b/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix
index 6740131dccde..95c8fb160510 100644
--- a/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix
+++ b/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix
@@ -1,7 +1,7 @@
 { config, lib, pkgs, ... }: with lib; let
   cfg = config.services.icingaweb2;
+  fpm = config.services.phpfpm.pools.${poolName};
   poolName = "icingaweb2";
-  phpfpmSocketName = "/var/run/phpfpm/${poolName}.sock";
 
   defaultConfig = {
     global = {
@@ -164,27 +164,26 @@ in {
   };
 
   config = mkIf cfg.enable {
-    services.phpfpm.poolConfigs = mkIf (cfg.pool == "${poolName}") {
-      "${poolName}" = ''
-        listen = "${phpfpmSocketName}"
-        listen.owner = nginx
-        listen.group = nginx
-        listen.mode = 0600
-        user = icingaweb2
-        pm = dynamic
-        pm.max_children = 75
-        pm.start_servers = 2
-        pm.min_spare_servers = 2
-        pm.max_spare_servers = 10
-      '';
+    services.phpfpm.pools = mkIf (cfg.pool == "${poolName}") {
+      "${poolName}" = {
+        user = "icingaweb2";
+        phpOptions = ''
+          extension = ${pkgs.phpPackages.imagick}/lib/php/extensions/imagick.so
+          date.timezone = "${cfg.timezone}"
+        '';
+        settings = mapAttrs (name: mkDefault) {
+          "listen.owner" = "nginx";
+          "listen.group" = "nginx";
+          "listen.mode" = "0600";
+          "pm" = "dynamic";
+          "pm.max_children" = 75;
+          "pm.start_servers" = 2;
+          "pm.min_spare_servers" = 2;
+          "pm.max_spare_servers" = 10;
+        };
+      };
     };
 
-    services.phpfpm.phpOptions = mkIf (cfg.pool == "${poolName}")
-      ''
-        extension = ${pkgs.phpPackages.imagick}/lib/php/extensions/imagick.so
-        date.timezone = "${cfg.timezone}"
-      '';
-
     systemd.services."phpfpm-${poolName}".serviceConfig.ReadWritePaths = [ "/etc/icingaweb2" ];
 
     services.nginx = {
@@ -208,7 +207,7 @@ in {
             include ${config.services.nginx.package}/conf/fastcgi.conf;
             try_files $uri =404;
             fastcgi_split_path_info ^(.+\.php)(/.+)$;
-            fastcgi_pass unix:${phpfpmSocketName};
+            fastcgi_pass unix:${fpm.socket};
             fastcgi_param SCRIPT_FILENAME ${pkgs.icingaweb2}/public/index.php;
           '';
         };
diff --git a/nixos/modules/services/web-apps/limesurvey.nix b/nixos/modules/services/web-apps/limesurvey.nix
index 84a94fc446e5..2797feb32ebf 100644
--- a/nixos/modules/services/web-apps/limesurvey.nix
+++ b/nixos/modules/services/web-apps/limesurvey.nix
@@ -120,15 +120,15 @@ in
     };
 
     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
-      '';
+      type = with types; attrsOf (oneOf [ str int bool ]);
+      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 LimeSurvey PHP pool. See the documentation on <literal>php-fpm.conf</literal>
         for details on configuration directives.
@@ -203,17 +203,12 @@ in
     };
 
     services.phpfpm.pools.limesurvey = {
-      listen = "/run/phpfpm/limesurvey.sock";
-      extraConfig = ''
-        listen.owner = ${config.services.httpd.user};
-        listen.group = ${config.services.httpd.group};
-        user = ${user};
-        group = ${group};
-
-        env[LIMESURVEY_CONFIG] = ${limesurveyConfig}
-
-        ${cfg.poolConfig}
-      '';
+      inherit user group;
+      phpEnv.LIMESURVEY_CONFIG = "${limesurveyConfig}";
+      settings = {
+        "listen.owner" = config.services.httpd.user;
+        "listen.group" = config.services.httpd.group;
+      } // cfg.poolConfig;
     };
 
     services.httpd = {
@@ -241,7 +236,7 @@ in
             <Directory "${pkg}/share/limesurvey">
               <FilesMatch "\.php$">
                 <If "-f %{REQUEST_FILENAME}">
-                  SetHandler "proxy:unix:${fpm.listen}|fcgi://localhost/"
+                  SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
                 </If>
               </FilesMatch>
 
diff --git a/nixos/modules/services/web-apps/matomo.nix b/nixos/modules/services/web-apps/matomo.nix
index 14aca45a342b..d9f840408cc8 100644
--- a/nixos/modules/services/web-apps/matomo.nix
+++ b/nixos/modules/services/web-apps/matomo.nix
@@ -176,7 +176,7 @@ in {
             # Use User-Private Group scheme to protect Matomo data, but allow administration / backup via 'matomo' group
             # Copy config folder
             chmod g+s "${dataDir}"
-            cp -r "${cfg.package}/config" "${dataDir}/"
+            cp -r "${cfg.package}/share/config" "${dataDir}/"
             chmod -R u+rwX,g+rwX,o-rwx "${dataDir}"
 
             # check whether user setup has already been done
@@ -225,22 +225,24 @@ in {
       serviceConfig.UMask = "0007";
     };
 
-    services.phpfpm.poolConfigs = let
+    services.phpfpm.pools = let
       # workaround for when both are null and need to generate a string,
       # which is illegal, but as assertions apparently are being triggered *after* config generation,
       # we have to avoid already throwing errors at this previous stage.
       socketOwner = if (cfg.nginx != null) then config.services.nginx.user
       else if (cfg.webServerUser != null) then cfg.webServerUser else "";
     in {
-      ${pool} = ''
-        listen = "${phpSocket}"
-        listen.owner = ${socketOwner}
-        listen.group = root
-        listen.mode = 0600
-        user = ${user}
-        env[PIWIK_USER_PATH] = ${dataDir}
-        ${cfg.phpfpmProcessManagerConfig}
-      '';
+      ${pool} = {
+        listen = phpSocket;
+        extraConfig = ''
+          listen.owner = ${socketOwner}
+          listen.group = root
+          listen.mode = 0600
+          user = ${user}
+          env[PIWIK_USER_PATH] = ${dataDir}
+          ${cfg.phpfpmProcessManagerConfig}
+        '';
+      };
     };
 
 
diff --git a/nixos/modules/services/web-apps/mediawiki.nix b/nixos/modules/services/web-apps/mediawiki.nix
index 5bd5977e592b..ec2568bf952d 100644
--- a/nixos/modules/services/web-apps/mediawiki.nix
+++ b/nixos/modules/services/web-apps/mediawiki.nix
@@ -312,17 +312,17 @@ in
       };
 
       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
-        '';
+        type = with types; attrsOf (oneOf [ str int bool ]);
+        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>
+          Options for the MediaWiki PHP pool. See the documentation on <literal>php-fpm.conf</literal>
           for details on configuration directives.
         '';
       };
@@ -379,17 +379,12 @@ in
     };
 
     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}
-      '';
+      inherit user group;
+      phpEnv.MEDIAWIKI_CONFIG = "${mediawikiConfig}";
+      settings = {
+        "listen.owner" = config.services.httpd.user;
+        "listen.group" = config.services.httpd.group;
+      } // cfg.poolConfig;
     };
 
     services.httpd = {
@@ -403,7 +398,7 @@ in
             <Directory "${pkg}/share/mediawiki">
               <FilesMatch "\.php$">
                 <If "-f %{REQUEST_FILENAME}">
-                  SetHandler "proxy:unix:${fpm.listen}|fcgi://localhost/"
+                  SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
                 </If>
               </FilesMatch>
 
diff --git a/nixos/modules/services/web-apps/moodle.nix b/nixos/modules/services/web-apps/moodle.nix
new file mode 100644
index 000000000000..f2516c67c6b3
--- /dev/null
+++ b/nixos/modules/services/web-apps/moodle.nix
@@ -0,0 +1,300 @@
+{ config, lib, pkgs, ... }:
+
+let
+  inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption types;
+  inherit (lib) concatStringsSep literalExample mapAttrsToList optional optionalString;
+
+  cfg = config.services.moodle;
+  fpm = config.services.phpfpm.pools.moodle;
+
+  user = "moodle";
+  group = config.services.httpd.group;
+  stateDir = "/var/lib/moodle";
+
+  moodleConfig = pkgs.writeText "config.php" ''
+  <?php  // Moodle configuration file
+
+  unset($CFG);
+  global $CFG;
+  $CFG = new stdClass();
+
+  $CFG->dbtype    = '${ { "mysql" = "mariadb"; "pgsql" = "pgsql"; }.${cfg.database.type} }';
+  $CFG->dblibrary = 'native';
+  $CFG->dbhost    = '${cfg.database.host}';
+  $CFG->dbname    = '${cfg.database.name}';
+  $CFG->dbuser    = '${cfg.database.user}';
+  ${optionalString (cfg.database.passwordFile != null) "$CFG->dbpass = file_get_contents('${cfg.database.passwordFile}');"}
+  $CFG->prefix    = 'mdl_';
+  $CFG->dboptions = array (
+    'dbpersist' => 0,
+    'dbport' => '${toString cfg.database.port}',
+    ${optionalString (cfg.database.socket != null) "'dbsocket' => '${cfg.database.socket}',"}
+    'dbcollation' => 'utf8mb4_unicode_ci',
+  );
+
+  $CFG->wwwroot   = '${if cfg.virtualHost.enableSSL then "https" else "http"}://${cfg.virtualHost.hostName}';
+  $CFG->dataroot  = '${stateDir}';
+  $CFG->admin     = 'admin';
+
+  $CFG->directorypermissions = 02777;
+  $CFG->disableupdateautodeploy = true;
+
+  $CFG->pathtogs = '${pkgs.ghostscript}/bin/gs';
+  $CFG->pathtophp = '${pkgs.php}/bin/php';
+  $CFG->pathtodu = '${pkgs.coreutils}/bin/du';
+  $CFG->aspellpath = '${pkgs.aspell}/bin/aspell';
+  $CFG->pathtodot = '${pkgs.graphviz}/bin/dot';
+
+  require_once('${cfg.package}/share/moodle/lib/setup.php');
+
+  // There is no php closing tag in this file,
+  // it is intentional because it prevents trailing whitespace problems!
+  '';
+
+  mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql";
+  pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql";
+in
+{
+  # interface
+  options.services.moodle = {
+    enable = mkEnableOption "Moodle web application";
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.moodle;
+      defaultText = "pkgs.moodle";
+      description = "The Moodle package to use.";
+    };
+
+    initialPassword = mkOption {
+      type = types.str;
+      example = "correcthorsebatterystaple";
+      description = ''
+        Specifies the initial password for the admin, i.e. the password assigned if the user does not already exist.
+        The password specified here is world-readable in the Nix store, so it should be changed promptly.
+      '';
+    };
+
+    database = {
+      type = mkOption {
+        type = types.enum [ "mysql" "pgsql" ];
+        default = "mysql";
+        description = ''Database engine to use.'';
+      };
+
+      host = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = "Database host address.";
+      };
+
+      port = mkOption {
+        type = types.int;
+        description = "Database host port.";
+        default = {
+          "mysql" = 3306;
+          "pgsql" = 5432;
+        }.${cfg.database.type};
+        defaultText = "3306";
+      };
+
+      name = mkOption {
+        type = types.str;
+        default = "moodle";
+        description = "Database name.";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "moodle";
+        description = "Database user.";
+      };
+
+      passwordFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/run/keys/moodle-dbpassword";
+        description = ''
+          A file containing the password corresponding to
+          <option>database.user</option>.
+        '';
+      };
+
+      socket = mkOption {
+        type = types.nullOr types.path;
+        default =
+          if mysqlLocal then "/run/mysqld/mysqld.sock"
+          else if pgsqlLocal then "/run/postgresql"
+          else null;
+        defaultText = "/run/mysqld/mysqld.sock";
+        description = "Path to the unix socket file to use for authentication.";
+      };
+
+      createLocally = mkOption {
+        type = types.bool;
+        default = 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 = {
+        hostName = "moodle.example.org";
+        enableSSL = true;
+        adminAddr = "webmaster@example.org";
+        sslServerCert = "/var/lib/acme/moodle.example.org/full.pem";
+        sslServerKey = "/var/lib/acme/moodle.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 = with types; attrsOf (oneOf [ str int bool ]);
+      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 Moodle PHP pool. See the documentation on <literal>php-fpm.conf</literal>
+        for details on configuration directives.
+      '';
+    };
+  };
+
+  # implementation
+  config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion = cfg.database.createLocally -> cfg.database.user == user;
+        message = "services.moodle.database.user must be set to ${user} if services.moodle.database.createLocally is set true";
+      }
+      { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
+        message = "a password cannot be specified if services.moodle.database.createLocally is set to true";
+      }
+    ];
+
+    services.mysql = mkIf mysqlLocal {
+      enable = true;
+      package = mkDefault pkgs.mariadb;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.database.user;
+          ensurePermissions = {
+            "${cfg.database.name}.*" = "SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER";
+          };
+        }
+      ];
+    };
+
+    services.postgresql = mkIf pgsqlLocal {
+      enable = true;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.database.user;
+          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
+
+    services.phpfpm.pools.moodle = {
+      inherit user group;
+      phpEnv.MOODLE_CONFIG = "${moodleConfig}";
+      phpOptions = ''
+        zend_extension = opcache.so
+        opcache.enable = 1
+      '';
+      settings = {
+        "listen.owner" = config.services.httpd.user;
+        "listen.group" = config.services.httpd.group;
+      } // cfg.poolConfig;
+    };
+
+    services.httpd = {
+      enable = true;
+      adminAddr = mkDefault cfg.virtualHost.adminAddr;
+      extraModules = [ "proxy_fcgi" ];
+      virtualHosts = [ (mkMerge [
+        cfg.virtualHost {
+          documentRoot = mkForce "${cfg.package}/share/moodle";
+          extraConfig = ''
+            <Directory "${cfg.package}/share/moodle">
+              <FilesMatch "\.php$">
+                <If "-f %{REQUEST_FILENAME}">
+                  SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
+                </If>
+              </FilesMatch>
+              Options -Indexes
+              DirectoryIndex index.php
+            </Directory>
+          '';
+        }
+      ]) ];
+    };
+
+    systemd.tmpfiles.rules = [
+      "d '${stateDir}' 0750 ${user} ${group} - -"
+    ];
+
+    systemd.services.moodle-init = {
+      wantedBy = [ "multi-user.target" ];
+      before = [ "phpfpm-moodle.service" ];
+      after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
+      environment.MOODLE_CONFIG = moodleConfig;
+      script = ''
+        ${pkgs.php}/bin/php ${cfg.package}/share/moodle/admin/cli/check_database_schema.php && rc=$? || rc=$?
+
+        [ "$rc" == 1 ] && ${pkgs.php}/bin/php ${cfg.package}/share/moodle/admin/cli/upgrade.php \
+          --non-interactive \
+          --allow-unstable
+
+        [ "$rc" == 2 ] && ${pkgs.php}/bin/php ${cfg.package}/share/moodle/admin/cli/install_database.php \
+          --agree-license \
+          --adminpass=${cfg.initialPassword}
+
+        true
+      '';
+      serviceConfig = {
+        User = user;
+        Group = group;
+        Type = "oneshot";
+      };
+    };
+
+    systemd.services.moodle-cron = {
+      description = "Moodle cron service";
+      after = [ "moodle-init.service" ];
+      environment.MOODLE_CONFIG = moodleConfig;
+      serviceConfig = {
+        User = user;
+        Group = group;
+        ExecStart = "${pkgs.php}/bin/php ${cfg.package}/share/moodle/admin/cli/cron.php";
+      };
+    };
+
+    systemd.timers.moodle-cron = {
+      description = "Moodle cron timer";
+      wantedBy = [ "timers.target" ];
+      timerConfig = {
+        OnCalendar = "minutely";
+      };
+    };
+
+    systemd.services.httpd.after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
+
+    users.users."${user}".group = group;
+
+  };
+}
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
index a0214a75d93e..ada14ad39291 100644
--- a/nixos/modules/services/web-apps/nextcloud.nix
+++ b/nixos/modules/services/web-apps/nextcloud.nix
@@ -4,6 +4,7 @@ with lib;
 
 let
   cfg = config.services.nextcloud;
+  fpm = config.services.phpfpm.pools.nextcloud;
 
   phpPackage = pkgs.php73;
   phpPackages = pkgs.php73Packages;
@@ -410,25 +411,20 @@ in {
       };
 
       services.phpfpm = {
-        pools.nextcloud = let
-          phpAdminValues = (toKeyValue
-            (foldr (a: b: a // b) {}
-              (mapAttrsToList (k: v: { "php_admin_value[${k}]" = v; })
-                phpOptions)));
-        in {
-          phpOptions = phpOptionsExtensions;
+        pools.nextcloud = {
+          user = "nextcloud";
+          group = "nginx";
+          phpOptions = phpOptionsExtensions + phpOptionsStr;
           phpPackage = phpPackage;
-          listen = "/run/phpfpm/nextcloud";
-          extraConfig = ''
-            listen.owner = nginx
-            listen.group = nginx
-            user = nextcloud
-            group = nginx
-            ${cfg.poolConfig}
-            env[NEXTCLOUD_CONFIG_DIR] = ${cfg.home}/config
-            env[PATH] = /run/wrappers/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/usr/bin:/bin
-            ${phpAdminValues}
-          '';
+          phpEnv = {
+            NEXTCLOUD_CONFIG_DIR = "${cfg.home}/config";
+            PATH = "/run/wrappers/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/usr/bin:/bin";
+          };
+          settings = mapAttrs (name: mkDefault) {
+            "listen.owner" = "nginx";
+            "listen.group" = "nginx";
+          };
+          extraConfig = cfg.poolConfig;
         };
       };
 
@@ -489,7 +485,7 @@ in {
                   fastcgi_param HTTPS ${if cfg.https then "on" else "off"};
                   fastcgi_param modHeadersAvailable true;
                   fastcgi_param front_controller_active true;
-                  fastcgi_pass unix:/run/phpfpm/nextcloud;
+                  fastcgi_pass unix:${fpm.socket};
                   fastcgi_intercept_errors on;
                   fastcgi_request_buffering off;
                   fastcgi_read_timeout 120s;
diff --git a/nixos/modules/services/web-apps/restya-board.nix b/nixos/modules/services/web-apps/restya-board.nix
index b200a89260ac..6a1b4143bc16 100644
--- a/nixos/modules/services/web-apps/restya-board.nix
+++ b/nixos/modules/services/web-apps/restya-board.nix
@@ -9,11 +9,11 @@ with lib;
 
 let
   cfg = config.services.restya-board;
+  fpm = config.services.phpfpm.pools.${poolName};
 
   runDir = "/run/restya-board";
 
   poolName = "restya-board";
-  phpfpmSocketName = "/run/phpfpm/${poolName}.sock";
 
 in
 
@@ -180,7 +180,7 @@ in
 
     services.phpfpm.pools = {
       "${poolName}" = {
-        listen = phpfpmSocketName;
+        inherit (cfg) user group;
         phpOptions = ''
           date.timezone = "CET"
 
@@ -191,20 +191,18 @@ in
             auth_password = ${cfg.email.password}
           ''}
         '';
-        extraConfig = ''
-          listen.owner = nginx
-          listen.group = nginx
-          listen.mode = 0600
-          user = ${cfg.user}
-          group = ${cfg.group}
-          pm = dynamic
-          pm.max_children = 75
-          pm.start_servers = 10
-          pm.min_spare_servers = 5
-          pm.max_spare_servers = 20
-          pm.max_requests = 500
-          catch_workers_output = 1
-        '';
+        settings = mapAttrs (name: mkDefault) {
+          "listen.owner" = "nginx";
+          "listen.group" = "nginx";
+          "listen.mode" = "0600";
+          "pm" = "dynamic";
+          "pm.max_children" = 75;
+          "pm.start_servers" = 10;
+          "pm.min_spare_servers" = 5;
+          "pm.max_spare_servers" = 20;
+          "pm.max_requests" = 500;
+          "catch_workers_output" = 1;
+        };
       };
     };
 
@@ -241,7 +239,7 @@ in
         tryFiles = "$uri =404";
         extraConfig = ''
           include ${pkgs.nginx}/conf/fastcgi_params;
-          fastcgi_pass    unix:${phpfpmSocketName};
+          fastcgi_pass    unix:${fpm.socket};
           fastcgi_index   index.php;
           fastcgi_param   SCRIPT_FILENAME $document_root$fastcgi_script_name;
           fastcgi_param   PHP_VALUE "upload_max_filesize=9G \n post_max_size=9G \n max_execution_time=200 \n max_input_time=200 \n memory_limit=256M";
diff --git a/nixos/modules/services/web-apps/selfoss.nix b/nixos/modules/services/web-apps/selfoss.nix
index cd0f743a5fb8..56b7cafffe8b 100644
--- a/nixos/modules/services/web-apps/selfoss.nix
+++ b/nixos/modules/services/web-apps/selfoss.nix
@@ -4,7 +4,6 @@ let
   cfg = config.services.selfoss;
 
   poolName = "selfoss_pool";
-  phpfpmSocketName = "/run/phpfpm/${poolName}.sock";
 
   dataDir = "/var/lib/selfoss";
 
@@ -116,21 +115,22 @@ in
 
   config = mkIf cfg.enable {
 
-    services.phpfpm.poolConfigs = mkIf (cfg.pool == "${poolName}") {
-      "${poolName}" = ''
-        listen = "${phpfpmSocketName}";
-        listen.owner = nginx
-        listen.group = nginx
-        listen.mode = 0600
-        user = nginx
-        pm = dynamic
-        pm.max_children = 75
-        pm.start_servers = 10
-        pm.min_spare_servers = 5
-        pm.max_spare_servers = 20
-        pm.max_requests = 500
-        catch_workers_output = 1
-      '';
+    services.phpfpm.pools = mkIf (cfg.pool == "${poolName}") {
+      "${poolName}" = {
+        user = "nginx";
+        settings = mapAttrs (name: mkDefault) {
+          "listen.owner" = "nginx";
+          "listen.group" = "nginx";
+          "listen.mode" = "0600";
+          "pm" = "dynamic";
+          "pm.max_children" = 75;
+          "pm.start_servers" = 10;
+          "pm.min_spare_servers" = 5;
+          "pm.max_spare_servers" = 20;
+          "pm.max_requests" = 500;
+          "catch_workers_output" = 1;
+        };
+      };
     };
 
     systemd.services.selfoss-config = {
diff --git a/nixos/modules/services/web-apps/tt-rss.nix b/nixos/modules/services/web-apps/tt-rss.nix
index 1bd9de93735d..59b0ee1addc6 100644
--- a/nixos/modules/services/web-apps/tt-rss.nix
+++ b/nixos/modules/services/web-apps/tt-rss.nix
@@ -521,20 +521,19 @@ let
 
     services.phpfpm.pools = mkIf (cfg.pool == "${poolName}") {
       "${poolName}" = {
-        listen = "/var/run/phpfpm/${poolName}.sock";
-        extraConfig = ''
-          listen.owner = nginx
-          listen.group = nginx
-          listen.mode = 0600
-          user = ${cfg.user}
-          pm = dynamic
-          pm.max_children = 75
-          pm.start_servers = 10
-          pm.min_spare_servers = 5
-          pm.max_spare_servers = 20
-          pm.max_requests = 500
-          catch_workers_output = 1
-        '';
+        inherit (cfg) user;
+        settings = mapAttrs (name: mkDefault) {
+          "listen.owner" = "nginx";
+          "listen.group" = "nginx";
+          "listen.mode" = "0600";
+          "pm" = "dynamic";
+          "pm.max_children" = 75;
+          "pm.start_servers" = 10;
+          "pm.min_spare_servers" = 5;
+          "pm.max_spare_servers" = 20;
+          "pm.max_requests" = 500;
+          "catch_workers_output" = 1;
+        };
       };
     };
 
@@ -552,7 +551,7 @@ let
           locations."~ \.php$" = {
             extraConfig = ''
               fastcgi_split_path_info ^(.+\.php)(/.+)$;
-              fastcgi_pass unix:${config.services.phpfpm.pools.${cfg.pool}.listen};
+              fastcgi_pass unix:${config.services.phpfpm.pools.${cfg.pool}.socket};
               fastcgi_index index.php;
             '';
           };
diff --git a/nixos/modules/services/web-apps/wordpress.nix b/nixos/modules/services/web-apps/wordpress.nix
index 624b0089a037..98dc84588189 100644
--- a/nixos/modules/services/web-apps/wordpress.nix
+++ b/nixos/modules/services/web-apps/wordpress.nix
@@ -216,15 +216,15 @@ let
         };
 
         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
-          '';
+          type = with types; attrsOf (oneOf [ str int bool ]);
+          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.
@@ -280,15 +280,11 @@ in
 
     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}
-        '';
+        inherit user group;
+        settings = {
+          "listen.owner" = config.services.httpd.user;
+          "listen.group" = config.services.httpd.group;
+        } // cfg.poolConfig;
       }
     )) eachSite;
 
@@ -303,7 +299,7 @@ in
               <Directory "${pkg hostName cfg}/share/wordpress">
                 <FilesMatch "\.php$">
                   <If "-f %{REQUEST_FILENAME}">
-                    SetHandler "proxy:unix:/run/phpfpm/wordpress-${hostName}.sock|fcgi://localhost/"
+                    SetHandler "proxy:unix:${config.services.phpfpm.pools."wordpress-${hostName}".socket}|fcgi://localhost/"
                   </If>
                 </FilesMatch>
 
diff --git a/nixos/modules/services/web-apps/zabbix.nix b/nixos/modules/services/web-apps/zabbix.nix
index 4b5334579a99..fa358ffafbc3 100644
--- a/nixos/modules/services/web-apps/zabbix.nix
+++ b/nixos/modules/services/web-apps/zabbix.nix
@@ -133,15 +133,15 @@ in
       };
 
       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
-        '';
+        type = with types; attrsOf (oneOf [ str int bool ]);
+        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.
         '';
@@ -160,6 +160,8 @@ in
     ];
 
     services.phpfpm.pools.zabbix = {
+      inherit user;
+      group = config.services.httpd.group;
       phpOptions = ''
         # https://www.zabbix.com/documentation/current/manual/installation/install
         memory_limit = 128M
@@ -177,15 +179,11 @@ in
       '' + 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}
-      '';
+      phpEnv.ZABBIX_CONFIG = zabbixConfig;
+      settings = {
+        "listen.owner" = config.services.httpd.user;
+        "listen.group" = config.services.httpd.group;
+      } // cfg.poolConfig;
     };
 
     services.httpd = {
diff --git a/nixos/modules/services/web-servers/darkhttpd.nix b/nixos/modules/services/web-servers/darkhttpd.nix
new file mode 100644
index 000000000000..80870118c334
--- /dev/null
+++ b/nixos/modules/services/web-servers/darkhttpd.nix
@@ -0,0 +1,77 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.darkhttpd;
+
+  args = concatStringsSep " " ([
+    cfg.rootDir
+    "--port ${toString cfg.port}"
+    "--addr ${cfg.address}"
+  ] ++ cfg.extraArgs
+    ++ optional cfg.hideServerId             "--no-server-id"
+    ++ optional config.networking.enableIPv6 "--ipv6");
+
+in {
+  options.services.darkhttpd = with types; {
+    enable = mkEnableOption "DarkHTTPd web server";
+
+    port = mkOption {
+      default = 80;
+      type = ints.u16;
+      description = ''
+        Port to listen on.
+        Pass 0 to let the system choose any free port for you.
+      '';
+    };
+
+    address = mkOption {
+      default = "127.0.0.1";
+      type = str;
+      description = ''
+        Address to listen on.
+        Pass `all` to listen on all interfaces.
+      '';
+    };
+
+    rootDir = mkOption {
+      type = path;
+      description = ''
+        Path from which to serve files.
+      '';
+    };
+
+    hideServerId = mkOption {
+      type = bool;
+      default = true;
+      description = ''
+        Don't identify the server type in headers or directory listings.
+      '';
+    };
+
+    extraArgs = mkOption {
+      type = listOf str;
+      default = [];
+      description = ''
+        Additional configuration passed to the executable.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.darkhttpd = {
+      description = "Dark HTTPd";
+      wants = [ "network.target" ];
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        DynamicUser = true;
+        ExecStart = "${cfg.package}/bin/darkhttpd ${args}";
+        AmbientCapabilities = lib.mkIf (cfg.port < 1024) [ "CAP_NET_BIND_SERVICE" ];
+        Restart = "on-failure";
+        RestartSec = "2s";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/web-servers/phpfpm/default.nix b/nixos/modules/services/web-servers/phpfpm/default.nix
index ffafbc5e92f2..e95e71e0d997 100644
--- a/nixos/modules/services/web-servers/phpfpm/default.nix
+++ b/nixos/modules/services/web-servers/phpfpm/default.nix
@@ -4,41 +4,27 @@ with lib;
 
 let
   cfg = config.services.phpfpm;
-  enabled = cfg.poolConfigs != {} || cfg.pools != {};
 
-  stateDir = "/run/phpfpm";
+  runtimeDir = "/run/phpfpm";
 
-  poolConfigs =
-    (mapAttrs mapPoolConfig cfg.poolConfigs) //
-    (mapAttrs mapPool cfg.pools);
+  toStr = value:
+    if true == value then "yes"
+    else if false == value then "no"
+    else toString value;
 
-  mapPoolConfig = n: p: {
-    phpPackage = cfg.phpPackage;
-    phpOptions = cfg.phpOptions;
-    config = p;
-  };
-
-  mapPool = n: p: {
-    phpPackage = p.phpPackage;
-    phpOptions = p.phpOptions;
-    config = ''
-      listen = ${p.listen}
-      ${p.extraConfig}
-    '';
-  };
-
-  fpmCfgFile = pool: conf: pkgs.writeText "phpfpm-${pool}.conf" ''
+  fpmCfgFile = pool: poolOpts: pkgs.writeText "phpfpm-${pool}.conf" ''
     [global]
-    error_log = syslog
-    daemonize = no
-    ${cfg.extraConfig}
+    ${concatStringsSep "\n" (mapAttrsToList (n: v: "${n} = ${toStr v}") cfg.settings)}
+    ${optionalString (cfg.extraConfig != null) cfg.extraConfig}
 
     [${pool}]
-    ${conf}
+    ${concatStringsSep "\n" (mapAttrsToList (n: v: "${n} = ${toStr v}") poolOpts.settings)}
+    ${concatStringsSep "\n" (mapAttrsToList (n: v: "env[${n}] = ${toStr v}") poolOpts.phpEnv)}
+    ${optionalString (poolOpts.extraConfig != null) poolOpts.extraConfig}
   '';
 
-  phpIni = pool: pkgs.runCommand "php.ini" {
-    inherit (pool) phpPackage phpOptions;
+  phpIni = poolOpts: pkgs.runCommand "php.ini" {
+    inherit (poolOpts) phpPackage phpOptions;
     preferLocalBuild = true;
     nixDefaults = ''
       sendmail_path = "/run/wrappers/bin/sendmail -t -i"
@@ -48,13 +34,138 @@ let
     cat $phpPackage/etc/php.ini $nixDefaultsPath $phpOptionsPath > $out
   '';
 
+  poolOpts = { name, ... }:
+    let
+      poolOpts = cfg.pools."${name}";
+    in
+    {
+      options = {
+        socket = mkOption {
+          type = types.str;
+          readOnly = true;
+          description = ''
+            Path to the unix socket file on which to accept FastCGI requests.
+            <note><para>This option is read-only and managed by NixOS.</para></note>
+          '';
+        };
+
+        listen = mkOption {
+          type = types.str;
+          default = "";
+          example = "/path/to/unix/socket";
+          description = ''
+            The address on which to accept FastCGI requests.
+          '';
+        };
+
+        phpPackage = mkOption {
+          type = types.package;
+          default = cfg.phpPackage;
+          defaultText = "config.services.phpfpm.phpPackage";
+          description = ''
+            The PHP package to use for running this PHP-FPM pool.
+          '';
+        };
+
+        phpOptions = mkOption {
+          type = types.lines;
+          default = cfg.phpOptions;
+          defaultText = "config.services.phpfpm.phpOptions";
+          description = ''
+            "Options appended to the PHP configuration file <filename>php.ini</filename> used for this PHP-FPM pool."
+          '';
+        };
+
+        phpEnv = lib.mkOption {
+          type = with types; attrsOf str;
+          default = {};
+          description = ''
+            Environment variables used for this PHP-FPM pool.
+          '';
+          example = literalExample ''
+            {
+              HOSTNAME = "$HOSTNAME";
+              TMP = "/tmp";
+              TMPDIR = "/tmp";
+              TEMP = "/tmp";
+            }
+          '';
+        };
+
+        user = mkOption {
+          type = types.str;
+          description = "User account under which this pool runs.";
+        };
+
+        group = mkOption {
+          type = types.str;
+          description = "Group account under which this pool runs.";
+        };
+
+        settings = mkOption {
+          type = with types; attrsOf (oneOf [ str int bool ]);
+          default = {};
+          description = ''
+            PHP-FPM pool directives. Refer to the "List of pool directives" section of
+            <link xlink:href="https://www.php.net/manual/en/install.fpm.configuration.php"/>
+            for details. Note that settings names must be enclosed in quotes (e.g.
+            <literal>"pm.max_children"</literal> instead of <literal>pm.max_children</literal>).
+          '';
+          example = literalExample ''
+            {
+              "pm" = "dynamic";
+              "pm.max_children" = 75;
+              "pm.start_servers" = 10;
+              "pm.min_spare_servers" = 5;
+              "pm.max_spare_servers" = 20;
+              "pm.max_requests" = 500;
+            }
+          '';
+        };
+
+        extraConfig = mkOption {
+          type = with types; nullOr lines;
+          default = null;
+          description = ''
+            Extra lines that go into the pool configuration.
+            See the documentation on <literal>php-fpm.conf</literal> for
+            details on configuration directives.
+          '';
+        };
+      };
+
+      config = {
+        socket = if poolOpts.listen == "" then "${runtimeDir}/${name}.sock" else poolOpts.listen;
+        group = mkDefault poolOpts.user;
+
+        settings = mapAttrs (name: mkDefault){
+          listen = poolOpts.socket;
+          user = poolOpts.user;
+          group = poolOpts.group;
+        };
+      };
+    };
+
 in {
 
   options = {
     services.phpfpm = {
+      settings = mkOption {
+        type = with types; attrsOf (oneOf [ str int bool ]);
+        default = {};
+        description = ''
+          PHP-FPM global directives. Refer to the "List of global php-fpm.conf directives" section of
+          <link xlink:href="https://www.php.net/manual/en/install.fpm.configuration.php"/>
+          for details. Note that settings names must be enclosed in quotes (e.g.
+          <literal>"pm.max_children"</literal> instead of <literal>pm.max_children</literal>).
+          You need not specify the options <literal>error_log</literal> or
+          <literal>daemonize</literal> here, since they are generated by NixOS.
+        '';
+      };
+
       extraConfig = mkOption {
-        type = types.lines;
-        default = "";
+        type = with types; nullOr lines;
+        default = null;
         description = ''
           Extra configuration that should be put in the global section of
           the PHP-FPM configuration file. Do not specify the options
@@ -80,64 +191,56 @@ in {
           ''
             date.timezone = "CET"
           '';
-        description =
-          "Options appended to the PHP configuration file <filename>php.ini</filename>.";
-      };
-
-      poolConfigs = mkOption {
-        default = {};
-        type = types.attrsOf types.lines;
-        example = literalExample ''
-          { mypool = '''
-              listen = /run/phpfpm/mypool
-              user = nobody
-              pm = dynamic
-              pm.max_children = 75
-              pm.start_servers = 10
-              pm.min_spare_servers = 5
-              pm.max_spare_servers = 20
-              pm.max_requests = 500
-            ''';
-          }
-        '';
         description = ''
-          A mapping between PHP-FPM pool names and their configurations.
-          See the documentation on <literal>php-fpm.conf</literal> for
-          details on configuration directives. If no pools are defined,
-          the phpfpm service is disabled.
+          Options appended to the PHP configuration file <filename>php.ini</filename>.
         '';
       };
 
       pools = mkOption {
-        type = types.attrsOf (types.submodule (import ./pool-options.nix {
-          inherit lib config;
-        }));
+        type = types.attrsOf (types.submodule poolOpts);
         default = {};
         example = literalExample ''
          {
            mypool = {
-             listen = "/path/to/unix/socket";
+             user = "php";
+             group = "php";
              phpPackage = pkgs.php;
-             extraConfig = '''
-               user = nobody
-               pm = dynamic
-               pm.max_children = 75
-               pm.start_servers = 10
-               pm.min_spare_servers = 5
-               pm.max_spare_servers = 20
-               pm.max_requests = 500
+             settings = '''
+               "pm" = "dynamic";
+               "pm.max_children" = 75;
+               "pm.start_servers" = 10;
+               "pm.min_spare_servers" = 5;
+               "pm.max_spare_servers" = 20;
+               "pm.max_requests" = 500;
              ''';
            }
          }'';
         description = ''
-          PHP-FPM pools. If no pools or poolConfigs are defined, the PHP-FPM
+          PHP-FPM pools. If no pools are defined, the PHP-FPM
           service is disabled.
         '';
       };
     };
   };
 
-  config = mkIf enabled {
+  config = mkIf (cfg.pools != {}) {
+
+    warnings =
+      mapAttrsToList (pool: poolOpts: ''
+        Using config.services.phpfpm.pools.${pool}.listen is deprecated and will become unsupported in a future release. Please reference the read-only option config.services.phpfpm.pools.${pool}.socket to access the path of your socket.
+      '') (filterAttrs (pool: poolOpts: poolOpts.listen != "") cfg.pools) ++
+      mapAttrsToList (pool: poolOpts: ''
+        Using config.services.phpfpm.pools.${pool}.extraConfig is deprecated and will become unsupported in a future release. Please migrate your configuration to config.services.phpfpm.pools.${pool}.settings.
+      '') (filterAttrs (pool: poolOpts: poolOpts.extraConfig != null) cfg.pools) ++
+      optional (cfg.extraConfig != null) ''
+        Using config.services.phpfpm.extraConfig is deprecated and will become unsupported in a future release. Please migrate your configuration to config.services.phpfpm.settings.
+      ''
+    ;
+
+    services.phpfpm.settings = {
+      error_log = "syslog";
+      daemonize = false;
+    };
 
     systemd.slices.phpfpm = {
       description = "PHP FastCGI Process manager pools slice";
@@ -148,18 +251,15 @@ in {
       wantedBy = [ "multi-user.target" ];
     };
 
-    systemd.services = flip mapAttrs' poolConfigs (pool: poolConfig:
+    systemd.services = mapAttrs' (pool: poolOpts:
       nameValuePair "phpfpm-${pool}" {
         description = "PHP FastCGI Process Manager service for pool ${pool}";
         after = [ "network.target" ];
         wantedBy = [ "phpfpm.target" ];
         partOf = [ "phpfpm.target" ];
-        preStart = ''
-          mkdir -p ${stateDir}
-        '';
         serviceConfig = let
-          cfgFile = fpmCfgFile pool poolConfig.config;
-          iniFile = phpIni poolConfig;
+          cfgFile = fpmCfgFile pool poolOpts;
+          iniFile = phpIni poolOpts;
         in {
           Slice = "phpfpm.slice";
           PrivateDevices = true;
@@ -168,10 +268,12 @@ in {
           # XXX: We need AF_NETLINK to make the sendmail SUID binary from postfix work
           RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
           Type = "notify";
-          ExecStart = "${poolConfig.phpPackage}/bin/php-fpm -y ${cfgFile} -c ${iniFile}";
+          ExecStart = "${poolOpts.phpPackage}/bin/php-fpm -y ${cfgFile} -c ${iniFile}";
           ExecReload = "${pkgs.coreutils}/bin/kill -USR2 $MAINPID";
+          RuntimeDirectory = "phpfpm";
+          RuntimeDirectoryPreserve = true; # Relevant when multiple processes are running
         };
       }
-   );
+    ) cfg.pools;
   };
 }
diff --git a/nixos/modules/services/web-servers/phpfpm/pool-options.nix b/nixos/modules/services/web-servers/phpfpm/pool-options.nix
deleted file mode 100644
index d9ad7eff71f2..000000000000
--- a/nixos/modules/services/web-servers/phpfpm/pool-options.nix
+++ /dev/null
@@ -1,57 +0,0 @@
-{ lib, config }:
-
-let
-  fpmCfg = config.services.phpfpm;
-in
-
-with lib; {
-
-  options = {
-
-    listen = mkOption {
-      type = types.str;
-      example = "/path/to/unix/socket";
-      description = ''
-        The address on which to accept FastCGI requests.
-      '';
-    };
-
-    phpPackage = mkOption {
-      type = types.package;
-      default = fpmCfg.phpPackage;
-      defaultText = "config.services.phpfpm.phpPackage";
-      description = ''
-        The PHP package to use for running this PHP-FPM pool.
-      '';
-    };
-
-    phpOptions = mkOption {
-      type = types.lines;
-      default = fpmCfg.phpOptions;
-      defaultText = "config.services.phpfpm.phpOptions";
-      description = ''
-        "Options appended to the PHP configuration file <filename>php.ini</filename> used for this PHP-FPM pool."
-      '';
-    };
-
-    extraConfig = mkOption {
-      type = types.lines;
-      example = ''
-        user = nobody
-        pm = dynamic
-        pm.max_children = 75
-        pm.start_servers = 10
-        pm.min_spare_servers = 5
-        pm.max_spare_servers = 20
-        pm.max_requests = 500
-      '';
-
-      description = ''
-        Extra lines that go into the pool configuration.
-        See the documentation on <literal>php-fpm.conf</literal> for
-        details on configuration directives.
-      '';
-    };
-  };
-}
-
diff --git a/nixos/modules/services/x11/desktop-managers/gnome3.nix b/nixos/modules/services/x11/desktop-managers/gnome3.nix
index 6434731d951f..0caa93ad217f 100644
--- a/nixos/modules/services/x11/desktop-managers/gnome3.nix
+++ b/nixos/modules/services/x11/desktop-managers/gnome3.nix
@@ -3,7 +3,9 @@
 with lib;
 
 let
+
   cfg = config.services.xserver.desktopManager.gnome3;
+  serviceCfg = config.services.gnome3;
 
   # Prioritize nautilus by default when opening directories
   mimeAppsList = pkgs.writeTextFile {
@@ -29,10 +31,10 @@ let
      chmod -R a+w $out/share/gsettings-schemas/nixos-gsettings-overrides
      cat - > $out/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas/nixos-defaults.gschema.override <<- EOF
        [org.gnome.desktop.background]
-       picture-uri='${pkgs.nixos-artwork.wallpapers.simple-dark-gray}/share/artwork/gnome/nix-wallpaper-simple-dark-gray.png'
+       picture-uri='file://${pkgs.nixos-artwork.wallpapers.simple-dark-gray}/share/artwork/gnome/nix-wallpaper-simple-dark-gray.png'
 
        [org.gnome.desktop.screensaver]
-       picture-uri='${pkgs.nixos-artwork.wallpapers.simple-dark-gray-bottom}/share/artwork/gnome/nix-wallpaper-simple-dark-gray_bottom.png'
+       picture-uri='file://${pkgs.nixos-artwork.wallpapers.simple-dark-gray-bottom}/share/artwork/gnome/nix-wallpaper-simple-dark-gray_bottom.png'
 
        [org.gnome.shell]
        favorite-apps=[ 'org.gnome.Epiphany.desktop', 'evolution.desktop', 'org.gnome.Music.desktop', 'org.gnome.Photos.desktop', 'org.gnome.Nautilus.desktop', 'org.gnome.Software.desktop' ]
@@ -45,10 +47,19 @@ let
 
   flashbackEnabled = cfg.flashback.enableMetacity || length cfg.flashback.customSessions > 0;
 
-in {
+in
+
+{
 
   options = {
 
+    services.gnome3 = {
+      core-os-services.enable = mkEnableOption "essential services for GNOME3";
+      core-shell.enable = mkEnableOption "GNOME Shell services";
+      core-utilities.enable = mkEnableOption "GNOME core utilities";
+      games.enable = mkEnableOption "GNOME games";
+    };
+
     services.xserver.desktopManager.gnome3 = {
       enable = mkOption {
         default = false;
@@ -121,138 +132,194 @@ in {
 
   };
 
-  config = mkIf cfg.enable {
-
-    # Enable helpful DBus services.
-    security.polkit.enable = true;
-    services.udisks2.enable = true;
-    services.accounts-daemon.enable = true;
-    services.dleyna-renderer.enable = mkDefault true;
-    services.dleyna-server.enable = mkDefault true;
-    services.gnome3.at-spi2-core.enable = true;
-    services.gnome3.evolution-data-server.enable = true;
-    services.gnome3.glib-networking.enable = true;
-    services.gnome3.gnome-keyring.enable = true;
-    services.gnome3.gnome-online-accounts.enable = mkDefault true;
-    services.gnome3.gnome-remote-desktop.enable = mkDefault true;
-    services.gnome3.gnome-settings-daemon.enable = true;
-    services.gnome3.gnome-user-share.enable = mkDefault true;
-    services.gvfs.enable = true;
-    services.gnome3.rygel.enable = mkDefault true;
-    services.gnome3.seahorse.enable = mkDefault true;
-    services.gnome3.sushi.enable = mkDefault true;
-    services.gnome3.tracker.enable = mkDefault true;
-    services.gnome3.tracker-miners.enable = mkDefault true;
-    hardware.pulseaudio.enable = mkDefault true;
-    services.telepathy.enable = mkDefault true;
-    networking.networkmanager.enable = mkDefault true;
-    services.upower.enable = config.powerManagement.enable;
-    services.dbus.packages =
-      optional config.services.printing.enable pkgs.system-config-printer ++
-      optional flashbackEnabled pkgs.gnome3.gnome-screensaver;
-    services.colord.enable = mkDefault true;
-    services.packagekit.enable = mkDefault true;
-    hardware.bluetooth.enable = mkDefault true;
-    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 ];
-    xdg.portal.enable = true;
-    xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-gtk ];
-
-    # Enable default programs
-    programs.dconf.enable = true;
-    programs.evince.enable = mkDefault true;
-    programs.file-roller.enable = mkDefault true;
-    programs.gnome-disks.enable = mkDefault true;
-    programs.gnome-documents.enable = mkDefault true;
-    programs.gnome-terminal.enable = mkDefault true;
-
-    # If gnome3 is installed, build vim for gtk3 too.
-    nixpkgs.config.vim.gui = "gtk3";
-
-    fonts.fonts = [
-      pkgs.dejavu_fonts pkgs.cantarell-fonts
-      pkgs.source-sans-pro
-      pkgs.source-code-pro # Default monospace font in 3.32
-    ];
-
-    services.xserver.displayManager.extraSessionFilePackages = [ pkgs.gnome3.gnome-session ]
-      ++ map
-        (wm: pkgs.gnome3.gnome-flashback.mkSessionForWm {
-          inherit (wm) wmName wmLabel wmCommand;
-        }) (optional cfg.flashback.enableMetacity {
-              wmName = "metacity";
-              wmLabel = "Metacity";
-              wmCommand = "${pkgs.gnome3.metacity}/bin/metacity";
-            } ++ cfg.flashback.customSessions);
-
-    environment.extraInit = ''
-      ${concatMapStrings (p: ''
-        if [ -d "${p}/share/gsettings-schemas/${p.name}" ]; then
-          export XDG_DATA_DIRS=$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}${p}/share/gsettings-schemas/${p.name}
-        fi
-
-        if [ -d "${p}/lib/girepository-1.0" ]; then
-          export GI_TYPELIB_PATH=$GI_TYPELIB_PATH''${GI_TYPELIB_PATH:+:}${p}/lib/girepository-1.0
-          export LD_LIBRARY_PATH=$LD_LIBRARY_PATH''${LD_LIBRARY_PATH:+:}${p}/lib
-        fi
-      '') cfg.sessionPath}
-    '';
+  config = mkMerge [
+    (mkIf (cfg.enable || flashbackEnabled) {
+      services.gnome3.core-os-services.enable = true;
+      services.gnome3.core-shell.enable = true;
+      services.gnome3.core-utilities.enable = mkDefault true;
 
+      services.xserver.displayManager.extraSessionFilePackages = [ pkgs.gnome3.gnome-session ];
 
-    services.geoclue2.enable = mkDefault true;
-    # GNOME should have its own geoclue agent
-    services.geoclue2.enableDemoAgent = false;
+      environment.extraInit = ''
+        ${concatMapStrings (p: ''
+          if [ -d "${p}/share/gsettings-schemas/${p.name}" ]; then
+            export XDG_DATA_DIRS=$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}${p}/share/gsettings-schemas/${p.name}
+          fi
 
-    services.geoclue2.appConfig."gnome-datetime-panel" = {
-      isAllowed = true;
-      isSystem = true;
-    };
-    services.geoclue2.appConfig."gnome-color-panel" = {
-      isAllowed = true;
-      isSystem = true;
-    };
-    services.geoclue2.appConfig."org.gnome.Shell" = {
-      isAllowed = true;
-      isSystem = true;
-    };
+          if [ -d "${p}/lib/girepository-1.0" ]; then
+            export GI_TYPELIB_PATH=$GI_TYPELIB_PATH''${GI_TYPELIB_PATH:+:}${p}/lib/girepository-1.0
+            export LD_LIBRARY_PATH=$LD_LIBRARY_PATH''${LD_LIBRARY_PATH:+:}${p}/lib
+          fi
+        '') cfg.sessionPath}
+      '';
 
-    environment.variables.GNOME_SESSION_DEBUG = optionalString cfg.debug "1";
+      environment.systemPackages = cfg.sessionPath;
 
-    # Override default mimeapps
-    environment.variables.XDG_DATA_DIRS = [ "${mimeAppsList}/share" ];
+      environment.variables.GNOME_SESSION_DEBUG = mkIf cfg.debug "1";
 
-    # Override GSettings schemas
-    environment.variables.NIX_GSETTINGS_OVERRIDES_DIR = "${nixos-gsettings-desktop-schemas}/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas";
+      # Override GSettings schemas
+      environment.variables.NIX_GSETTINGS_OVERRIDES_DIR = "${nixos-gsettings-desktop-schemas}/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas";
 
-    # Let nautilus find extensions
-    # TODO: Create nautilus-with-extensions package
-    environment.variables.NAUTILUS_EXTENSION_DIR = "${config.system.path}/lib/nautilus/extensions-3.0";
+       # If gnome3 is installed, build vim for gtk3 too.
+      nixpkgs.config.vim.gui = "gtk3";
+    })
 
-    services.xserver.updateDbusEnvironment = true;
+    (mkIf flashbackEnabled {
+      services.xserver.displayManager.extraSessionFilePackages =  map
+        (wm: pkgs.gnome3.gnome-flashback.mkSessionForWm {
+          inherit (wm) wmName wmLabel wmCommand;
+        }) (optional cfg.flashback.enableMetacity {
+              wmName = "metacity";
+              wmLabel = "Metacity";
+              wmCommand = "${pkgs.gnome3.metacity}/bin/metacity";
+            } ++ cfg.flashback.customSessions);
 
-    environment.systemPackages = pkgs.gnome3.corePackages ++ cfg.sessionPath
-      ++ (pkgs.gnome3.removePackagesByName pkgs.gnome3.optionalPackages config.environment.gnome3.excludePackages) ++ [
-      pkgs.xdg-user-dirs # Update user dirs as described in http://freedesktop.org/wiki/Software/xdg-user-dirs/
-    ];
+      security.pam.services.gnome-screensaver = {
+        enableGnomeKeyring = true;
+      };
 
-    # Use the correct gnome3 packageSet
-    networking.networkmanager.basePackages =
-      { inherit (pkgs) networkmanager modemmanager wpa_supplicant crda;
+      services.dbus.packages = [
+        pkgs.gnome3.gnome-screensaver
+      ];
+    })
+
+    (mkIf serviceCfg.core-os-services.enable {
+      hardware.bluetooth.enable = mkDefault true;
+      hardware.pulseaudio.enable = mkDefault true;
+      programs.dconf.enable = true;
+      security.polkit.enable = true;
+      services.accounts-daemon.enable = true;
+      services.dleyna-renderer.enable = mkDefault true;
+      services.dleyna-server.enable = mkDefault true;
+      services.gnome3.at-spi2-core.enable = true;
+      services.gnome3.evolution-data-server.enable = true;
+      services.gnome3.gnome-keyring.enable = true;
+      services.gnome3.gnome-online-accounts.enable = mkDefault true;
+      services.gnome3.gnome-online-miners.enable = true;
+      services.gnome3.tracker-miners.enable = mkDefault true;
+      services.gnome3.tracker.enable = mkDefault true;
+      services.hardware.bolt.enable = mkDefault true;
+      services.packagekit.enable = mkDefault true;
+      services.udisks2.enable = true;
+      services.upower.enable = config.powerManagement.enable;
+      services.xserver.libinput.enable = mkDefault true; # for controlling touchpad settings via gnome control center
+
+      xdg.portal.enable = true;
+      xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-gtk ];
+
+      networking.networkmanager.enable = mkDefault true;
+
+      # Use the correct gnome3 packageSet
+      networking.networkmanager.basePackages = {
+        inherit (pkgs) networkmanager modemmanager wpa_supplicant crda;
         inherit (pkgs.gnome3) networkmanager-openvpn networkmanager-vpnc
-                              networkmanager-openconnect networkmanager-fortisslvpn
-                              networkmanager-iodine networkmanager-l2tp; };
-
-    # Needed for themes and backgrounds
-    environment.pathsToLink = [
-      "/share"
-      "/share/nautilus-python/extensions"
-    ];
+        networkmanager-openconnect networkmanager-fortisslvpn
+        networkmanager-iodine networkmanager-l2tp;
+      };
 
-    security.pam.services.gnome-screensaver = mkIf flashbackEnabled {
-      enableGnomeKeyring = true;
-    };
-  };
+      services.xserver.updateDbusEnvironment = true;
+
+      # Needed for themes and backgrounds
+      environment.pathsToLink = [
+        "/share" # TODO: https://github.com/NixOS/nixpkgs/issues/47173
+      ];
+    })
+
+    (mkIf serviceCfg.core-shell.enable {
+      services.colord.enable = mkDefault true;
+      services.gnome3.glib-networking.enable = true;
+      services.gnome3.gnome-remote-desktop.enable = mkDefault true;
+      services.gnome3.gnome-settings-daemon.enable = true;
+      services.gnome3.gnome-user-share.enable = mkDefault true;
+      services.gnome3.rygel.enable = mkDefault true;
+      services.gvfs.enable = true;
+      services.telepathy.enable = mkDefault true;
+      systemd.packages = [ pkgs.gnome3.vino ];
+      services.dbus.packages =
+        optional config.services.printing.enable pkgs.system-config-printer;
+
+      services.geoclue2.enable = mkDefault true;
+      services.geoclue2.enableDemoAgent = false; # GNOME has its own geoclue agent
+
+      services.geoclue2.appConfig."gnome-datetime-panel" = {
+        isAllowed = true;
+        isSystem = true;
+      };
+      services.geoclue2.appConfig."gnome-color-panel" = {
+        isAllowed = true;
+        isSystem = true;
+      };
+      services.geoclue2.appConfig."org.gnome.Shell" = {
+        isAllowed = true;
+        isSystem = true;
+      };
 
+      fonts.fonts = with pkgs; [
+        cantarell-fonts
+        dejavu_fonts
+        source-code-pro # Default monospace font in 3.32
+        source-sans-pro
+      ];
+
+      environment.systemPackages = with pkgs.gnome3; [
+        adwaita-icon-theme
+        gnome-backgrounds
+        gnome-bluetooth
+        gnome-control-center
+        gnome-getting-started-docs
+        gnome-shell
+        gnome-shell-extensions
+        gnome-themes-extra
+        gnome-user-docs
+        pkgs.glib # for gsettings
+        pkgs.gnome-menus
+        pkgs.gtk3.out # for gtk-launch
+        pkgs.hicolor-icon-theme
+        pkgs.shared-mime-info # for update-mime-database
+        pkgs.xdg-user-dirs # Update user dirs as described in http://freedesktop.org/wiki/Software/xdg-user-dirs/
+        vino
+      ];
+    })
+
+    (mkIf serviceCfg.core-utilities.enable {
+      environment.systemPackages = (with pkgs.gnome3; removePackagesByName [
+        baobab eog epiphany evince gucharmap nautilus totem yelp gnome-calculator
+        gnome-contacts gnome-font-viewer gnome-screenshot gnome-system-monitor simple-scan
+        gnome-terminal evolution file-roller gedit gnome-clocks gnome-music gnome-tweaks
+        pkgs.gnome-photos nautilus-sendto dconf-editor vinagre gnome-weather gnome-logs
+        gnome-maps gnome-characters gnome-calendar accerciser gnome-nettool gnome-packagekit
+        gnome-software gnome-power-manager gnome-todo pkgs.gnome-usage
+      ] config.environment.gnome3.excludePackages);
+
+      # Enable default programs
+      programs.evince.enable = mkDefault true;
+      programs.file-roller.enable = mkDefault true;
+      programs.gnome-disks.enable = mkDefault true;
+      programs.gnome-documents.enable = mkDefault true;
+      programs.gnome-terminal.enable = mkDefault true;
+      services.gnome3.seahorse.enable = mkDefault true;
+      services.gnome3.sushi.enable = mkDefault true;
+
+      # Let nautilus find extensions
+      # TODO: Create nautilus-with-extensions package
+      environment.variables.NAUTILUS_EXTENSION_DIR = "${config.system.path}/lib/nautilus/extensions-3.0";
+
+      # Override default mimeapps for nautilus
+      environment.variables.XDG_DATA_DIRS = [ "${mimeAppsList}/share" ];
+
+      environment.pathsToLink = [
+        "/share/nautilus-python/extensions"
+      ];
+    })
+
+    (mkIf serviceCfg.games.enable {
+      environment.systemPackages = (with pkgs.gnome3; removePackagesByName [
+        aisleriot atomix five-or-more four-in-a-row gnome-chess gnome-klotski
+        gnome-mahjongg gnome-mines gnome-nibbles gnome-robots gnome-sudoku
+        gnome-taquin gnome-tetravex hitori iagno lightsoff quadrapassel
+        swell-foop tali
+      ] config.environment.gnome3.excludePackages);
+    })
+  ];
 
 }
diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix
index 23a2dd45d492..2a0360b12cbc 100644
--- a/nixos/modules/system/boot/systemd.nix
+++ b/nixos/modules/system/boot/systemd.nix
@@ -537,7 +537,7 @@ in
     };
 
     systemd.enableCgroupAccounting = mkOption {
-      default = false;
+      default = true;
       type = types.bool;
       description = ''
         Whether to enable cgroup accounting.
@@ -804,10 +804,10 @@ in
         [Manager]
         ${optionalString config.systemd.enableCgroupAccounting ''
           DefaultCPUAccounting=yes
+          DefaultBlockIOAccounting=yes
           DefaultIOAccounting=yes
           DefaultBlockIOAccounting=yes
-          DefaultMemoryAccounting=yes
-          DefaultTasksAccounting=yes
+          DefaultIPAccounting=yes
         ''}
         DefaultLimitCORE=infinity
         ${config.systemd.extraConfig}
diff --git a/nixos/modules/virtualisation/libvirtd.nix b/nixos/modules/virtualisation/libvirtd.nix
index 394b4ce56563..16b79d869193 100644
--- a/nixos/modules/virtualisation/libvirtd.nix
+++ b/nixos/modules/virtualisation/libvirtd.nix
@@ -104,6 +104,18 @@ in {
       '';
     };
 
+    onBoot = mkOption {
+      type = types.enum ["start" "ignore" ];
+      default = "start";
+      description = ''
+        Specifies the action to be done to / on the guests when the host boots.
+        The "start" option starts all guests that were running prior to shutdown
+        regardless of their autostart settings. The "ignore" option will not
+        start the formally running guest on boot. However, any guest marked as
+        autostart will still be automatically started by libvirtd.
+      '';
+    };
+
     onShutdown = mkOption {
       type = types.enum ["shutdown" "suspend" ];
       default = "suspend";
@@ -221,6 +233,7 @@ in {
       path = with pkgs; [ coreutils libvirt gawk ];
       restartIfChanged = false;
 
+      environment.ON_BOOT = "${cfg.onBoot}";
       environment.ON_SHUTDOWN = "${cfg.onShutdown}";
     };
 
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 47eaec7c7838..557ee78df7c6 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -97,6 +97,7 @@ in
   glusterfs = handleTest ./glusterfs.nix {};
   gnome3-xorg = handleTest ./gnome3-xorg.nix {};
   gnome3 = handleTest ./gnome3.nix {};
+  gnome-photos = handleTest ./gnome-photos.nix {};
   gocd-agent = handleTest ./gocd-agent.nix {};
   gocd-server = handleTest ./gocd-server.nix {};
   google-oslogin = handleTest ./google-oslogin {};
@@ -127,6 +128,7 @@ in
   jackett = handleTest ./jackett.nix {};
   jellyfin = handleTest ./jellyfin.nix {};
   jenkins = handleTest ./jenkins.nix {};
+  jormungandr = handleTest ./jormungandr.nix {};
   kafka = handleTest ./kafka.nix {};
   kerberos = handleTest ./kerberos/default.nix {};
   kernel-latest = handleTest ./kernel-latest.nix {};
@@ -141,6 +143,7 @@ in
   latestKernel.login = handleTest ./login.nix { latestKernel = true; };
   ldap = handleTest ./ldap.nix {};
   leaps = handleTest ./leaps.nix {};
+  libxmlb = handleTest ./libxmlb.nix {};
   lidarr = handleTest ./lidarr.nix {};
   lightdm = handleTest ./lightdm.nix {};
   limesurvey = handleTest ./limesurvey.nix {};
@@ -158,6 +161,7 @@ in
   minio = handleTest ./minio.nix {};
   misc = handleTest ./misc.nix {};
   mongodb = handleTest ./mongodb.nix {};
+  moodle = handleTest ./moodle.nix {};
   morty = handleTest ./morty.nix {};
   mosquitto = handleTest ./mosquitto.nix {};
   mpd = handleTest ./mpd.nix {};
@@ -262,6 +266,7 @@ in
   tinydns = handleTest ./tinydns.nix {};
   tor = handleTest ./tor.nix {};
   transmission = handleTest ./transmission.nix {};
+  trezord = handleTest ./trezord.nix {};
   udisks2 = handleTest ./udisks2.nix {};
   upnp = handleTest ./upnp.nix {};
   uwsgi = handleTest ./uwsgi.nix {};
diff --git a/nixos/tests/gnome-photos.nix b/nixos/tests/gnome-photos.nix
new file mode 100644
index 000000000000..2ecda1d68ce3
--- /dev/null
+++ b/nixos/tests/gnome-photos.nix
@@ -0,0 +1,42 @@
+# run installed tests
+import ./make-test.nix ({ pkgs, lib, ... }:
+
+let
+
+  # gsettings tool with access to gsettings-desktop-schemas
+  desktop-gsettings = with pkgs; stdenv.mkDerivation {
+    name = "desktop-gsettings";
+    dontUnpack = true;
+    nativeBuildInputs = [ glib wrapGAppsHook ];
+    buildInputs = [ gsettings-desktop-schemas ];
+    installPhase = ''
+      runHook preInstall
+      mkdir -p $out/bin
+      ln -s ${glib.bin}/bin/gsettings $out/bin/desktop-gsettings
+      runHook postInstall
+    '';
+  };
+
+in
+
+{
+  name = "gnome-photos";
+  meta = {
+    maintainers = pkgs.gnome-photos.meta.maintainers;
+  };
+
+  machine = { pkgs, ... }: {
+    imports = [ ./common/x11.nix ];
+    programs.dconf.enable = true;
+    services.gnome3.at-spi2-core.enable = true; # needed for dogtail
+    environment.systemPackages = with pkgs; [ gnome-desktop-testing desktop-gsettings ];
+    services.dbus.packages = with pkgs; [ gnome-photos ];
+  };
+
+  testScript = ''
+    $machine->waitForX;
+    # dogtail needs accessibility enabled
+    $machine->succeed("desktop-gsettings set org.gnome.desktop.interface toolkit-accessibility true 2>&1");
+    $machine->succeed("gnome-desktop-testing-runner -d '${pkgs.gnome-photos.installedTests}/share' 2>&1");
+  '';
+})
diff --git a/nixos/tests/jormungandr.nix b/nixos/tests/jormungandr.nix
new file mode 100644
index 000000000000..2abafc53ce51
--- /dev/null
+++ b/nixos/tests/jormungandr.nix
@@ -0,0 +1,77 @@
+import ./make-test.nix ({ pkgs, ... }: {
+  name = "jormungandr";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ mmahut ];
+  };
+
+  nodes = {
+    # Testing the Byzantine Fault Tolerant protocol
+    bft = { ... }: {
+      environment.systemPackages = [ pkgs.jormungandr ];
+      services.jormungandr.enable = true;
+      services.jormungandr.genesisBlockFile = "/var/lib/jormungandr/block-0.bin";
+      services.jormungandr.secretFile = "/etc/secrets/jormungandr.yaml";
+    };
+
+    # Testing the Ouroboros Genesis Praos protocol
+    genesis = { ... }: {
+      environment.systemPackages = [ pkgs.jormungandr ];
+      services.jormungandr.enable = true;
+      services.jormungandr.genesisBlockFile = "/var/lib/jormungandr/block-0.bin";
+      services.jormungandr.secretFile = "/etc/secrets/jormungandr.yaml";
+    };
+  };
+
+  testScript = ''
+    startAll;
+
+    ## Testing BFT
+    # Let's wait for the StateDirectory
+    $bft->waitForFile("/var/lib/jormungandr/");
+
+    # First, we generate the genesis file for our new blockchain
+    $bft->succeed("jcli genesis init > /root/genesis.yaml");
+
+    # We need to generate our secret key
+    $bft->succeed("jcli key generate --type=Ed25519 > /root/key.prv");
+
+    # We include the secret key into our services.jormungandr.secretFile
+    $bft->succeed("mkdir -p /etc/secrets");
+    $bft->succeed("echo -e \"bft:\\n signing_key:\" \$(cat /root/key.prv) > /etc/secrets/jormungandr.yaml");
+
+    # After that, we generate our public key from it
+    $bft->succeed("cat /root/key.prv | jcli key to-public > /root/key.pub");
+
+    # We add our public key as a consensus leader in the genesis configration file
+    $bft->succeed("sed -ie \"s/ed25519_pk1vvwp2s0n5jl5f4xcjurp2e92sj2awehkrydrlas4vgqr7xzt33jsadha32/\$(cat /root/key.pub)/\" /root/genesis.yaml");
+
+    # Now we can generate the genesis block from it
+    $bft->succeed("jcli genesis encode --input /root/genesis.yaml --output /var/lib/jormungandr/block-0.bin");
+
+    # We should have everything to start the service now
+    $bft->succeed("systemctl restart jormungandr");
+    $bft->waitForUnit("jormungandr.service");
+
+    # Now we can test if we are able to reach the REST API
+    $bft->waitUntilSucceeds("curl -L http://localhost:8607/api/v0/node/stats | grep uptime");
+
+    ## Testing Genesis
+    # Let's wait for the StateDirectory
+    $genesis->waitForFile("/var/lib/jormungandr/");
+
+    # Bootstraping the configuration
+    $genesis->succeed("jormungandr-bootstrap -g -p 8607 -s 1");
+
+    # Moving generated files in place
+    $genesis->succeed("mkdir -p /etc/secrets");
+    $genesis->succeed("mv pool-secret1.yaml /etc/secrets/jormungandr.yaml");
+    $genesis->succeed("mv block-0.bin /var/lib/jormungandr/");
+
+    # We should have everything to start the service now
+    $genesis->succeed("systemctl restart jormungandr");
+    $genesis->waitForUnit("jormungandr.service");
+
+    # Now we can create and delegate an account
+    $genesis->succeed("./create-account-and-delegate.sh | tee -a /tmp/delegate.log");
+  '';
+})
diff --git a/nixos/tests/libxmlb.nix b/nixos/tests/libxmlb.nix
new file mode 100644
index 000000000000..3bee568ac5a2
--- /dev/null
+++ b/nixos/tests/libxmlb.nix
@@ -0,0 +1,17 @@
+# run installed tests
+import ./make-test.nix ({ pkgs, ... }:
+
+{
+  name = "libxmlb";
+  meta = {
+    maintainers = pkgs.libxmlb.meta.maintainers;
+  };
+
+  machine = { pkgs, ... }: {
+    environment.systemPackages = with pkgs; [ gnome-desktop-testing ];
+  };
+
+  testScript = ''
+    $machine->succeed("gnome-desktop-testing-runner -d '${pkgs.libxmlb.installedTests}/share'");
+  '';
+})
diff --git a/nixos/tests/moodle.nix b/nixos/tests/moodle.nix
new file mode 100644
index 000000000000..565a6b636949
--- /dev/null
+++ b/nixos/tests/moodle.nix
@@ -0,0 +1,22 @@
+import ./make-test.nix ({ pkgs, lib, ... }: {
+  name = "moodle";
+  meta.maintainers = [ lib.maintainers.aanderse ];
+
+  machine =
+    { ... }:
+    { services.moodle.enable = true;
+      services.moodle.virtualHost.hostName = "localhost";
+      services.moodle.virtualHost.adminAddr = "root@example.com";
+      services.moodle.initialPassword = "correcthorsebatterystaple";
+
+      # Ensure the virtual machine has enough memory to avoid errors like:
+      # Fatal error: Out of memory (allocated 152047616) (tried to allocate 33554440 bytes)
+      virtualisation.memorySize = 2000;
+    };
+
+  testScript = ''
+    startAll;
+    $machine->waitForUnit('phpfpm-moodle.service');
+    $machine->succeed('curl http://localhost/') =~ /You are not logged in/ or die;
+  '';
+})
diff --git a/nixos/tests/systemd.nix b/nixos/tests/systemd.nix
index 3168c026d514..1c201e3b5dcc 100644
--- a/nixos/tests/systemd.nix
+++ b/nixos/tests/systemd.nix
@@ -89,5 +89,12 @@ import ./make-test.nix ({ pkgs, ... }: {
      $machine->waitForUnit('multi-user.target');
      $machine->succeed('sysctl net.core.default_qdisc | grep -q "fq_codel"');
    };
+
+   # Test cgroup accounting is enabled
+   subtest "systemd cgroup accounting is enabled", sub {
+     $machine->waitForUnit('multi-user.target');
+     $machine->succeed('systemctl show testservice1.service -p IOAccounting | grep -q "yes"');
+     $machine->succeed('systemctl status testservice1.service | grep -q "CPU:"');
+   };
   '';
 })
diff --git a/nixos/tests/trezord.nix b/nixos/tests/trezord.nix
new file mode 100644
index 000000000000..1c85bf539345
--- /dev/null
+++ b/nixos/tests/trezord.nix
@@ -0,0 +1,20 @@
+import ./make-test.nix ({ pkgs, ... }: {
+  name = "trezord";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ mmahut ];
+  };
+
+  nodes = {
+    machine = { ... }: {
+      services.trezord.enable = true;
+      services.trezord.emulator.enable = true;
+    };
+  };
+
+  testScript = ''
+    startAll;
+    $machine->waitForUnit("trezord.service");
+    $machine->waitForOpenPort(21325);
+    $machine->waitUntilSucceeds("curl -L http://localhost:21325/status/ | grep Version");
+  '';
+})
diff --git a/nixos/tests/xmpp/ejabberd.nix b/nixos/tests/xmpp/ejabberd.nix
index 7eb566b54685..196a04aca300 100644
--- a/nixos/tests/xmpp/ejabberd.nix
+++ b/nixos/tests/xmpp/ejabberd.nix
@@ -45,8 +45,7 @@ import ../make-test.nix ({ pkgs, ... }: {
               ip: "127.0.0.1"
               module: ejabberd_service
               access: local
-              shaper_rule: fast
-              ip: "127.0.0.1"
+              shaper: fast
 
           ## Disabling digest-md5 SASL authentication. digest-md5 requires plain-text
           ## password storage (see auth_password_format option).
@@ -181,7 +180,6 @@ import ../make-test.nix ({ pkgs, ... }: {
             mod_client_state: {}
             mod_configure: {} # requires mod_adhoc
             ## mod_delegation: {} # for xep0356
-            mod_echo: {}
             #mod_irc:
             #  host: "irc.@HOST@"
             #  default_encoding: "utf-8"