summary refs log tree commit diff
path: root/nixos/modules/services
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/services')
-rw-r--r--nixos/modules/services/audio/snapserver.nix28
-rw-r--r--nixos/modules/services/cluster/hadoop/default.nix4
-rw-r--r--nixos/modules/services/cluster/kubernetes/kubelet.nix12
-rw-r--r--nixos/modules/services/continuous-integration/buildbot/master.nix4
-rw-r--r--nixos/modules/services/continuous-integration/hercules-ci-agent/common.nix105
-rw-r--r--nixos/modules/services/continuous-integration/hercules-ci-agent/default.nix8
-rw-r--r--nixos/modules/services/development/hoogle.nix1
-rw-r--r--nixos/modules/services/hardware/auto-cpufreq.nix18
-rw-r--r--nixos/modules/services/hardware/power-profiles-daemon.nix53
-rw-r--r--nixos/modules/services/hardware/thinkfan.nix260
-rw-r--r--nixos/modules/services/mail/dovecot.nix2
-rw-r--r--nixos/modules/services/misc/etebase-server.nix205
-rw-r--r--nixos/modules/services/misc/gitlab.nix2
-rw-r--r--nixos/modules/services/misc/pykms.nix13
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters.nix4
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/flow.nix50
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/nginx.nix7
-rw-r--r--nixos/modules/services/networking/dnscrypt-proxy2.nix8
-rw-r--r--nixos/modules/services/networking/epmd.nix2
-rw-r--r--nixos/modules/services/networking/flashpolicyd.nix86
-rw-r--r--nixos/modules/services/networking/jitsi-videobridge.nix12
-rw-r--r--nixos/modules/services/networking/kresd.nix2
-rw-r--r--nixos/modules/services/networking/resilio.nix1
-rw-r--r--nixos/modules/services/networking/sabnzbd.nix3
-rw-r--r--nixos/modules/services/security/oauth2_proxy.nix1
-rw-r--r--nixos/modules/services/security/oauth2_proxy_nginx.nix2
-rw-r--r--nixos/modules/services/web-apps/galene.nix178
-rw-r--r--nixos/modules/services/web-apps/hedgedoc.nix4
-rw-r--r--nixos/modules/services/web-apps/mastodon.nix542
-rw-r--r--nixos/modules/services/web-apps/nextcloud.nix31
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/default.nix12
-rw-r--r--nixos/modules/services/web-servers/nginx/default.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/herbstluftwm.nix13
-rw-r--r--nixos/modules/services/x11/window-managers/xmonad.nix1
-rw-r--r--nixos/modules/services/x11/xserver.nix2
35 files changed, 1393 insertions, 285 deletions
diff --git a/nixos/modules/services/audio/snapserver.nix b/nixos/modules/services/audio/snapserver.nix
index f614f0ba3e1..a261b876078 100644
--- a/nixos/modules/services/audio/snapserver.nix
+++ b/nixos/modules/services/audio/snapserver.nix
@@ -48,8 +48,8 @@ let
     ++ [ "--stream.port ${toString cfg.port}" ]
     ++ optionalNull cfg.sampleFormat "--stream.sampleformat ${cfg.sampleFormat}"
     ++ optionalNull cfg.codec "--stream.codec ${cfg.codec}"
-    ++ optionalNull cfg.streamBuffer "--stream.stream_buffer ${cfg.streamBuffer}"
-    ++ optionalNull cfg.buffer "--stream.buffer ${cfg.buffer}"
+    ++ optionalNull cfg.streamBuffer "--stream.stream_buffer ${toString cfg.streamBuffer}"
+    ++ optionalNull cfg.buffer "--stream.buffer ${toString cfg.buffer}"
     ++ optional cfg.sendToMuted "--stream.send_to_muted"
     # tcp json rpc
     ++ [ "--tcp.enabled ${toString cfg.tcp.enable}" ]
@@ -198,13 +198,14 @@ in {
         type = with types; attrsOf (submodule {
           options = {
             location = mkOption {
-              type = types.path;
+              type = types.oneOf [ types.path types.str ];
               description = ''
-                The location of the pipe.
+                The location of the pipe, file, Librespot/Airplay/process binary, or a TCP address.
+                Use an empty string for alsa.
               '';
             };
             type = mkOption {
-              type = types.enum [ "pipe" "file" "process" "spotify" "airplay" ];
+              type = types.enum [ "pipe" "librespot" "airplay" "file" "process" "tcp" "alsa" "spotify" ];
               default = "pipe";
               description = ''
                 The type of input stream.
@@ -219,13 +220,21 @@ in {
               example = literalExample ''
                 # for type == "pipe":
                 {
-                  mode = "listen";
+                  mode = "create";
                 };
                 # for type == "process":
                 {
                   params = "--param1 --param2";
                   logStderr = "true";
                 };
+                # for type == "tcp":
+                {
+                  mode = "client";
+                }
+                # for type == "alsa":
+                {
+                  device = "hw:0,0";
+                }
               '';
             };
             inherit sampleFormat;
@@ -255,6 +264,11 @@ in {
 
   config = mkIf cfg.enable {
 
+    # https://github.com/badaix/snapcast/blob/98ac8b2fb7305084376607b59173ce4097c620d8/server/streamreader/stream_manager.cpp#L85
+    warnings = filter (w: w != "") (mapAttrsToList (k: v: if v.type == "spotify" then ''
+      services.snapserver.streams.${k}.type = "spotify" is deprecated, use services.snapserver.streams.${k}.type = "librespot" instead.
+    '' else "") cfg.streams);
+
     systemd.services.snapserver = {
       after = [ "network.target" ];
       description = "Snapserver";
@@ -272,7 +286,7 @@ in {
         ProtectKernelTunables = true;
         ProtectControlGroups = true;
         ProtectKernelModules = true;
-        RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX";
+        RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX AF_NETLINK";
         RestrictNamespaces = true;
         RuntimeDirectory = name;
         StateDirectory = name;
diff --git a/nixos/modules/services/cluster/hadoop/default.nix b/nixos/modules/services/cluster/hadoop/default.nix
index 171d4aced65..41ac46e538e 100644
--- a/nixos/modules/services/cluster/hadoop/default.nix
+++ b/nixos/modules/services/cluster/hadoop/default.nix
@@ -7,6 +7,7 @@ with lib;
   options.services.hadoop = {
     coreSite = mkOption {
       default = {};
+      type = types.attrsOf types.anything;
       example = literalExample ''
         {
           "fs.defaultFS" = "hdfs://localhost";
@@ -17,6 +18,7 @@ with lib;
 
     hdfsSite = mkOption {
       default = {};
+      type = types.attrsOf types.anything;
       example = literalExample ''
         {
           "dfs.nameservices" = "namenode1";
@@ -27,6 +29,7 @@ with lib;
 
     mapredSite = mkOption {
       default = {};
+      type = types.attrsOf types.anything;
       example = literalExample ''
         {
           "mapreduce.map.cpu.vcores" = "1";
@@ -37,6 +40,7 @@ with lib;
 
     yarnSite = mkOption {
       default = {};
+      type = types.attrsOf types.anything;
       example = literalExample ''
         {
           "yarn.resourcemanager.ha.id" = "resourcemanager1";
diff --git a/nixos/modules/services/cluster/kubernetes/kubelet.nix b/nixos/modules/services/cluster/kubernetes/kubelet.nix
index 2b6e45ba1b9..479027f1b27 100644
--- a/nixos/modules/services/cluster/kubernetes/kubelet.nix
+++ b/nixos/modules/services/cluster/kubernetes/kubelet.nix
@@ -241,7 +241,17 @@ in
         description = "Kubernetes Kubelet Service";
         wantedBy = [ "kubernetes.target" ];
         after = [ "network.target" "docker.service" "kube-apiserver.service" ];
-        path = with pkgs; [ gitMinimal openssh docker util-linux iproute ethtool thin-provisioning-tools iptables socat ] ++ top.path;
+        path = with pkgs; [
+          gitMinimal
+          openssh
+          docker
+          util-linux
+          iproute
+          ethtool
+          thin-provisioning-tools
+          iptables
+          socat
+        ] ++ lib.optional config.boot.zfs.enabled config.boot.zfs.package ++ top.path;
         preStart = ''
           ${concatMapStrings (img: ''
             echo "Seeding docker image: ${img}"
diff --git a/nixos/modules/services/continuous-integration/buildbot/master.nix b/nixos/modules/services/continuous-integration/buildbot/master.nix
index d30d94c53cc..a49f5f8100d 100644
--- a/nixos/modules/services/continuous-integration/buildbot/master.nix
+++ b/nixos/modules/services/continuous-integration/buildbot/master.nix
@@ -223,7 +223,7 @@ in {
       };
 
       pythonPackages = mkOption {
-        type = types.listOf types.package;
+        type = types.functionTo (types.listOf types.package);
         default = pythonPackages: with pythonPackages; [ ];
         defaultText = "pythonPackages: with pythonPackages; [ ]";
         description = "Packages to add the to the PYTHONPATH of the buildbot process.";
@@ -283,5 +283,5 @@ in {
     '')
   ];
 
-  meta.maintainers = with lib.maintainers; [ nand0p mic92 ];
+  meta.maintainers = with lib.maintainers; [ nand0p mic92 lopsided98 ];
 }
diff --git a/nixos/modules/services/continuous-integration/hercules-ci-agent/common.nix b/nixos/modules/services/continuous-integration/hercules-ci-agent/common.nix
index 4aed493c0fb..9f9b86ee61c 100644
--- a/nixos/modules/services/continuous-integration/hercules-ci-agent/common.nix
+++ b/nixos/modules/services/continuous-integration/hercules-ci-agent/common.nix
@@ -7,14 +7,21 @@ Platform-specific code is in the respective default.nix files.
  */
 
 { config, lib, options, pkgs, ... }:
-
 let
-  inherit (lib) mkOption mkIf types filterAttrs literalExample mkRenamedOptionModule;
+  inherit (lib)
+    filterAttrs
+    literalExample
+    mkIf
+    mkOption
+    mkRemovedOptionModule
+    mkRenamedOptionModule
+    types
+    ;
 
   cfg =
     config.services.hercules-ci-agent;
 
-  format = pkgs.formats.toml {};
+  format = pkgs.formats.toml { };
 
   settingsModule = { config, ... }: {
     freeformType = format.type;
@@ -28,10 +35,14 @@ let
       };
       concurrentTasks = mkOption {
         description = ''
-          Number of tasks to perform simultaneously, such as evaluations, derivations.
+          Number of tasks to perform simultaneously.
+
+          A task is a single derivation build or an evaluation.
+          At minimum, you need 2 concurrent tasks for <literal>x86_64-linux</literal>
+          in your cluster, to allow for import from derivation.
 
-          You must have a total capacity across agents of at least 2 concurrent tasks on <literal>x86_64-linux</literal>
-          to allow for import from derivation.
+          <literal>concurrentTasks</literal> can be around the CPU core count or lower if memory is
+          the bottleneck.
         '';
         type = types.int;
         default = 4;
@@ -77,61 +88,39 @@ let
     };
   };
 
+  # TODO (roberth, >=2022) remove
   checkNix =
     if !cfg.checkNix
     then ""
-    else if lib.versionAtLeast config.nix.package.version "2.4.0"
+    else if lib.versionAtLeast config.nix.package.version "2.3.10"
     then ""
-    else pkgs.stdenv.mkDerivation {
-      name = "hercules-ci-check-system-nix-src";
-      inherit (config.nix.package) src patches;
-      configurePhase = ":";
-      buildPhase = ''
-        echo "Checking in-memory pathInfoCache expiry"
-        if ! grep 'struct PathInfoCacheValue' src/libstore/store-api.hh >/dev/null; then
-          cat 1>&2 <<EOF
-
-          You are deploying Hercules CI Agent on a system with an incompatible
-          nix-daemon. Please
-           - either upgrade Nix to version 2.4.0 (when released),
-           - or set option services.hercules-ci-agent.patchNix = true;
-           - or set option nix.package to a build of Nix 2.3 with this patch applied:
-               https://github.com/NixOS/nix/pull/3405
-
-          The patch is required for Nix-daemon clients that expect a change in binary
-          cache contents while running, like the agent's evaluator. Without it, import
-          from derivation will fail if your cluster has more than one machine.
-          We are conservative with changes to the overall system, which is why we
-          keep changes to a minimum and why we ask for confirmation in the form of
-          services.hercules-ci-agent.patchNix = true before applying.
-
-        EOF
-          exit 1
-        fi
-      '';
-      installPhase = "touch $out";
-    };
+    else
+      pkgs.stdenv.mkDerivation {
+        name = "hercules-ci-check-system-nix-src";
+        inherit (config.nix.package) src patches;
+        configurePhase = ":";
+        buildPhase = ''
+          echo "Checking in-memory pathInfoCache expiry"
+          if ! grep 'PathInfoCacheValue' src/libstore/store-api.hh >/dev/null; then
+            cat 1>&2 <<EOF
+
+            You are deploying Hercules CI Agent on a system with an incompatible
+            nix-daemon. Please make sure nix.package is set to a Nix version of at
+            least 2.3.10 or a master version more recent than Mar 12, 2020.
+          EOF
+            exit 1
+          fi
+        '';
+        installPhase = "touch $out";
+      };
 
-  patchedNix = lib.mkIf (!lib.versionAtLeast pkgs.nix.version "2.4.0") (
-    if lib.versionAtLeast pkgs.nix.version "2.4pre"
-    then lib.warn "Hercules CI Agent module will not patch 2.4 pre-release. Make sure it includes (equivalently) PR #3043, commit d048577909 or is no older than 2020-03-13." pkgs.nix
-    else pkgs.nix.overrideAttrs (
-      o: {
-        patches = (o.patches or []) ++ [ backportNix3398 ];
-      }
-    )
-  );
-
-  backportNix3398 = pkgs.fetchurl {
-    url = "https://raw.githubusercontent.com/hercules-ci/hercules-ci-agent/hercules-ci-agent-0.7.3/for-upstream/issue-3398-path-info-cache-ttls-backport-2.3.patch";
-    sha256 = "0jfckqjir9il2il7904yc1qyadw366y7xqzg81sp9sl3f1pw70ib";
-  };
 in
 {
   imports = [
-    (mkRenamedOptionModule ["services" "hercules-ci-agent" "extraOptions"] ["services" "hercules-ci-agent" "settings"])
-    (mkRenamedOptionModule ["services" "hercules-ci-agent" "baseDirectory"] ["services" "hercules-ci-agent" "settings" "baseDirectory"])
-    (mkRenamedOptionModule ["services" "hercules-ci-agent" "concurrentTasks"] ["services" "hercules-ci-agent" "settings" "concurrentTasks"])
+    (mkRenamedOptionModule [ "services" "hercules-ci-agent" "extraOptions" ] [ "services" "hercules-ci-agent" "settings" ])
+    (mkRenamedOptionModule [ "services" "hercules-ci-agent" "baseDirectory" ] [ "services" "hercules-ci-agent" "settings" "baseDirectory" ])
+    (mkRenamedOptionModule [ "services" "hercules-ci-agent" "concurrentTasks" ] [ "services" "hercules-ci-agent" "settings" "concurrentTasks" ])
+    (mkRemovedOptionModule [ "services" "hercules-ci-agent" "patchNix" ] "Nix versions packaged in this version of Nixpkgs don't need a patched nix-daemon to work correctly in Hercules CI Agent clusters.")
   ];
 
   options.services.hercules-ci-agent = {
@@ -147,15 +136,6 @@ in
         Support is available at <link xlink:href="mailto:help@hercules-ci.com">help@hercules-ci.com</link>.
       '';
     };
-    patchNix = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        Fix Nix 2.3 cache path metadata caching behavior. Has the effect of <literal>nix.package = patch pkgs.nix;</literal>
-
-        This option will be removed when Hercules CI Agent moves to Nix 2.4 (upcoming Nix release).
-      '';
-    };
     checkNix = mkOption {
       type = types.bool;
       default = true;
@@ -206,7 +186,6 @@ in
       # even shortly after the previous lookup. This *also* applies to the daemon.
       narinfo-cache-negative-ttl = 0
     '';
-    nix.package = mkIf cfg.patchNix patchedNix;
     services.hercules-ci-agent.tomlFile =
       format.generate "hercules-ci-agent.toml" cfg.settings;
   };
diff --git a/nixos/modules/services/continuous-integration/hercules-ci-agent/default.nix b/nixos/modules/services/continuous-integration/hercules-ci-agent/default.nix
index 79d1ce58054..e8a42e59de0 100644
--- a/nixos/modules/services/continuous-integration/hercules-ci-agent/default.nix
+++ b/nixos/modules/services/continuous-integration/hercules-ci-agent/default.nix
@@ -7,9 +7,7 @@ Code that is shared with nix-darwin goes in common.nix.
  */
 
 { pkgs, config, lib, ... }:
-
 let
-
   inherit (lib) mkIf mkDefault;
 
   cfg = config.services.hercules-ci-agent;
@@ -21,7 +19,7 @@ in
 {
   imports = [
     ./common.nix
-    (lib.mkRenamedOptionModule ["services" "hercules-ci-agent" "user"] ["systemd" "services" "hercules-ci-agent" "serviceConfig" "User"])
+    (lib.mkRenamedOptionModule [ "services" "hercules-ci-agent" "user" ] [ "systemd" "services" "hercules-ci-agent" "serviceConfig" "User" ])
   ];
 
   config = mkIf cfg.enable {
@@ -80,6 +78,8 @@ in
       isSystemUser = true;
     };
 
-    users.groups.hercules-ci-agent = {};
+    users.groups.hercules-ci-agent = { };
   };
+
+  meta.maintainers = [ lib.maintainers.roberth ];
 }
diff --git a/nixos/modules/services/development/hoogle.nix b/nixos/modules/services/development/hoogle.nix
index bd55483f46d..6d6c88b9b2a 100644
--- a/nixos/modules/services/development/hoogle.nix
+++ b/nixos/modules/services/development/hoogle.nix
@@ -41,7 +41,6 @@ in {
     haskellPackages = mkOption {
       description = "Which haskell package set to use.";
       default = pkgs.haskellPackages;
-      type = types.package;
       defaultText = "pkgs.haskellPackages";
     };
 
diff --git a/nixos/modules/services/hardware/auto-cpufreq.nix b/nixos/modules/services/hardware/auto-cpufreq.nix
new file mode 100644
index 00000000000..72c4eccaff7
--- /dev/null
+++ b/nixos/modules/services/hardware/auto-cpufreq.nix
@@ -0,0 +1,18 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.auto-cpufreq;
+in {
+  options = {
+    services.auto-cpufreq = {
+      enable = mkEnableOption "auto-cpufreq daemon";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.auto-cpufreq ];
+
+    systemd.packages = [ pkgs.auto-cpufreq ];
+    systemd.services.auto-cpufreq.path = with pkgs; [ bash coreutils ];
+  };
+}
diff --git a/nixos/modules/services/hardware/power-profiles-daemon.nix b/nixos/modules/services/hardware/power-profiles-daemon.nix
new file mode 100644
index 00000000000..70b7a72b8ba
--- /dev/null
+++ b/nixos/modules/services/hardware/power-profiles-daemon.nix
@@ -0,0 +1,53 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.power-profiles-daemon;
+  package = pkgs.power-profiles-daemon;
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.power-profiles-daemon = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable power-profiles-daemon, a DBus daemon that allows
+          changing system behavior based upon user-selected power profiles.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion = !config.services.tlp.enable;
+        message = ''
+          You have set services.power-profiles-daemon.enable = true;
+          which conflicts with services.tlp.enable = true;
+        '';
+      }
+    ];
+
+    services.dbus.packages = [ package ];
+
+    services.udev.packages = [ package ];
+
+    systemd.packages = [ package ];
+
+  };
+
+}
diff --git a/nixos/modules/services/hardware/thinkfan.nix b/nixos/modules/services/hardware/thinkfan.nix
index 3bda61ed1a9..7a5a7e1c41c 100644
--- a/nixos/modules/services/hardware/thinkfan.nix
+++ b/nixos/modules/services/hardware/thinkfan.nix
@@ -5,49 +5,95 @@ with lib;
 let
 
   cfg = config.services.thinkfan;
-  configFile = pkgs.writeText "thinkfan.conf" ''
-    # ATTENTION: There is only very basic sanity checking on the configuration.
-    # That means you can set your temperature limits as insane as you like. You
-    # can do anything stupid, e.g. turn off your fan when your CPU reaches 70°C.
-    #
-    # That's why this program is called THINKfan: You gotta think for yourself.
-    #
-    ######################################################################
-    #
-    # IBM/Lenovo Thinkpads (thinkpad_acpi, /proc/acpi/ibm)
-    # ====================================================
-    #
-    # IMPORTANT:
-    #
-    # To keep your HD from overheating, you have to specify a correction value for
-    # the sensor that has the HD's temperature. You need to do this because
-    # thinkfan uses only the highest temperature it can find in the system, and
-    # that'll most likely never be your HD, as most HDs are already out of spec
-    # when they reach 55 °C.
-    # Correction values are applied from left to right in the same order as the
-    # temperatures are read from the file.
-    #
-    # For example:
-    # tp_thermal /proc/acpi/ibm/thermal (0, 0, 10)
-    # will add a fixed value of 10 °C the 3rd value read from that file. Check out
-    # http://www.thinkwiki.org/wiki/Thermal_Sensors to find out how much you may
-    # want to add to certain temperatures.
-
-    ${cfg.fan}
-    ${cfg.sensors}
-
-    #  Syntax:
-    #  (LEVEL, LOW, HIGH)
-    #  LEVEL is the fan level to use (0-7 with thinkpad_acpi)
-    #  LOW is the temperature at which to step down to the previous level
-    #  HIGH is the temperature at which to step up to the next level
-    #  All numbers are integers.
-    #
-
-    ${cfg.levels}
-  '';
+  settingsFormat = pkgs.formats.yaml { };
+  configFile = settingsFormat.generate "thinkfan.yaml" cfg.settings;
+  thinkfan = pkgs.thinkfan.override { inherit (cfg) smartSupport; };
+
+  # fan-speed and temperature levels
+  levelType = with types;
+    let
+      tuple = ts: mkOptionType {
+        name = "tuple";
+        merge = mergeOneOption;
+        check = xs: all id (zipListsWith (t: x: t.check x) ts xs);
+        description = "tuple of" + concatMapStrings (t: " (${t.description})") ts;
+      };
+      level = ints.unsigned;
+      special = enum [ "level auto" "level full-speed" "level disengage" ];
+    in
+      tuple [ (either level special) level level ];
+
+  # sensor or fan config
+  sensorType = name: types.submodule {
+    freeformType = types.attrsOf settingsFormat.type;
+    options = {
+      type = mkOption {
+        type = types.enum [ "hwmon" "atasmart" "tpacpi" "nvml" ];
+        description = ''
+          The ${name} type, can be
+          <literal>hwmon</literal> for standard ${name}s,
 
-  thinkfan = pkgs.thinkfan.override { smartSupport = cfg.smartSupport; };
+          <literal>atasmart</literal> to read the temperature via
+          S.M.A.R.T (requires smartSupport to be enabled),
+
+          <literal>tpacpi</literal> for the legacy thinkpac_acpi driver, or
+
+          <literal>nvml</literal> for the (proprietary) nVidia driver.
+        '';
+      };
+      query = mkOption {
+        type = types.str;
+        description = ''
+          The query string used to match one or more ${name}s: can be
+          a fullpath to the temperature file (single ${name}) or a fullpath
+          to a driver directory (multiple ${name}s).
+
+          <note><para>
+            When multiple ${name}s match, the query can be restricted using the
+            <option>name</option> or <option>indices</option> options.
+          </para></note>
+        '';
+      };
+      indices = mkOption {
+        type = with types; nullOr (listOf ints.unsigned);
+        default = null;
+        description = ''
+          A list of ${name}s to pick in case multiple ${name}s match the query.
+
+          <note><para>Indices start from 0.</para></note>
+        '';
+      };
+    } // optionalAttrs (name == "sensor") {
+      correction = mkOption {
+        type = with types; nullOr (listOf int);
+        default = null;
+        description = ''
+          A list of values to be added to the temperature of each sensor,
+          can be used to equalize small discrepancies in temperature ratings.
+        '';
+      };
+    };
+  };
+
+  # removes NixOS special and unused attributes
+  sensorToConf = { type, query, ... }@args:
+    (filterAttrs (k: v: v != null && !(elem k ["type" "query"])) args)
+    // { "${type}" = query; };
+
+  syntaxNote = name: ''
+    <note><para>
+      This section slightly departs from the thinkfan.conf syntax.
+      The type and path must be specified like this:
+      <literal>
+        type = "tpacpi";
+        query = "/proc/acpi/ibm/${name}";
+      </literal>
+      instead of a single declaration like:
+      <literal>
+        - tpacpi: /proc/acpi/ibm/${name}
+      </literal>
+    </para></note>
+  '';
 
 in {
 
@@ -59,76 +105,93 @@ in {
         type = types.bool;
         default = false;
         description = ''
-          Whether to enable thinkfan, fan controller for IBM/Lenovo ThinkPads.
+          Whether to enable thinkfan, a fan control program.
+
+          <note><para>
+            This module targets IBM/Lenovo thinkpads by default, for
+            other hardware you will have configure it more carefully.
+          </para></note>
         '';
+        relatedPackages = [ "thinkfan" ];
       };
 
       smartSupport = mkOption {
         type = types.bool;
         default = false;
         description = ''
-          Whether to build thinkfan with SMART support to read temperatures
+          Whether to build thinkfan with S.M.A.R.T. support to read temperatures
           directly from hard disks.
         '';
       };
 
       sensors = mkOption {
-        type = types.lines;
-        default = ''
-          tp_thermal /proc/acpi/ibm/thermal (0,0,10)
-        '';
-        description =''
-          thinkfan can read temperatures from three possible sources:
-
-            /proc/acpi/ibm/thermal
-              Which is provided by the thinkpad_acpi kernel
-              module (keyword tp_thermal)
-
-            /sys/class/hwmon/*/temp*_input
-              Which may be provided by any hwmon drivers (keyword
-              hwmon)
-
-            S.M.A.R.T. (requires smartSupport to be enabled)
-              Which reads the temperature directly from the hard
-              disk using libatasmart (keyword atasmart)
-
-          Multiple sensors may be added, in which case they will be
-          numbered in their order of appearance.
-        '';
+        type = types.listOf (sensorType "sensor");
+        default = [
+          { type = "tpacpi";
+            query = "/proc/acpi/ibm/thermal";
+          }
+        ];
+        description = ''
+          List of temperature sensors thinkfan will monitor.
+        '' + syntaxNote "thermal";
       };
 
-      fan = mkOption {
-        type = types.str;
-        default = "tp_fan /proc/acpi/ibm/fan";
-        description =''
-          Specifies the fan we want to use.
-          On anything other than a Thinkpad you'll probably
-          use some PWM control file in /sys/class/hwmon.
-          A sysfs fan would be specified like this:
-            pwm_fan /sys/class/hwmon/hwmon2/device/pwm1
-        '';
+      fans = mkOption {
+        type = types.listOf (sensorType "fan");
+        default = [
+          { type = "tpacpi";
+            query = "/proc/acpi/ibm/fan";
+          }
+        ];
+        description = ''
+          List of fans thinkfan will control.
+        '' + syntaxNote "fan";
       };
 
       levels = mkOption {
-        type = types.lines;
-        default = ''
-          (0,     0,      55)
-          (1,     48,     60)
-          (2,     50,     61)
-          (3,     52,     63)
-          (6,     56,     65)
-          (7,     60,     85)
-          (127,   80,     32767)
-        '';
+        type = types.listOf levelType;
+        default = [
+          [0  0   55]
+          [1  48  60]
+          [2  50  61]
+          [3  52  63]
+          [6  56  65]
+          [7  60  85]
+          ["level auto" 80 32767]
+        ];
         description = ''
-          (LEVEL, LOW, HIGH)
-          LEVEL is the fan level to use (0-7 with thinkpad_acpi).
+          [LEVEL LOW HIGH]
+
+          LEVEL is the fan level to use: it can be an integer (0-7 with thinkpad_acpi),
+          "level auto" (to keep the default firmware behavior), "level full-speed" or
+          "level disengage" (to run the fan as fast as possible).
           LOW is the temperature at which to step down to the previous level.
           HIGH is the temperature at which to step up to the next level.
           All numbers are integers.
         '';
       };
 
+      extraArgs = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        example = [ "-b" "0" ];
+        description = ''
+          A list of extra command line arguments to pass to thinkfan.
+          Check the thinkfan(1) manpage for available arguments.
+        '';
+      };
+
+      settings = mkOption {
+        type = types.attrsOf settingsFormat.type;
+        default = { };
+        description = ''
+          Thinkfan settings. Use this option to configure thinkfan
+          settings not exposed in a NixOS option or to bypass one.
+          Before changing this, read the <literal>thinkfan.conf(5)</literal>
+          manpage and take a look at the example config file at
+          <link xlink:href="https://github.com/vmatare/thinkfan/blob/master/examples/thinkfan.yaml"/>
+        '';
+      };
 
     };
 
@@ -138,12 +201,21 @@ in {
 
     environment.systemPackages = [ thinkfan ];
 
-    systemd.services.thinkfan = {
-      description = "Thinkfan";
-      after = [ "basic.target" ];
-      wantedBy = [ "multi-user.target" ];
-      path = [ thinkfan ];
-      serviceConfig.ExecStart = "${thinkfan}/bin/thinkfan -n -c ${configFile}";
+    services.thinkfan.settings = mapAttrs (k: v: mkDefault v) {
+      sensors = map sensorToConf cfg.sensors;
+      fans    = map sensorToConf cfg.fans;
+      levels  = cfg.levels;
+    };
+
+    systemd.packages = [ thinkfan ];
+
+    systemd.services = {
+      thinkfan.environment.THINKFAN_ARGS = escapeShellArgs ([ "-c" configFile ] ++ cfg.extraArgs);
+
+      # must be added manually, see issue #81138
+      thinkfan.wantedBy = [ "multi-user.target" ];
+      thinkfan-wakeup.wantedBy = [ "sleep.target" ];
+      thinkfan-sleep.wantedBy = [ "sleep.target" ];
     };
 
     boot.extraModprobeConfig = "options thinkpad_acpi experimental=1 fan_control=1";
diff --git a/nixos/modules/services/mail/dovecot.nix b/nixos/modules/services/mail/dovecot.nix
index 03e7e40e388..a2298152b02 100644
--- a/nixos/modules/services/mail/dovecot.nix
+++ b/nixos/modules/services/mail/dovecot.nix
@@ -463,7 +463,7 @@ in
     environment.systemPackages = [ dovecotPkg ];
 
     warnings = mkIf (any isList options.services.dovecot2.mailboxes.definitions) [
-      "Declaring `services.dovecot2.mailboxes' as a list is deprecated and will break eval in 21.03! See the release notes for more info for migration."
+      "Declaring `services.dovecot2.mailboxes' as a list is deprecated and will break eval in 21.05! See the release notes for more info for migration."
     ];
 
     assertions = [
diff --git a/nixos/modules/services/misc/etebase-server.nix b/nixos/modules/services/misc/etebase-server.nix
new file mode 100644
index 00000000000..d9d12698d79
--- /dev/null
+++ b/nixos/modules/services/misc/etebase-server.nix
@@ -0,0 +1,205 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.etebase-server;
+
+  pythonEnv = pkgs.python3.withPackages (ps: with ps;
+    [ etebase-server daphne ]);
+
+  dbConfig = {
+    sqlite3 = ''
+      engine = django.db.backends.sqlite3
+      name = ${cfg.dataDir}/db.sqlite3
+    '';
+  };
+
+  defaultConfigIni = toString (pkgs.writeText "etebase-server.ini" ''
+    [global]
+    debug = false
+    secret_file = ${if cfg.secretFile != null then cfg.secretFile else ""}
+    media_root = ${cfg.dataDir}/media
+
+    [allowed_hosts]
+    allowed_host1 = ${cfg.host}
+
+    [database]
+    ${dbConfig."${cfg.database.type}"}
+  '');
+
+  configIni = if cfg.customIni != null then cfg.customIni else defaultConfigIni;
+
+  defaultUser = "etebase-server";
+in
+{
+  options = {
+    services.etebase-server = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        example = true;
+        description = ''
+          Whether to enable the Etebase server.
+
+          Once enabled you need to create an admin user using the
+          shell command <literal>etebase-server createsuperuser</literal>.
+          Then you can login and create accounts on your-etebase-server.com/admin
+        '';
+      };
+
+      secretFile = mkOption {
+        default = null;
+        type = with types; nullOr str;
+        description = ''
+          The path to a file containing the secret
+          used as django's SECRET_KEY.
+        '';
+      };
+
+      dataDir = mkOption {
+        type = types.str;
+        default = "/var/lib/etebase-server";
+        description = "Directory to store the Etebase server data.";
+      };
+
+      port = mkOption {
+        type = with types; nullOr port;
+        default = 8001;
+        description = "Port to listen on.";
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to open ports in the firewall for the server.
+        '';
+      };
+
+      host = mkOption {
+        type = types.str;
+        default = "0.0.0.0";
+        example = "localhost";
+        description = ''
+          Host to listen on.
+        '';
+      };
+
+      unixSocket = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = "The path to the socket to bind to.";
+        example = "/run/etebase-server/etebase-server.sock";
+      };
+
+      database = {
+        type = mkOption {
+          type = types.enum [ "sqlite3" ];
+          default = "sqlite3";
+          description = ''
+            Database engine to use.
+            Currently only sqlite3 is supported.
+            Other options can be configured using <literal>extraConfig</literal>.
+          '';
+        };
+      };
+
+      customIni = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Custom etebase-server.ini.
+
+          See <literal>etebase-src/etebase-server.ini.example</literal> for available options.
+
+          Setting this option overrides the default config which is generated from the options
+          <literal>secretFile</literal>, <literal>host</literal> and <literal>database</literal>.
+        '';
+        example = literalExample ''
+          [global]
+          debug = false
+          secret_file = /path/to/secret
+          media_root = /path/to/media
+
+          [allowed_hosts]
+          allowed_host1 = example.com
+
+          [database]
+          engine = django.db.backends.sqlite3
+          name = db.sqlite3
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = defaultUser;
+        description = "User under which Etebase server runs.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = with pkgs; [
+      (runCommand "etebase-server" {
+        buildInputs = [ makeWrapper ];
+      } ''
+        makeWrapper ${pythonEnv}/bin/etebase-server \
+          $out/bin/etebase-server \
+          --run "cd ${cfg.dataDir}" \
+          --prefix ETEBASE_EASY_CONFIG_PATH : "${configIni}"
+      '')
+    ];
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' - ${cfg.user} ${config.users.users.${cfg.user}.group} - -"
+    ];
+
+    systemd.services.etebase-server = {
+      description = "An Etebase (EteSync 2.0) server";
+      after = [ "network.target" "systemd-tmpfiles-setup.service" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        User = cfg.user;
+        Restart = "always";
+        WorkingDirectory = cfg.dataDir;
+      };
+      environment = {
+        PYTHONPATH="${pythonEnv}/${pkgs.python3.sitePackages}";
+        ETEBASE_EASY_CONFIG_PATH="${configIni}";
+      };
+      preStart = ''
+        # Auto-migrate on first run or if the package has changed
+        versionFile="${cfg.dataDir}/src-version"
+        if [[ $(cat "$versionFile" 2>/dev/null) != ${pkgs.etebase-server} ]]; then
+          ${pythonEnv}/bin/etebase-server migrate
+          echo ${pkgs.etebase-server} > "$versionFile"
+        fi
+      '';
+      script =
+        let
+          networking = if cfg.unixSocket != null
+          then "-u ${cfg.unixSocket}"
+          else "-b 0.0.0.0 -p ${toString cfg.port}";
+        in ''
+          cd "${pythonEnv}/lib/etebase-server";
+          ${pythonEnv}/bin/daphne ${networking} \
+            etebase_server.asgi:application
+        '';
+    };
+
+    users = optionalAttrs (cfg.user == defaultUser) {
+      users.${defaultUser} = {
+        group = defaultUser;
+        home = cfg.dataDir;
+      };
+
+      groups.${defaultUser} = {};
+    };
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.port ];
+    };
+  };
+}
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index de4d1bf1987..61faeab7d32 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -454,7 +454,7 @@ in {
         authentication = mkOption {
           type = with types; nullOr str;
           default = null;
-          description = "Authentitcation type to use, see http://api.rubyonrails.org/classes/ActionMailer/Base.html";
+          description = "Authentication type to use, see http://api.rubyonrails.org/classes/ActionMailer/Base.html";
         };
 
         enableStartTLSAuto = mkOption {
diff --git a/nixos/modules/services/misc/pykms.nix b/nixos/modules/services/misc/pykms.nix
index d6aeae48ccb..2f752bcc7ed 100644
--- a/nixos/modules/services/misc/pykms.nix
+++ b/nixos/modules/services/misc/pykms.nix
@@ -1,12 +1,12 @@
 { config, lib, pkgs, ... }:
 
 with lib;
-
 let
   cfg = config.services.pykms;
   libDir = "/var/lib/pykms";
 
-in {
+in
+{
   meta.maintainers = with lib.maintainers; [ peterhoeg ];
 
   imports = [
@@ -46,14 +46,14 @@ in {
       };
 
       logLevel = mkOption {
-        type = types.enum [ "CRITICAL" "ERROR" "WARNING" "INFO" "DEBUG" "MINI" ];
+        type = types.enum [ "CRITICAL" "ERROR" "WARNING" "INFO" "DEBUG" "MININFO" ];
         default = "INFO";
         description = "How much to log";
       };
 
       extraArgs = mkOption {
         type = types.listOf types.str;
-        default = [];
+        default = [ ];
         description = "Additional arguments";
       };
     };
@@ -74,8 +74,9 @@ in {
         ExecStartPre = "${getBin pykms}/libexec/create_pykms_db.sh ${libDir}/clients.db";
         ExecStart = lib.concatStringsSep " " ([
           "${getBin pykms}/bin/server"
-          "--logfile STDOUT"
-          "--loglevel ${cfg.logLevel}"
+          "--logfile=STDOUT"
+          "--loglevel=${cfg.logLevel}"
+          "--sqlite=${libDir}/clients.db"
         ] ++ cfg.extraArgs ++ [
           cfg.listenAddress
           (toString cfg.port)
diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix
index 1fd85c66f84..940f2818937 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters.nix
@@ -56,6 +56,7 @@ let
     "unifi-poller"
     "varnish"
     "wireguard"
+    "flow"
   ] (name:
     import (./. + "/exporters/${name}.nix") { inherit config lib pkgs options; }
   );
@@ -238,9 +239,6 @@ in
     services.prometheus.exporters.minio.minioAccessSecret = mkDefault config.services.minio.secretKey;
   })] ++ [(mkIf config.services.prometheus.exporters.rtl_433.enable {
     hardware.rtl-sdr.enable = mkDefault true;
-  })] ++ [(mkIf config.services.nginx.enable {
-    systemd.services.prometheus-nginx-exporter.after = [ "nginx.service" ];
-    systemd.services.prometheus-nginx-exporter.requires = [ "nginx.service" ];
   })] ++ [(mkIf config.services.postfix.enable {
     services.prometheus.exporters.postfix.group = mkDefault config.services.postfix.setgidGroup;
   })] ++ (mapAttrsToList (name: conf:
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/flow.nix b/nixos/modules/services/monitoring/prometheus/exporters/flow.nix
new file mode 100644
index 00000000000..6a35f46308f
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/flow.nix
@@ -0,0 +1,50 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.flow;
+in {
+  port = 9590;
+  extraOpts = {
+    brokers = mkOption {
+      type = types.listOf types.str;
+      example = literalExample ''[ "kafka.example.org:19092" ]'';
+      description = "List of Kafka brokers to connect to.";
+    };
+
+    asn = mkOption {
+      type = types.ints.positive;
+      example = 65542;
+      description = "The ASN being monitored.";
+    };
+
+    partitions = mkOption {
+      type = types.listOf types.int;
+      default = [];
+      description = ''
+        The number of the partitions to consume, none means all.
+      '';
+    };
+
+    topic = mkOption {
+      type = types.str;
+      example = "pmacct.acct";
+      description = "The Kafka topic to consume from.";
+    };
+  };
+
+  serviceOpts = {
+    serviceConfig = {
+      DynamicUser = true;
+      ExecStart = ''
+        ${pkgs.prometheus-flow-exporter}/bin/flow-exporter \
+          -asn ${toString cfg.asn} \
+          -topic ${cfg.topic} \
+          -brokers ${concatStringsSep "," cfg.brokers} \
+          ${optionalString (cfg.partitions != []) "-partitions ${concatStringsSep "," cfg.partitions}"} \
+          -addr ${cfg.listenAddress}:${toString cfg.port} ${concatStringsSep " " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix b/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix
index 56cddfc55b7..5ee8c346be1 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix
@@ -42,7 +42,7 @@ in
       '';
     };
   };
-  serviceOpts = {
+  serviceOpts = mkMerge ([{
     serviceConfig = {
       ExecStart = ''
         ${pkgs.prometheus-nginx-exporter}/bin/nginx-prometheus-exporter \
@@ -54,7 +54,10 @@ in
           ${concatStringsSep " \\\n  " cfg.extraFlags}
       '';
     };
-  };
+  }] ++ [(mkIf config.services.nginx.enable {
+    after = [ "nginx.service" ];
+    requires = [ "nginx.service" ];
+  })]);
   imports = [
     (mkRenamedOptionModule [ "telemetryEndpoint" ] [ "telemetryPath" ])
     (mkRemovedOptionModule [ "insecure" ] ''
diff --git a/nixos/modules/services/networking/dnscrypt-proxy2.nix b/nixos/modules/services/networking/dnscrypt-proxy2.nix
index ff8a2ab3077..afc2a6d1c75 100644
--- a/nixos/modules/services/networking/dnscrypt-proxy2.nix
+++ b/nixos/modules/services/networking/dnscrypt-proxy2.nix
@@ -87,6 +87,7 @@ in
         NoNewPrivileges = true;
         NonBlocking = true;
         PrivateDevices = true;
+        ProtectClock = true;
         ProtectControlGroups = true;
         ProtectHome = true;
         ProtectHostname = true;
@@ -107,8 +108,13 @@ in
         SystemCallFilter = [
           "@system-service"
           "@chown"
+          "~@aio"
+          "~@keyring"
+          "~@memlock"
           "~@resources"
-          "@privileged"
+          "~@setuid"
+          "~@sync"
+          "~@timer"
         ];
       };
     };
diff --git a/nixos/modules/services/networking/epmd.nix b/nixos/modules/services/networking/epmd.nix
index 692b75e4f08..f7cdc0fe79c 100644
--- a/nixos/modules/services/networking/epmd.nix
+++ b/nixos/modules/services/networking/epmd.nix
@@ -53,4 +53,6 @@ in
       };
     };
   };
+
+  meta.maintainers = teams.beam.members;
 }
diff --git a/nixos/modules/services/networking/flashpolicyd.nix b/nixos/modules/services/networking/flashpolicyd.nix
deleted file mode 100644
index d3ac78430ca..00000000000
--- a/nixos/modules/services/networking/flashpolicyd.nix
+++ /dev/null
@@ -1,86 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  cfg = config.services.flashpolicyd;
-
-  flashpolicyd = pkgs.stdenv.mkDerivation {
-    name = "flashpolicyd-0.6";
-
-    src = pkgs.fetchurl {
-      name = "flashpolicyd_v0.6.zip";
-      url = "https://download.adobe.com/pub/adobe/devnet/flashplayer/articles/socket_policy_files/flashpolicyd_v0.6.zip";
-      sha256 = "16zk237233npwfq1m4ksy4g5lzy1z9fp95w7pz0cdlpmv0fv9sm3";
-    };
-
-    buildInputs = [ pkgs.unzip pkgs.perl ];
-
-    installPhase = "mkdir $out; cp -pr * $out/; chmod +x $out/*/*.pl";
-  };
-
-  flashpolicydWrapper = pkgs.writeScriptBin "flashpolicyd"
-    ''
-      #! ${pkgs.runtimeShell}
-      exec ${flashpolicyd}/Perl_xinetd/in.flashpolicyd.pl \
-        --file=${pkgs.writeText "flashpolixy.xml" cfg.policy} \
-        2> /dev/null
-    '';
-
-in
-
-{
-
-  ###### interface
-
-  options = {
-
-    services.flashpolicyd = {
-
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description =
-          ''
-            Whether to enable the Flash Policy server.  This is
-            necessary if you want Flash applications to make
-            connections to your server.
-          '';
-      };
-
-      policy = mkOption {
-        type = types.lines;
-        default =
-          ''
-            <?xml version="1.0"?>
-            <!DOCTYPE cross-domain-policy SYSTEM "/xml/dtds/cross-domain-policy.dtd">
-            <cross-domain-policy>
-              <site-control permitted-cross-domain-policies="master-only"/>
-              <allow-access-from domain="*" to-ports="*" />
-            </cross-domain-policy>
-          '';
-        description = "The policy to be served.  The default is to allow connections from any domain to any port.";
-      };
-
-    };
-
-  };
-
-
-  ###### implementation
-
-  config = mkIf cfg.enable {
-
-    services.xinetd.enable = true;
-
-    services.xinetd.services = singleton
-      { name = "flashpolicy";
-        port = 843;
-        unlisted = true;
-        server = "${flashpolicydWrapper}/bin/flashpolicyd";
-      };
-
-  };
-
-}
diff --git a/nixos/modules/services/networking/jitsi-videobridge.nix b/nixos/modules/services/networking/jitsi-videobridge.nix
index 5482e997a40..80f35d56e2d 100644
--- a/nixos/modules/services/networking/jitsi-videobridge.nix
+++ b/nixos/modules/services/networking/jitsi-videobridge.nix
@@ -191,6 +191,16 @@ in
         Whether to open ports in the firewall for the videobridge.
       '';
     };
+
+    apis = mkOption {
+      type = with types; listOf str;
+      description = ''
+        What is passed as --apis= parameter. If this is empty, "none" is passed.
+        Needed for monitoring jitsi.
+      '';
+      default = [];
+      example = literalExample "[ \"colibri\" \"rest\" ]";
+    };
   };
 
   config = mkIf cfg.enable {
@@ -221,7 +231,7 @@ in
         "export ${toVarName name}=$(cat ${xmppConfig.passwordFile})\n"
       ) cfg.xmppConfigs))
       + ''
-        ${pkgs.jitsi-videobridge}/bin/jitsi-videobridge --apis=none
+        ${pkgs.jitsi-videobridge}/bin/jitsi-videobridge --apis=${if (cfg.apis == []) then "none" else concatStringsSep "," cfg.apis}
       '';
 
       serviceConfig = {
diff --git a/nixos/modules/services/networking/kresd.nix b/nixos/modules/services/networking/kresd.nix
index 074830fc352..4131ff8be5d 100644
--- a/nixos/modules/services/networking/kresd.nix
+++ b/nixos/modules/services/networking/kresd.nix
@@ -140,7 +140,7 @@ in {
 
     # Try cleaning up the previously default location of cache file.
     # Note that /var/cache/* should always be safe to remove.
-    # TODO: remove later, probably between 20.09 and 21.03
+    # TODO: remove later, probably between 20.09 and 21.05
     systemd.tmpfiles.rules = [ "R /var/cache/kresd" ];
   };
 }
diff --git a/nixos/modules/services/networking/resilio.nix b/nixos/modules/services/networking/resilio.nix
index 6193d7340fc..4701b0e8143 100644
--- a/nixos/modules/services/networking/resilio.nix
+++ b/nixos/modules/services/networking/resilio.nix
@@ -183,6 +183,7 @@ in
 
       sharedFolders = mkOption {
         default = [];
+        type = types.listOf (types.attrsOf types.anything);
         example =
           [ { secret         = "AHMYFPCQAHBM7LQPFXQ7WV6Y42IGUXJ5Y";
               directory      = "/home/user/sync_test";
diff --git a/nixos/modules/services/networking/sabnzbd.nix b/nixos/modules/services/networking/sabnzbd.nix
index ff5aef7d1cb..43566dfd25c 100644
--- a/nixos/modules/services/networking/sabnzbd.nix
+++ b/nixos/modules/services/networking/sabnzbd.nix
@@ -18,16 +18,19 @@ in
       enable = mkEnableOption "the sabnzbd server";
 
       configFile = mkOption {
+        type = types.path;
         default = "/var/lib/sabnzbd/sabnzbd.ini";
         description = "Path to config file.";
       };
 
       user = mkOption {
         default = "sabnzbd";
+        type = types.str;
         description = "User to run the service as";
       };
 
       group = mkOption {
+        type = types.str;
         default = "sabnzbd";
         description = "Group to run the service as";
       };
diff --git a/nixos/modules/services/security/oauth2_proxy.nix b/nixos/modules/services/security/oauth2_proxy.nix
index 486f3ab0538..77c579279ab 100644
--- a/nixos/modules/services/security/oauth2_proxy.nix
+++ b/nixos/modules/services/security/oauth2_proxy.nix
@@ -538,6 +538,7 @@ in
 
     extraConfig = mkOption {
       default = {};
+      type = types.attrsOf types.anything;
       description = ''
         Extra config to pass to oauth2-proxy.
       '';
diff --git a/nixos/modules/services/security/oauth2_proxy_nginx.nix b/nixos/modules/services/security/oauth2_proxy_nginx.nix
index be6734f439f..553638ad496 100644
--- a/nixos/modules/services/security/oauth2_proxy_nginx.nix
+++ b/nixos/modules/services/security/oauth2_proxy_nginx.nix
@@ -31,7 +31,7 @@ in
         proxyPass = cfg.proxy;
         extraConfig = ''
           proxy_set_header X-Scheme                $scheme;
-          proxy_set_header X-Auth-Request-Redirect $request_uri;
+          proxy_set_header X-Auth-Request-Redirect $scheme://$host$request_uri;
         '';
       };
       locations."/oauth2/auth" = {
diff --git a/nixos/modules/services/web-apps/galene.nix b/nixos/modules/services/web-apps/galene.nix
new file mode 100644
index 00000000000..769490e915a
--- /dev/null
+++ b/nixos/modules/services/web-apps/galene.nix
@@ -0,0 +1,178 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.galene;
+  defaultstateDir = "/var/lib/galene";
+  defaultrecordingsDir = "${cfg.stateDir}/recordings";
+  defaultgroupsDir = "${cfg.stateDir}/groups";
+  defaultdataDir = "${cfg.stateDir}/data";
+in
+{
+  options = {
+    services.galene = {
+      enable = mkEnableOption "Galene Service.";
+
+      stateDir = mkOption {
+        default = defaultstateDir;
+        type = types.str;
+        description = ''
+          The directory where Galene stores its internal state. If left as the default
+          value this directory will automatically be created before the Galene server
+          starts, otherwise the sysadmin is responsible for ensuring the directory
+          exists with appropriate ownership and permissions.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "galene";
+        description = "User account under which galene runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "galene";
+        description = "Group under which galene runs.";
+      };
+
+      insecure = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether Galene should listen in http or in https. If left as the default
+          value (false), Galene needs to be fed a private key and a certificate.
+        '';
+      };
+
+      certFile = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/path/to/your/cert.pem";
+        description = ''
+          Path to the server's certificate. The file is copied at runtime to
+          Galene's data directory where it needs to reside.
+        '';
+      };
+
+      keyFile = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/path/to/your/key.pem";
+        description = ''
+          Path to the server's private key. The file is copied at runtime to
+          Galene's data directory where it needs to reside.
+        '';
+      };
+
+      httpAddress = mkOption {
+        type = types.str;
+        default = "";
+        description = "HTTP listen address for galene.";
+      };
+
+      httpPort = mkOption {
+        type = types.port;
+        default = 8443;
+        description = "HTTP listen port.";
+      };
+
+      staticDir = mkOption {
+        type = types.str;
+        default = "${cfg.package.static}/static";
+        example = "/var/lib/galene/static";
+        description = "Web server directory.";
+      };
+
+      recordingsDir = mkOption {
+        type = types.str;
+        default = defaultrecordingsDir;
+        example = "/var/lib/galene/recordings";
+        description = "Recordings directory.";
+      };
+
+      dataDir = mkOption {
+        type = types.str;
+        default = defaultdataDir;
+        example = "/var/lib/galene/data";
+        description = "Data directory.";
+      };
+
+      groupsDir = mkOption {
+        type = types.str;
+        default = defaultgroupsDir;
+        example = "/var/lib/galene/groups";
+        description = "Web server directory.";
+      };
+
+      package = mkOption {
+        default = pkgs.galene;
+        defaultText = "pkgs.galene";
+        type = types.package;
+        description = ''
+          Package for running Galene.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = cfg.insecure || (cfg.certFile != null && cfg.keyFile != null);
+        message = ''
+          Galene needs both certFile and keyFile defined for encryption, or
+          the insecure flag.
+        '';
+      }
+    ];
+
+    systemd.services.galene = {
+      description = "galene";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      preStart = ''
+        install -m 700 -o '${cfg.user}' -g '${cfg.group}' ${cfg.certFile} ${cfg.dataDir}/cert.pem
+        install -m 700 -o '${cfg.user}' -g '${cfg.group}' ${cfg.keyFile} ${cfg.dataDir}/key.pem
+      '';
+
+      serviceConfig = mkMerge [
+        {
+          Type = "simple";
+          User = cfg.user;
+          Group = cfg.group;
+          WorkingDirectory = cfg.stateDir;
+          ExecStart = ''${cfg.package}/bin/galene \
+          ${optionalString (cfg.insecure) "-insecure"} \
+          -data ${cfg.dataDir} \
+          -groups ${cfg.groupsDir} \
+          -recordings ${cfg.recordingsDir} \
+          -static ${cfg.staticDir}'';
+          Restart = "always";
+          # Upstream Requirements
+          LimitNOFILE = 65536;
+          StateDirectory = [ ] ++
+            optional (cfg.stateDir == defaultstateDir) "galene" ++
+            optional (cfg.dataDir == defaultdataDir) "galene/data" ++
+            optional (cfg.groupsDir == defaultgroupsDir) "galene/groups" ++
+            optional (cfg.recordingsDir == defaultrecordingsDir) "galene/recordings";
+        }
+      ];
+    };
+
+    users.users = mkIf (cfg.user == "galene")
+      {
+        galene = {
+          description = "galene Service";
+          group = cfg.group;
+          isSystemUser = true;
+        };
+      };
+
+    users.groups = mkIf (cfg.group == "galene") {
+      galene = { };
+    };
+  };
+  meta.maintainers = with lib.maintainers; [ rgrunbla ];
+}
diff --git a/nixos/modules/services/web-apps/hedgedoc.nix b/nixos/modules/services/web-apps/hedgedoc.nix
index 3f646d7db0c..d940f3d3dae 100644
--- a/nixos/modules/services/web-apps/hedgedoc.nix
+++ b/nixos/modules/services/web-apps/hedgedoc.nix
@@ -5,6 +5,10 @@ with lib;
 let
   cfg = config.services.hedgedoc;
 
+  # 21.03 will not be an official release - it was instead 21.05.  This
+  # versionAtLeast statement remains set to 21.03 for backwards compatibility.
+  # See https://github.com/NixOS/nixpkgs/pull/108899 and
+  # https://github.com/NixOS/rfcs/blob/master/rfcs/0080-nixos-release-schedule.md.
   name = if versionAtLeast config.system.stateVersion "21.03"
     then "hedgedoc"
     else "codimd";
diff --git a/nixos/modules/services/web-apps/mastodon.nix b/nixos/modules/services/web-apps/mastodon.nix
new file mode 100644
index 00000000000..92b1be963bf
--- /dev/null
+++ b/nixos/modules/services/web-apps/mastodon.nix
@@ -0,0 +1,542 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.mastodon;
+  # We only want to create a database if we're actually going to connect to it.
+  databaseActuallyCreateLocally = cfg.database.createLocally && cfg.database.host == "/run/postgresql";
+
+  env = {
+    RAILS_ENV = "production";
+    NODE_ENV = "production";
+
+    DB_USER = cfg.database.user;
+
+    REDIS_HOST = cfg.redis.host;
+    REDIS_PORT = toString(cfg.redis.port);
+    DB_HOST = cfg.database.host;
+    DB_PORT = toString(cfg.database.port);
+    DB_NAME = cfg.database.name;
+    LOCAL_DOMAIN = cfg.localDomain;
+    SMTP_SERVER = cfg.smtp.host;
+    SMTP_PORT = toString(cfg.smtp.port);
+    SMTP_FROM_ADDRESS = cfg.smtp.fromAddress;
+    PAPERCLIP_ROOT_PATH = "/var/lib/mastodon/public-system";
+    PAPERCLIP_ROOT_URL = "/system";
+    ES_ENABLED = if (cfg.elasticsearch.host != null) then "true" else "false";
+    ES_HOST = cfg.elasticsearch.host;
+    ES_PORT = toString(cfg.elasticsearch.port);
+  }
+  // (if cfg.smtp.authenticate then { SMTP_LOGIN  = cfg.smtp.user; } else {})
+  // cfg.extraConfig;
+
+  envFile = pkgs.writeText "mastodon.env" (lib.concatMapStrings (s: s + "\n") (
+    (lib.concatLists (lib.mapAttrsToList (name: value:
+      if value != null then [
+        "${name}=\"${toString value}\""
+      ] else []
+    ) env))));
+
+  mastodonEnv = pkgs.writeShellScriptBin "mastodon-env" ''
+    set -a
+    source "${envFile}"
+    source /var/lib/mastodon/.secrets_env
+    eval -- "\$@"
+  '';
+
+in {
+
+  options = {
+    services.mastodon = {
+      enable = lib.mkEnableOption "Mastodon, a federated social network server";
+
+      configureNginx = lib.mkOption {
+        description = ''
+          Configure nginx as a reverse proxy for mastodon.
+          Note that this makes some assumptions on your setup, and sets settings that will
+          affect other virtualHosts running on your nginx instance, if any.
+          Alternatively you can configure a reverse-proxy of your choice to serve these paths:
+
+          <code>/ -> $(nix-instantiate --eval '&lt;nixpkgs&gt;' -A mastodon.outPath)/public</code>
+
+          <code>/ -> 127.0.0.1:{{ webPort }} </code>(If there was no file in the directory above.)
+
+          <code>/system/ -> /var/lib/mastodon/public-system/</code>
+
+          <code>/api/v1/streaming/ -> 127.0.0.1:{{ streamingPort }}</code>
+
+          Make sure that websockets are forwarded properly. You might want to set up caching
+          of some requests. Take a look at mastodon's provided nginx configuration at
+          <code>https://github.com/tootsuite/mastodon/blob/master/dist/nginx.conf</code>.
+        '';
+        type = lib.types.bool;
+        default = false;
+      };
+
+      user = lib.mkOption {
+        description = ''
+          User under which mastodon runs. If it is set to "mastodon",
+          that user will be created, otherwise it should be set to the
+          name of a user created elsewhere.  In both cases,
+          <package>mastodon</package> and a package containing only
+          the shell script <code>mastodon-env</code> will be added to
+          the user's package set. To run a command from
+          <package>mastodon</package> such as <code>tootctl</code>
+          with the environment configured by this module use
+          <code>mastodon-env</code>, as in:
+
+          <code>mastodon-env tootctl accounts create newuser --email newuser@example.com</code>
+        '';
+        type = lib.types.str;
+        default = "mastodon";
+      };
+
+      group = lib.mkOption {
+        description = ''
+          Group under which mastodon runs.
+          If it is set to "mastodon", a group will be created.
+        '';
+        type = lib.types.str;
+        default = "mastodon";
+      };
+
+      streamingPort = lib.mkOption {
+        description = "TCP port used by the mastodon-streaming service.";
+        type = lib.types.port;
+        default = 55000;
+      };
+
+      webPort = lib.mkOption {
+        description = "TCP port used by the mastodon-web service.";
+        type = lib.types.port;
+        default = 55001;
+      };
+
+      sidekiqPort = lib.mkOption {
+        description = "TCP port used by the mastodon-sidekiq service";
+        type = lib.types.port;
+        default = 55002;
+      };
+
+      vapidPublicKeyFile = lib.mkOption {
+        description = ''
+          Path to file containing the public key used for Web Push
+          Voluntary Application Server Identification.  A new keypair can
+          be generated by running:
+
+          <code>nix build -f '&lt;nixpkgs&gt;' mastodon; cd result; bin/rake webpush:generate_keys</code>
+
+          If <option>mastodon.vapidPrivateKeyFile</option>does not
+          exist, it and this file will be created with a new keypair.
+        '';
+        default = "/var/lib/mastodon/secrets/vapid-public-key";
+        type = lib.types.str;
+      };
+
+      localDomain = lib.mkOption {
+        description = "The domain serving your Mastodon instance.";
+        example = "social.example.org";
+        type = lib.types.str;
+      };
+
+      secretKeyBaseFile = lib.mkOption {
+        description = ''
+          Path to file containing the secret key base.
+          A new secret key base can be generated by running:
+
+          <code>nix build -f '&lt;nixpkgs&gt;' mastodon; cd result; bin/rake secret</code>
+
+          If this file does not exist, it will be created with a new secret key base.
+        '';
+        default = "/var/lib/mastodon/secrets/secret-key-base";
+        type = lib.types.str;
+      };
+
+      otpSecretFile = lib.mkOption {
+        description = ''
+          Path to file containing the OTP secret.
+          A new OTP secret can be generated by running:
+
+          <code>nix build -f '&lt;nixpkgs&gt;' mastodon; cd result; bin/rake secret</code>
+
+          If this file does not exist, it will be created with a new OTP secret.
+        '';
+        default = "/var/lib/mastodon/secrets/otp-secret";
+        type = lib.types.str;
+      };
+
+      vapidPrivateKeyFile = lib.mkOption {
+        description = ''
+          Path to file containing the private key used for Web Push
+          Voluntary Application Server Identification.  A new keypair can
+          be generated by running:
+
+          <code>nix build -f '&lt;nixpkgs&gt;' mastodon; cd result; bin/rake webpush:generate_keys</code>
+
+          If this file does not exist, it will be created with a new
+          private key.
+        '';
+        default = "/var/lib/mastodon/secrets/vapid-private-key";
+        type = lib.types.str;
+      };
+
+      redis = {
+        createLocally = lib.mkOption {
+          description = "Configure local Redis server for Mastodon.";
+          type = lib.types.bool;
+          default = true;
+        };
+
+        host = lib.mkOption {
+          description = "Redis host.";
+          type = lib.types.str;
+          default = "127.0.0.1";
+        };
+
+        port = lib.mkOption {
+          description = "Redis port.";
+          type = lib.types.port;
+          default = 6379;
+        };
+      };
+
+      database = {
+        createLocally = lib.mkOption {
+          description = "Configure local PostgreSQL database server for Mastodon.";
+          type = lib.types.bool;
+          default = true;
+        };
+
+        host = lib.mkOption {
+          type = lib.types.str;
+          default = "/run/postgresql";
+          example = "192.168.23.42";
+          description = "Database host address or unix socket.";
+        };
+
+        port = lib.mkOption {
+          type = lib.types.int;
+          default = 5432;
+          description = "Database host port.";
+        };
+
+        name = lib.mkOption {
+          type = lib.types.str;
+          default = "mastodon";
+          description = "Database name.";
+        };
+
+        user = lib.mkOption {
+          type = lib.types.str;
+          default = "mastodon";
+          description = "Database user.";
+        };
+
+        passwordFile = lib.mkOption {
+          type = lib.types.nullOr lib.types.path;
+          default = "/var/lib/mastodon/secrets/db-password";
+          example = "/run/keys/mastodon-db-password";
+          description = ''
+            A file containing the password corresponding to
+            <option>database.user</option>.
+          '';
+        };
+      };
+
+      smtp = {
+        createLocally = lib.mkOption {
+          description = "Configure local Postfix SMTP server for Mastodon.";
+          type = lib.types.bool;
+          default = true;
+        };
+
+        authenticate = lib.mkOption {
+          description = "Authenticate with the SMTP server using username and password.";
+          type = lib.types.bool;
+          default = true;
+        };
+
+        host = lib.mkOption {
+          description = "SMTP host used when sending emails to users.";
+          type = lib.types.str;
+          default = "127.0.0.1";
+        };
+
+        port = lib.mkOption {
+          description = "SMTP port used when sending emails to users.";
+          type = lib.types.port;
+          default = 25;
+        };
+
+        fromAddress = lib.mkOption {
+          description = ''"From" address used when sending Emails to users.'';
+          type = lib.types.str;
+        };
+
+        user = lib.mkOption {
+          description = "SMTP login name.";
+          type = lib.types.str;
+        };
+
+        passwordFile = lib.mkOption {
+          description = ''
+            Path to file containing the SMTP password.
+          '';
+          default = "/var/lib/mastodon/secrets/smtp-password";
+          example = "/run/keys/mastodon-smtp-password";
+          type = lib.types.str;
+        };
+      };
+
+      elasticsearch = {
+        host = lib.mkOption {
+          description = ''
+            Elasticsearch host.
+            If it is not null, Elasticsearch full text search will be enabled.
+          '';
+          type = lib.types.nullOr lib.types.str;
+          default = null;
+        };
+
+        port = lib.mkOption {
+          description = "Elasticsearch port.";
+          type = lib.types.port;
+          default = 9200;
+        };
+      };
+
+      package = lib.mkOption {
+        type = lib.types.package;
+        default = pkgs.mastodon;
+        defaultText = "pkgs.mastodon";
+        description = "Mastodon package to use.";
+      };
+
+      extraConfig = lib.mkOption {
+        type = lib.types.attrs;
+        default = {};
+        description = ''
+          Extra environment variables to pass to all mastodon services.
+        '';
+      };
+
+      automaticMigrations = lib.mkOption {
+        type = lib.types.bool;
+        default = true;
+        description = ''
+          Do automatic database migrations.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = databaseActuallyCreateLocally -> (cfg.user == cfg.database.user);
+        message = ''For local automatic database provisioning (services.mastodon.database.createLocally == true) with peer authentication (services.mastodon.database.host == "/run/postgresql") to work services.mastodon.user and services.mastodon.database.user must be identical.'';
+      }
+    ];
+
+    systemd.services.mastodon-init-dirs = {
+      script = ''
+        umask 077
+
+        if ! test -f ${cfg.secretKeyBaseFile}; then
+          mkdir -p $(dirname ${cfg.secretKeyBaseFile})
+          bin/rake secret > ${cfg.secretKeyBaseFile}
+        fi
+        if ! test -f ${cfg.otpSecretFile}; then
+          mkdir -p $(dirname ${cfg.otpSecretFile})
+          bin/rake secret > ${cfg.otpSecretFile}
+        fi
+        if ! test -f ${cfg.vapidPrivateKeyFile}; then
+          mkdir -p $(dirname ${cfg.vapidPrivateKeyFile}) $(dirname ${cfg.vapidPublicKeyFile})
+          keypair=$(bin/rake webpush:generate_keys)
+          echo $keypair | grep --only-matching "Private -> [^ ]\+" | sed 's/^Private -> //' > ${cfg.vapidPrivateKeyFile}
+          echo $keypair | grep --only-matching "Public -> [^ ]\+" | sed 's/^Public -> //' > ${cfg.vapidPublicKeyFile}
+        fi
+
+        cat > /var/lib/mastodon/.secrets_env <<EOF
+        SECRET_KEY_BASE="$(cat ${cfg.secretKeyBaseFile})"
+        OTP_SECRET="$(cat ${cfg.otpSecretFile})"
+        VAPID_PRIVATE_KEY="$(cat ${cfg.vapidPrivateKeyFile})"
+        VAPID_PUBLIC_KEY="$(cat ${cfg.vapidPublicKeyFile})"
+        DB_PASS="$(cat ${cfg.database.passwordFile})"
+      '' + (if cfg.smtp.authenticate then ''
+        SMTP_PASSWORD="$(cat ${cfg.smtp.passwordFile})"
+      '' else "") + ''
+        EOF
+      '';
+      environment = env;
+      serviceConfig = {
+        Type = "oneshot";
+        User = cfg.user;
+        Group = cfg.group;
+        WorkingDirectory = cfg.package;
+        LogsDirectory = "mastodon";
+        StateDirectory = "mastodon";
+      };
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+    };
+
+    systemd.services.mastodon-init-db = lib.mkIf cfg.automaticMigrations {
+      script = ''
+        if [ `psql mastodon -c \
+                "select count(*) from pg_class c \
+                join pg_namespace s on s.oid = c.relnamespace \
+                where s.nspname not in ('pg_catalog', 'pg_toast', 'information_schema') \
+                and s.nspname not like 'pg_temp%';" | sed -n 3p` -eq 0 ]; then
+          SAFETY_ASSURED=1 rake db:schema:load
+          rake db:seed
+        else
+          rake db:migrate
+        fi
+      '';
+      path = [ cfg.package pkgs.postgresql ];
+      environment = env;
+      serviceConfig = {
+        Type = "oneshot";
+        User = cfg.user;
+        Group = cfg.group;
+        EnvironmentFile = "/var/lib/mastodon/.secrets_env";
+        PrivateTmp = true;
+        LogsDirectory = "mastodon";
+        StateDirectory = "mastodon";
+        WorkingDirectory = cfg.package;
+      };
+      after = [ "mastodon-init-dirs.service" "network.target" ] ++ (if databaseActuallyCreateLocally then [ "postgresql.service" ] else []);
+      wantedBy = [ "multi-user.target" ];
+    };
+
+    systemd.services.mastodon-streaming = {
+      after = [ "network.target" ]
+        ++ (if databaseActuallyCreateLocally then [ "postgresql.service" ] else [])
+        ++ (if cfg.automaticMigrations then [ "mastodon-init-db.service" ] else [ "mastodon-init-dirs.service" ]);
+      description = "Mastodon streaming";
+      wantedBy = [ "multi-user.target" ];
+      environment = env // {
+        PORT = toString(cfg.streamingPort);
+      };
+      serviceConfig = {
+        ExecStart = "${pkgs.nodejs-slim}/bin/node streaming";
+        Restart = "always";
+        RestartSec = 20;
+        User = cfg.user;
+        Group = cfg.group;
+        WorkingDirectory = cfg.package;
+        EnvironmentFile = "/var/lib/mastodon/.secrets_env";
+        PrivateTmp = true;
+        LogsDirectory = "mastodon";
+        StateDirectory = "mastodon";
+      };
+    };
+
+    systemd.services.mastodon-web = {
+      after = [ "network.target" ]
+        ++ (if databaseActuallyCreateLocally then [ "postgresql.service" ] else [])
+        ++ (if cfg.automaticMigrations then [ "mastodon-init-db.service" ] else [ "mastodon-init-dirs.service" ]);
+      description = "Mastodon web";
+      wantedBy = [ "multi-user.target" ];
+      environment = env // {
+        PORT = toString(cfg.webPort);
+      };
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/puma -C config/puma.rb";
+        Restart = "always";
+        RestartSec = 20;
+        User = cfg.user;
+        Group = cfg.group;
+        WorkingDirectory = cfg.package;
+        EnvironmentFile = "/var/lib/mastodon/.secrets_env";
+        PrivateTmp = true;
+        LogsDirectory = "mastodon";
+        StateDirectory = "mastodon";
+      };
+      path = with pkgs; [ file imagemagick ffmpeg ];
+    };
+
+    systemd.services.mastodon-sidekiq = {
+      after = [ "network.target" ]
+        ++ (if databaseActuallyCreateLocally then [ "postgresql.service" ] else [])
+        ++ (if cfg.automaticMigrations then [ "mastodon-init-db.service" ] else [ "mastodon-init-dirs.service" ]);
+      description = "Mastodon sidekiq";
+      wantedBy = [ "multi-user.target" ];
+      environment = env // {
+        PORT = toString(cfg.sidekiqPort);
+      };
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/sidekiq -c 25 -r ${cfg.package}";
+        Restart = "always";
+        RestartSec = 20;
+        User = cfg.user;
+        Group = cfg.group;
+        WorkingDirectory = cfg.package;
+        EnvironmentFile = "/var/lib/mastodon/.secrets_env";
+        PrivateTmp = true;
+        LogsDirectory = "mastodon";
+        StateDirectory = "mastodon";
+      };
+      path = with pkgs; [ file imagemagick ffmpeg ];
+    };
+
+    services.nginx = lib.mkIf cfg.configureNginx {
+      enable = true;
+      recommendedProxySettings = true; # required for redirections to work
+      virtualHosts."${cfg.localDomain}" = {
+        root = "${cfg.package}/public/";
+        forceSSL = true; # mastodon only supports https
+        enableACME = true;
+
+        locations."/system/".alias = "/var/lib/mastodon/public-system/";
+
+        locations."/" = {
+          tryFiles = "$uri @proxy";
+        };
+
+        locations."@proxy" = {
+          proxyPass = "http://127.0.0.1:${toString(cfg.webPort)}";
+          proxyWebsockets = true;
+        };
+
+        locations."/api/v1/streaming/" = {
+          proxyPass = "http://127.0.0.1:${toString(cfg.streamingPort)}/";
+          proxyWebsockets = true;
+        };
+      };
+    };
+
+    services.postfix = lib.mkIf (cfg.smtp.createLocally && cfg.smtp.host == "127.0.0.1") {
+      enable = true;
+    };
+    services.redis = lib.mkIf (cfg.redis.createLocally && cfg.redis.host == "127.0.0.1") {
+      enable = true;
+    };
+    services.postgresql = lib.mkIf databaseActuallyCreateLocally {
+      enable = true;
+      ensureUsers = [
+        {
+          name = cfg.database.user;
+          ensurePermissions."DATABASE ${cfg.database.name}" = "ALL PRIVILEGES";
+        }
+      ];
+      ensureDatabases = [ cfg.database.name ];
+    };
+
+    users.users = lib.mkMerge [
+      (lib.mkIf (cfg.user == "mastodon") {
+        mastodon = {
+          isSystemUser = true;
+          home = cfg.package;
+          inherit (cfg) group;
+        };
+      })
+      (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ cfg.package mastodonEnv ])
+    ];
+
+    users.groups.mastodon = lib.mkIf (cfg.group == "mastodon") { };
+  };
+
+  meta.maintainers = with lib.maintainers; [ happy-river erictapen ];
+
+}
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
index 1b643bd3260..60d40355335 100644
--- a/nixos/modules/services/web-apps/nextcloud.nix
+++ b/nixos/modules/services/web-apps/nextcloud.nix
@@ -244,7 +244,8 @@ in {
         type = types.nullOr types.str;
         default = null;
         description = ''
-          The full path to a file that contains the admin's password.
+          The full path to a file that contains the admin's password. Must be
+          readable by user <literal>nextcloud</literal>.
         '';
       };
 
@@ -367,7 +368,7 @@ in {
         '')
         ++ (optional (versionOlder cfg.package.version "18") (upgradeWarning 17 "20.03"))
         ++ (optional (versionOlder cfg.package.version "19") (upgradeWarning 18 "20.09"))
-        ++ (optional (versionOlder cfg.package.version "20") (upgradeWarning 19 "21.03"));
+        ++ (optional (versionOlder cfg.package.version "20") (upgradeWarning 19 "21.05"));
 
       services.nextcloud.package = with pkgs;
         mkDefault (
@@ -379,6 +380,10 @@ in {
             ''
           else if versionOlder stateVersion "20.03" then nextcloud17
           else if versionOlder stateVersion "20.09" then nextcloud18
+          # 21.03 will not be an official release - it was instead 21.05.
+          # This versionOlder statement remains set to 21.03 for backwards compatibility.
+          # See https://github.com/NixOS/nixpkgs/pull/108899 and
+          # https://github.com/NixOS/rfcs/blob/master/rfcs/0080-nixos-release-schedule.md.
           else if versionOlder stateVersion "21.03" then nextcloud19
           else nextcloud20
         );
@@ -482,6 +487,28 @@ in {
           path = [ occ ];
           script = ''
             chmod og+x ${cfg.home}
+
+            ${optionalString (c.dbpassFile != null) ''
+              if [ ! -r "${c.dbpassFile}" ]; then
+                echo "dbpassFile ${c.dbpassFile} is not readable by nextcloud:nextcloud! Aborting..."
+                exit 1
+              fi
+              if [ -z "$(<${c.dbpassFile})" ]; then
+                echo "dbpassFile ${c.dbpassFile} is empty!"
+                exit 1
+              fi
+            ''}
+            ${optionalString (c.adminpassFile != null) ''
+              if [ ! -r "${c.adminpassFile}" ]; then
+                echo "adminpassFile ${c.adminpassFile} is not readable by nextcloud:nextcloud! Aborting..."
+                exit 1
+              fi
+              if [ -z "$(<${c.adminpassFile})" ]; then
+                echo "adminpassFile ${c.adminpassFile} is empty!"
+                exit 1
+              fi
+            ''}
+
             ln -sf ${cfg.package}/apps ${cfg.home}/
 
             # create nextcloud directories.
diff --git a/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixos/modules/services/web-servers/apache-httpd/default.nix
index de3c7d693d4..7f50b8fd8d4 100644
--- a/nixos/modules/services/web-servers/apache-httpd/default.nix
+++ b/nixos/modules/services/web-servers/apache-httpd/default.nix
@@ -126,10 +126,14 @@ let
     </IfModule>
   '';
 
-  luaSetPaths = ''
+  luaSetPaths = let
+    # support both lua and lua.withPackages derivations
+    luaversion = cfg.package.lua5.lua.luaversion or cfg.package.lua5.luaversion;
+    in
+  ''
     <IfModule mod_lua.c>
-      LuaPackageCPath ${cfg.package.lua5}/lib/lua/${cfg.package.lua5.lua.luaversion}/?.so
-      LuaPackagePath  ${cfg.package.lua5}/share/lua/${cfg.package.lua5.lua.luaversion}/?.lua
+      LuaPackageCPath ${cfg.package.lua5}/lib/lua/${luaversion}/?.so
+      LuaPackagePath  ${cfg.package.lua5}/share/lua/${luaversion}/?.lua
     </IfModule>
   '';
 
@@ -333,7 +337,7 @@ let
 
     ${sslConf}
 
-    ${if cfg.package.luaSupport then luaSetPaths else ""}
+    ${optionalString cfg.package.luaSupport luaSetPaths}
 
     # Fascist default - deny access to everything.
     <Directory />
diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix
index 29cb7cedcb0..fa8614e8ec1 100644
--- a/nixos/modules/services/web-servers/nginx/default.nix
+++ b/nixos/modules/services/web-servers/nginx/default.nix
@@ -79,6 +79,8 @@ let
       include ${pkgs.mailcap}/etc/nginx/mime.types;
       include ${cfg.package}/conf/fastcgi.conf;
       include ${cfg.package}/conf/uwsgi_params;
+
+      default_type application/octet-stream;
   '';
 
   configFile = pkgs.writers.writeNginxConfig "nginx.conf" ''
diff --git a/nixos/modules/services/x11/window-managers/herbstluftwm.nix b/nixos/modules/services/x11/window-managers/herbstluftwm.nix
index e3ea61cb9a6..548097a412d 100644
--- a/nixos/modules/services/x11/window-managers/herbstluftwm.nix
+++ b/nixos/modules/services/x11/window-managers/herbstluftwm.nix
@@ -11,6 +11,15 @@ in
     services.xserver.windowManager.herbstluftwm = {
       enable = mkEnableOption "herbstluftwm";
 
+      package = mkOption {
+        type = types.package;
+        default = pkgs.herbstluftwm;
+        defaultText = "pkgs.herbstluftwm";
+        description = ''
+          Herbstluftwm package to use.
+        '';
+      };
+
       configFile = mkOption {
         default     = null;
         type        = with types; nullOr path;
@@ -31,8 +40,8 @@ in
             (cfg.configFile != null)
             ''-c "${cfg.configFile}"''
             ;
-        in "${pkgs.herbstluftwm}/bin/herbstluftwm ${configFileClause}";
+        in "${cfg.package}/bin/herbstluftwm ${configFileClause}";
     };
-    environment.systemPackages = [ pkgs.herbstluftwm ];
+    environment.systemPackages = [ cfg.package ];
   };
 }
diff --git a/nixos/modules/services/x11/window-managers/xmonad.nix b/nixos/modules/services/x11/window-managers/xmonad.nix
index 1ad0bb9ce67..fe8ed381251 100644
--- a/nixos/modules/services/x11/window-managers/xmonad.nix
+++ b/nixos/modules/services/x11/window-managers/xmonad.nix
@@ -43,7 +43,6 @@ in {
       haskellPackages = mkOption {
         default = pkgs.haskellPackages;
         defaultText = "pkgs.haskellPackages";
-        type = types.package;
         example = literalExample "pkgs.haskell.packages.ghc784";
         description = ''
           haskellPackages used to build Xmonad and other packages.
diff --git a/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix
index e89ad9a5796..8858559d8f2 100644
--- a/nixos/modules/services/x11/xserver.nix
+++ b/nixos/modules/services/x11/xserver.nix
@@ -650,7 +650,7 @@ in
         xorg.xprop
         xorg.xauth
         pkgs.xterm
-        pkgs.xdg_utils
+        pkgs.xdg-utils
         xorg.xf86inputevdev.out # get evdev.4 man page
       ]
       ++ optional (elem "virtualbox" cfg.videoDrivers) xorg.xrefresh;