about summary refs log tree commit diff
path: root/nixos/modules/services
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/services')
-rw-r--r--nixos/modules/services/accessibility/speechd.nix32
-rw-r--r--nixos/modules/services/audio/alsa.nix144
-rw-r--r--nixos/modules/services/audio/goxlr-utility.nix25
-rw-r--r--nixos/modules/services/audio/jack.nix4
-rw-r--r--nixos/modules/services/audio/jmusicbot.nix2
-rw-r--r--nixos/modules/services/audio/music-assistant.nix113
-rw-r--r--nixos/modules/services/audio/snapserver.nix1
-rw-r--r--nixos/modules/services/backup/borgmatic.nix57
-rw-r--r--nixos/modules/services/backup/btrbk.nix4
-rw-r--r--nixos/modules/services/backup/duplicity.nix24
-rw-r--r--nixos/modules/services/backup/mysql-backup.nix10
-rw-r--r--nixos/modules/services/backup/restic-rest-server.nix2
-rw-r--r--nixos/modules/services/backup/restic.nix22
-rw-r--r--nixos/modules/services/backup/tsm.nix2
-rw-r--r--nixos/modules/services/blockchain/ethereum/lighthouse.nix2
-rw-r--r--nixos/modules/services/cluster/druid/default.nix296
-rw-r--r--nixos/modules/services/cluster/k3s/default.nix413
-rw-r--r--nixos/modules/services/cluster/kubernetes/apiserver.nix8
-rw-r--r--nixos/modules/services/cluster/kubernetes/controller-manager.nix8
-rw-r--r--nixos/modules/services/cluster/kubernetes/default.nix8
-rw-r--r--nixos/modules/services/cluster/kubernetes/kubelet.nix6
-rw-r--r--nixos/modules/services/cluster/kubernetes/proxy.nix8
-rw-r--r--nixos/modules/services/cluster/kubernetes/scheduler.nix8
-rw-r--r--nixos/modules/services/cluster/patroni/default.nix36
-rw-r--r--nixos/modules/services/cluster/rke2/default.nix2
-rw-r--r--nixos/modules/services/continuous-integration/buildkite-agents.nix2
-rw-r--r--nixos/modules/services/continuous-integration/gitlab-runner.nix26
-rw-r--r--nixos/modules/services/databases/monetdb.nix2
-rw-r--r--nixos/modules/services/databases/redis.nix9
-rw-r--r--nixos/modules/services/databases/surrealdb.nix2
-rw-r--r--nixos/modules/services/desktop-managers/lomiri.nix23
-rw-r--r--nixos/modules/services/desktop-managers/plasma6.nix4
-rw-r--r--nixos/modules/services/desktops/ayatana-indicators.nix40
-rw-r--r--nixos/modules/services/desktops/espanso.nix2
-rw-r--r--nixos/modules/services/desktops/flatpak.nix10
-rw-r--r--nixos/modules/services/development/livebook.nix12
-rw-r--r--nixos/modules/services/display-managers/default.nix3
-rw-r--r--nixos/modules/services/display-managers/ly.nix147
-rw-r--r--nixos/modules/services/finance/odoo.nix46
-rw-r--r--nixos/modules/services/games/archisteamfarm.nix7
-rw-r--r--nixos/modules/services/games/terraria.nix23
-rw-r--r--nixos/modules/services/hardware/display.md130
-rw-r--r--nixos/modules/services/hardware/display.nix193
-rw-r--r--nixos/modules/services/hardware/nvidia-container-toolkit/cdi-generate.nix2
-rw-r--r--nixos/modules/services/hardware/nvidia-container-toolkit/default.nix12
-rw-r--r--nixos/modules/services/hardware/openrgb.nix2
-rw-r--r--nixos/modules/services/hardware/sane.nix4
-rw-r--r--nixos/modules/services/hardware/udev.nix5
-rw-r--r--nixos/modules/services/home-automation/home-assistant.nix7
-rw-r--r--nixos/modules/services/logging/graylog.nix7
-rw-r--r--nixos/modules/services/mail/dovecot.nix2
-rw-r--r--nixos/modules/services/mail/mailman.nix5
-rw-r--r--nixos/modules/services/mail/postfix.nix2
-rw-r--r--nixos/modules/services/mail/protonmail-bridge.nix60
-rw-r--r--nixos/modules/services/mail/roundcube.nix12
-rw-r--r--nixos/modules/services/misc/airsonic.nix2
-rw-r--r--nixos/modules/services/misc/ananicy.nix212
-rw-r--r--nixos/modules/services/misc/blenderfarm.nix141
-rw-r--r--nixos/modules/services/misc/dictd.nix3
-rw-r--r--nixos/modules/services/misc/etebase-server.nix2
-rw-r--r--nixos/modules/services/misc/flaresolverr.nix58
-rw-r--r--nixos/modules/services/misc/forgejo.nix2
-rw-r--r--nixos/modules/services/misc/fstrim.nix2
-rw-r--r--nixos/modules/services/misc/gitlab.nix37
-rw-r--r--nixos/modules/services/misc/gitweb.nix2
-rw-r--r--nixos/modules/services/misc/gollum.nix47
-rw-r--r--nixos/modules/services/misc/gotenberg.nix258
-rw-r--r--nixos/modules/services/misc/graphical-desktop.nix2
-rw-r--r--nixos/modules/services/misc/jackett.nix12
-rw-r--r--nixos/modules/services/misc/jellyseerr.nix7
-rw-r--r--nixos/modules/services/misc/languagetool.nix43
-rw-r--r--nixos/modules/services/misc/mame.nix2
-rw-r--r--nixos/modules/services/misc/ollama.nix206
-rw-r--r--nixos/modules/services/misc/open-webui.nix5
-rw-r--r--nixos/modules/services/misc/private-gpt.nix2
-rw-r--r--nixos/modules/services/misc/radicle.nix358
-rw-r--r--nixos/modules/services/misc/redlib.nix (renamed from nixos/modules/services/misc/libreddit.nix)14
-rw-r--r--nixos/modules/services/misc/rkvm.nix2
-rw-r--r--nixos/modules/services/misc/snapper.nix26
-rw-r--r--nixos/modules/services/misc/sonarr.nix8
-rw-r--r--nixos/modules/services/misc/sssd.nix2
-rw-r--r--nixos/modules/services/misc/xmr-stak.nix89
-rw-r--r--nixos/modules/services/misc/zoneminder.nix7
-rw-r--r--nixos/modules/services/monitoring/librenms.nix127
-rw-r--r--nixos/modules/services/monitoring/nagios.nix4
-rw-r--r--nixos/modules/services/monitoring/netdata.nix3
-rw-r--r--nixos/modules/services/monitoring/opentelemetry-collector.nix47
-rw-r--r--nixos/modules/services/monitoring/prometheus/alertmanager-webhook-logger.nix11
-rw-r--r--nixos/modules/services/monitoring/prometheus/alertmanager.nix52
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters.nix17
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/borgmatic.nix34
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/deluge.nix85
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/nginxlog.nix22
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/pve.nix8
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/smartctl.nix14
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/snmp.nix30
-rw-r--r--nixos/modules/services/monitoring/prometheus/pushgateway.nix44
-rw-r--r--nixos/modules/services/monitoring/scrutiny.nix5
-rw-r--r--nixos/modules/services/networking/antennas.nix2
-rw-r--r--nixos/modules/services/networking/bee.nix5
-rw-r--r--nixos/modules/services/networking/bind.nix3
-rw-r--r--nixos/modules/services/networking/cgit.nix4
-rw-r--r--nixos/modules/services/networking/cloudflare-dyndns.nix17
-rw-r--r--nixos/modules/services/networking/cloudflare-warp.nix91
-rw-r--r--nixos/modules/services/networking/ddns-updater.nix46
-rw-r--r--nixos/modules/services/networking/deconz.nix3
-rw-r--r--nixos/modules/services/networking/eternal-terminal.nix2
-rw-r--r--nixos/modules/services/networking/firefox-syncserver.nix2
-rw-r--r--nixos/modules/services/networking/hans.nix2
-rw-r--r--nixos/modules/services/networking/hickory-dns.nix (renamed from nixos/modules/services/networking/trust-dns.nix)39
-rw-r--r--nixos/modules/services/networking/i2pd.nix68
-rw-r--r--nixos/modules/services/networking/magic-wormhole-mailbox-server.nix24
-rw-r--r--nixos/modules/services/networking/mosquitto.nix4
-rw-r--r--nixos/modules/services/networking/mxisd.nix137
-rw-r--r--nixos/modules/services/networking/nar-serve.nix15
-rw-r--r--nixos/modules/services/networking/nats.nix9
-rw-r--r--nixos/modules/services/networking/nebula.nix8
-rw-r--r--nixos/modules/services/networking/opengfw.nix414
-rw-r--r--nixos/modules/services/networking/openvpn.nix2
-rw-r--r--nixos/modules/services/networking/pppd.nix2
-rw-r--r--nixos/modules/services/networking/prosody.nix57
-rw-r--r--nixos/modules/services/networking/rathole.nix165
-rw-r--r--nixos/modules/services/networking/realm.nix50
-rw-r--r--nixos/modules/services/networking/resilio.nix2
-rw-r--r--nixos/modules/services/networking/scion/scion-control.nix12
-rw-r--r--nixos/modules/services/networking/scion/scion-daemon.nix10
-rw-r--r--nixos/modules/services/networking/scion/scion-dispatcher.nix5
-rw-r--r--nixos/modules/services/networking/scion/scion-router.nix5
-rw-r--r--nixos/modules/services/networking/scion/scion.nix19
-rw-r--r--nixos/modules/services/networking/smokeping.nix4
-rw-r--r--nixos/modules/services/networking/spiped.nix20
-rw-r--r--nixos/modules/services/networking/wg-quick.nix25
-rw-r--r--nixos/modules/services/networking/wireguard.nix36
-rw-r--r--nixos/modules/services/networking/wpa_supplicant.nix14
-rw-r--r--nixos/modules/services/networking/wstunnel.nix4
-rw-r--r--nixos/modules/services/networking/wvdial.nix47
-rw-r--r--nixos/modules/services/networking/zeronsd.nix117
-rw-r--r--nixos/modules/services/search/tika.nix98
-rw-r--r--nixos/modules/services/security/authelia.nix48
-rw-r--r--nixos/modules/services/security/clamav.nix10
-rw-r--r--nixos/modules/services/security/hologram-agent.nix2
-rw-r--r--nixos/modules/services/security/shibboleth-sp.nix2
-rw-r--r--nixos/modules/services/security/sks.nix2
-rw-r--r--nixos/modules/services/security/step-ca.nix2
-rw-r--r--nixos/modules/services/security/vaultwarden/default.nix20
-rw-r--r--nixos/modules/services/ttys/kmscon.nix2
-rw-r--r--nixos/modules/services/video/frigate.nix2
-rw-r--r--nixos/modules/services/video/replay-sorcery.nix72
-rw-r--r--nixos/modules/services/web-apps/akkoma.nix6
-rw-r--r--nixos/modules/services/web-apps/cryptpad.nix293
-rw-r--r--nixos/modules/services/web-apps/eintopf.nix92
-rw-r--r--nixos/modules/services/web-apps/freshrss.nix5
-rw-r--r--nixos/modules/services/web-apps/glance.md39
-rw-r--r--nixos/modules/services/web-apps/glance.nix141
-rw-r--r--nixos/modules/services/web-apps/goatcounter.nix80
-rw-r--r--nixos/modules/services/web-apps/gotify-server.nix95
-rw-r--r--nixos/modules/services/web-apps/ifm.nix81
-rw-r--r--nixos/modules/services/web-apps/jitsi-meet.nix14
-rw-r--r--nixos/modules/services/web-apps/mealie.nix1
-rw-r--r--nixos/modules/services/web-apps/mediawiki.nix196
-rw-r--r--nixos/modules/services/web-apps/meme-bingo-web.nix6
-rw-r--r--nixos/modules/services/web-apps/miniflux.nix12
-rw-r--r--nixos/modules/services/web-apps/misskey.nix418
-rw-r--r--nixos/modules/services/web-apps/movim.nix4
-rw-r--r--nixos/modules/services/web-apps/nextcloud.nix6
-rw-r--r--nixos/modules/services/web-apps/onlyoffice.nix58
-rw-r--r--nixos/modules/services/web-apps/pretix.nix10
-rw-r--r--nixos/modules/services/web-apps/screego.nix96
-rw-r--r--nixos/modules/services/web-apps/sogo.nix8
-rw-r--r--nixos/modules/services/web-apps/stirling-pdf.nix110
-rw-r--r--nixos/modules/services/web-apps/weblate.nix388
-rw-r--r--nixos/modules/services/web-apps/wordpress.nix2
-rw-r--r--nixos/modules/services/web-apps/zabbix.nix252
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/default.nix18
-rw-r--r--nixos/modules/services/web-servers/caddy/default.nix8
-rw-r--r--nixos/modules/services/web-servers/fcgiwrap.nix32
-rw-r--r--nixos/modules/services/web-servers/hydron.nix164
-rw-r--r--nixos/modules/services/web-servers/nginx/gitweb.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/budgie.nix30
-rw-r--r--nixos/modules/services/x11/desktop-managers/cinnamon.nix28
-rw-r--r--nixos/modules/services/x11/desktop-managers/enlightenment.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/plasma5.nix2
-rw-r--r--nixos/modules/services/x11/display-managers/gdm.nix5
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm-greeters/slick.nix4
-rw-r--r--nixos/modules/services/x11/xserver.nix3
185 files changed, 6982 insertions, 1572 deletions
diff --git a/nixos/modules/services/accessibility/speechd.nix b/nixos/modules/services/accessibility/speechd.nix
new file mode 100644
index 0000000000000..165be86346ccb
--- /dev/null
+++ b/nixos/modules/services/accessibility/speechd.nix
@@ -0,0 +1,32 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+let
+  cfg = config.services.speechd;
+  inherit (lib)
+    getExe
+    mkEnableOption
+    mkIf
+    mkPackageOption
+    ;
+in
+{
+  options.services.speechd = {
+    # FIXME: figure out how to deprecate this EXTREMELY CAREFULLY
+    # default guessed conservatively in ../misc/graphical-desktop.nix
+    enable = mkEnableOption "speech-dispatcher speech synthesizer daemon";
+    package = mkPackageOption pkgs "speechd" { };
+  };
+
+  # FIXME: speechd 0.12 (or whatever the next version is)
+  # will support socket activation, so switch to that once it's out.
+  config = mkIf cfg.enable {
+    environment = {
+      systemPackages = [ cfg.package ];
+      sessionVariables.SPEECHD_CMD = getExe cfg.package;
+    };
+  };
+}
diff --git a/nixos/modules/services/audio/alsa.nix b/nixos/modules/services/audio/alsa.nix
index b002cb1274ac3..fac0012b666d4 100644
--- a/nixos/modules/services/audio/alsa.nix
+++ b/nixos/modules/services/audio/alsa.nix
@@ -1,134 +1,38 @@
 # ALSA sound support.
 { config, lib, pkgs, ... }:
 
-with lib;
-
-let
-
-  inherit (pkgs) alsa-utils;
-
-  pulseaudioEnabled = config.hardware.pulseaudio.enable;
-
-in
-
 {
   imports = [
-    (mkRenamedOptionModule [ "sound" "enableMediaKeys" ] [ "sound" "mediaKeys" "enable" ])
+    (lib.mkRemovedOptionModule [ "sound" ] "The option was heavily overloaded and can be removed from most configurations.")
   ];
 
-  ###### interface
-
-  options = {
-
-    sound = {
-
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to enable ALSA sound.
-        '';
-      };
-
-      enableOSSEmulation = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to enable ALSA OSS emulation (with certain cards sound mixing may not work!).
-        '';
-      };
-
-      extraConfig = mkOption {
-        type = types.lines;
-        default = "";
-        example = ''
-          defaults.pcm.!card 3
-        '';
-        description = ''
-          Set addition configuration for system-wide alsa.
-        '';
-      };
-
-      mediaKeys = {
-
-        enable = mkOption {
-          type = types.bool;
-          default = false;
-          description = ''
-            Whether to enable volume and capture control with keyboard media keys.
-
-            You want to leave this disabled if you run a desktop environment
-            like KDE, Gnome, Xfce, etc, as those handle such things themselves.
-            You might want to enable this if you run a minimalistic desktop
-            environment or work from bare linux ttys/framebuffers.
-
-            Enabling this will turn on {option}`services.actkbd`.
-          '';
-        };
-
-        volumeStep = mkOption {
-          type = types.str;
-          default = "1";
-          example = "1%";
-          description = ''
-            The value by which to increment/decrement volume on media keys.
-
-            See amixer(1) for allowed values.
-          '';
-        };
-
-      };
-
-    };
-
+  options.hardware.alsa.enablePersistence = lib.mkOption {
+    type = lib.types.bool;
+    default = false;
+    description = ''
+      Whether to enable ALSA sound card state saving on shutdown.
+      This is generally not necessary if you're using an external sound server.
+    '';
   };
 
-
-  ###### implementation
-
-  config = mkIf config.sound.enable {
-
-    environment.systemPackages = [ alsa-utils ];
-
-    environment.etc = mkIf (!pulseaudioEnabled && config.sound.extraConfig != "")
-      { "asound.conf".text = config.sound.extraConfig; };
-
+  config = lib.mkIf config.hardware.alsa.enablePersistence {
     # ALSA provides a udev rule for restoring volume settings.
-    services.udev.packages = [ alsa-utils ];
-
-    boot.kernelModules = optional config.sound.enableOSSEmulation "snd_pcm_oss";
-
-    systemd.services.alsa-store =
-      { description = "Store Sound Card State";
-        wantedBy = [ "multi-user.target" ];
-        unitConfig.RequiresMountsFor = "/var/lib/alsa";
-        unitConfig.ConditionVirtualization = "!systemd-nspawn";
-        serviceConfig = {
-          Type = "oneshot";
-          RemainAfterExit = true;
-          ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p /var/lib/alsa";
-          ExecStart = "${alsa-utils}/sbin/alsactl restore --ignore";
-          ExecStop = "${alsa-utils}/sbin/alsactl store --ignore";
-        };
+    services.udev.packages = [ pkgs.alsa-utils ];
+
+    systemd.services.alsa-store = {
+      description = "Store Sound Card State";
+      wantedBy = [ "multi-user.target" ];
+      unitConfig = {
+        RequiresMountsFor = "/var/lib/alsa";
+        ConditionVirtualization = "!systemd-nspawn";
+      };
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p /var/lib/alsa";
+        ExecStart = "${pkgs.alsa-utils}/sbin/alsactl restore --ignore";
+        ExecStop = "${pkgs.alsa-utils}/sbin/alsactl store --ignore";
       };
-
-    services.actkbd = mkIf config.sound.mediaKeys.enable {
-      enable = true;
-      bindings = [
-        # "Mute" media key
-        { keys = [ 113 ]; events = [ "key" ];       command = "${alsa-utils}/bin/amixer -q set Master toggle"; }
-
-        # "Lower Volume" media key
-        { keys = [ 114 ]; events = [ "key" "rep" ]; command = "${alsa-utils}/bin/amixer -q set Master ${config.sound.mediaKeys.volumeStep}- unmute"; }
-
-        # "Raise Volume" media key
-        { keys = [ 115 ]; events = [ "key" "rep" ]; command = "${alsa-utils}/bin/amixer -q set Master ${config.sound.mediaKeys.volumeStep}+ unmute"; }
-
-        # "Mic Mute" media key
-        { keys = [ 190 ]; events = [ "key" ];       command = "${alsa-utils}/bin/amixer -q set Capture toggle"; }
-      ];
     };
-
   };
-
 }
diff --git a/nixos/modules/services/audio/goxlr-utility.nix b/nixos/modules/services/audio/goxlr-utility.nix
index 6081b3707f54d..00aaa77a24d59 100644
--- a/nixos/modules/services/audio/goxlr-utility.nix
+++ b/nixos/modules/services/audio/goxlr-utility.nix
@@ -28,19 +28,30 @@ with lib;
     };
   };
 
-  config = mkIf config.services.goxlr-utility.enable
-    {
+  config =
+    let
+      goxlr-autostart = pkgs.stdenv.mkDerivation {
+        name = "autostart-goxlr-daemon";
+        priority = 5;
+
+        buildCommand = ''
+          mkdir -p $out/etc/xdg/autostart
+          cp ${cfg.package}/share/applications/goxlr-utility.desktop $out/etc/xdg/autostart/goxlr-daemon.desktop
+          chmod +w $out/etc/xdg/autostart/goxlr-daemon.desktop
+          echo "X-KDE-autostart-phase=2" >> $out/etc/xdg/autostart/goxlr-daemon.desktop
+          substituteInPlace $out/etc/xdg/autostart/goxlr-daemon.desktop \
+            --replace-fail goxlr-launcher goxlr-daemon
+        '';
+      };
+    in
+    mkIf config.services.goxlr-utility.enable {
       services.udev.packages = [ cfg.package ];
 
       xdg.autostart.enable = mkIf cfg.autoStart.xdg true;
       environment.systemPackages = mkIf cfg.autoStart.xdg
         [
           cfg.package
-          (pkgs.makeAutostartItem
-            {
-              name = "goxlr-utility";
-              package = cfg.package;
-            })
+          goxlr-autostart
         ];
     };
 
diff --git a/nixos/modules/services/audio/jack.nix b/nixos/modules/services/audio/jack.nix
index 20ba091542fe3..a5cb0da29592e 100644
--- a/nixos/modules/services/audio/jack.nix
+++ b/nixos/modules/services/audio/jack.nix
@@ -122,7 +122,7 @@ in {
   config = mkMerge [
 
     (mkIf pcmPlugin {
-      sound.extraConfig = ''
+      environment.etc."alsa/conf.d/98-jack.conf".text = ''
         pcm_type.jack {
           libs.native = ${pkgs.alsa-plugins}/lib/alsa-lib/libasound_module_pcm_jack.so ;
           ${lib.optionalString enable32BitAlsaPlugins
@@ -139,7 +139,7 @@ in {
     (mkIf loopback {
       boot.kernelModules = [ "snd-aloop" ];
       boot.kernelParams = [ "snd-aloop.index=${toString cfg.loopback.index}" ];
-      sound.extraConfig = cfg.loopback.config;
+      environment.etc."alsa/conf.d/99-jack-loopback.conf".text = cfg.loopback.config;
     })
 
     (mkIf cfg.jackd.enable {
diff --git a/nixos/modules/services/audio/jmusicbot.nix b/nixos/modules/services/audio/jmusicbot.nix
index 5507f4859058f..d1317facf273f 100644
--- a/nixos/modules/services/audio/jmusicbot.nix
+++ b/nixos/modules/services/audio/jmusicbot.nix
@@ -40,5 +40,5 @@ in
     };
   };
 
-  meta.maintainers = with maintainers; [ ];
+  meta.maintainers = [ ];
 }
diff --git a/nixos/modules/services/audio/music-assistant.nix b/nixos/modules/services/audio/music-assistant.nix
new file mode 100644
index 0000000000000..90c0b41fc587f
--- /dev/null
+++ b/nixos/modules/services/audio/music-assistant.nix
@@ -0,0 +1,113 @@
+{
+  config,
+  lib,
+  pkgs,
+  utils,
+  ...
+}:
+
+let
+  inherit (lib)
+    mkIf
+    mkEnableOption
+    mkOption
+    mkPackageOption
+    types
+  ;
+
+  inherit (types)
+    listOf
+    enum
+    str
+  ;
+
+  cfg = config.services.music-assistant;
+
+  finalPackage = cfg.package.override {
+    inherit (cfg) providers;
+  };
+in
+
+{
+  meta.buildDocsInSandbox = false;
+
+  options.services.music-assistant = {
+    enable = mkEnableOption "Music Assistant";
+
+    package = mkPackageOption pkgs "music-assistant" { };
+
+    extraOptions = mkOption {
+      type = listOf str;
+      default = [ "--config" "/var/lib/music-assistant" ];
+      example = [
+        "--log-level"
+        "DEBUG"
+      ];
+      description = ''
+        List of extra options to pass to the music-assistant executable.
+      '';
+    };
+
+    providers = mkOption {
+      type = listOf (enum cfg.package.providerNames);
+      default = [];
+      example = [
+        "opensubsonic"
+        "snapcast"
+      ];
+      description = ''
+        List of provider names for which dependencies will be installed.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.music-assistant = {
+      description = "Music Assistant";
+      documentation = [ "https://music-assistant.io" ];
+
+      wantedBy = [ "multi-user.target" ];
+
+      environment = {
+        HOME = "/var/lib/music-assistant";
+        PYTHONPATH = finalPackage.pythonPath;
+      };
+
+      serviceConfig = {
+        ExecStart = utils.escapeSystemdExecArgs ([
+          (lib.getExe cfg.package)
+        ] ++ cfg.extraOptions);
+        DynamicUser = true;
+        StateDirectory = "music-assistant";
+        AmbientCapabilities = "";
+        CapabilityBoundingSet = [ "" ];
+        DevicePolicy = "closed";
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        ProcSubset = "pid";
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        RestrictAddressFamilies = [
+          "AF_INET"
+          "AF_INET6"
+          "AF_NETLINK"
+        ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [
+          "@system-service"
+          "~@privileged @resources"
+        ];
+        RestrictSUIDSGID = true;
+        UMask = "0077";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/audio/snapserver.nix b/nixos/modules/services/audio/snapserver.nix
index f79fb7a07d79b..e624dacf34bfc 100644
--- a/nixos/modules/services/audio/snapserver.nix
+++ b/nixos/modules/services/audio/snapserver.nix
@@ -37,6 +37,7 @@ let
         "&${key}=${value}";
     in
       "--stream.stream=\"${opt.type}://" + os opt.location + "?" + os' "name=" name
+        + os' "&sampleformat=" opt.sampleFormat + os' "&codec=" opt.codec
         + concatStrings (mapAttrsToList flatten opt.query) + "\"";
 
   optionalNull = val: ret:
diff --git a/nixos/modules/services/backup/borgmatic.nix b/nixos/modules/services/backup/borgmatic.nix
index 5da78828bfa83..4d744a735582b 100644
--- a/nixos/modules/services/backup/borgmatic.nix
+++ b/nixos/modules/services/backup/borgmatic.nix
@@ -26,8 +26,8 @@ let
     freeformType = settingsFormat.type;
     options = {
       source_directories = mkOption {
-        type = nullOr (listOf str);
-        default = null;
+        type = listOf str;
+        default = [];
         description = ''
           List of source directories and files to backup. Globs and tildes are
           expanded. Do not backslash spaces in path names.
@@ -35,8 +35,8 @@ let
         example = [ "/home" "/etc" "/var/log/syslog*" "/home/user/path with spaces" ];
       };
       repositories = mkOption {
-        type = nullOr (listOf repository);
-        default = null;
+        type = listOf repository;
+        default = [];
         description = ''
           A required list of local or remote repositories with paths and
           optional labels (which can be used with the --repository flag to
@@ -76,29 +76,42 @@ in
       default = { };
       type = types.attrsOf cfgType;
     };
+
+    enableConfigCheck = mkEnableOption "checking all configurations during build time" // { default = true; };
   };
 
-  config = mkIf cfg.enable {
+  config =
+    let
+      configFiles =
+        (optionalAttrs (cfg.settings != null) { "borgmatic/config.yaml".source = cfgfile; }) //
+        mapAttrs'
+          (name: value: nameValuePair
+            "borgmatic.d/${name}.yaml"
+            { source = settingsFormat.generate "${name}.yaml" value; })
+          cfg.configurations;
+      borgmaticCheck = name: f: pkgs.runCommandCC "${name} validation" { } ''
+            ${pkgs.borgmatic}/bin/borgmatic -c ${f.source} config validate
+            touch $out
+          '';
+    in
+      mkIf cfg.enable {
 
-    warnings = []
-      ++ optional (cfg.settings != null && cfg.settings ? location)
-        "`services.borgmatic.settings.location` is deprecated, please move your options out of sections to the global scope"
-      ++ optional (catAttrs "location" (attrValues cfg.configurations) != [])
-        "`services.borgmatic.configurations.<name>.location` is deprecated, please move your options out of sections to the global scope"
-    ;
+        warnings = []
+          ++ optional (cfg.settings != null && cfg.settings ? location)
+            "`services.borgmatic.settings.location` is deprecated, please move your options out of sections to the global scope"
+          ++ optional (catAttrs "location" (attrValues cfg.configurations) != [])
+            "`services.borgmatic.configurations.<name>.location` is deprecated, please move your options out of sections to the global scope"
+        ;
 
-    environment.systemPackages = [ pkgs.borgmatic ];
+        environment.systemPackages = [ pkgs.borgmatic ];
 
-    environment.etc = (optionalAttrs (cfg.settings != null) { "borgmatic/config.yaml".source = cfgfile; }) //
-      mapAttrs'
-        (name: value: nameValuePair
-          "borgmatic.d/${name}.yaml"
-          { source = settingsFormat.generate "${name}.yaml" value; })
-        cfg.configurations;
+        environment.etc = configFiles;
 
-    systemd.packages = [ pkgs.borgmatic ];
+        systemd.packages = [ pkgs.borgmatic ];
 
-    # Workaround: https://github.com/NixOS/nixpkgs/issues/81138
-    systemd.timers.borgmatic.wantedBy = [ "timers.target" ];
-  };
+        # Workaround: https://github.com/NixOS/nixpkgs/issues/81138
+        systemd.timers.borgmatic.wantedBy = [ "timers.target" ];
+
+        system.checks = mkIf cfg.enableConfigCheck (mapAttrsToList borgmaticCheck configFiles);
+      };
 }
diff --git a/nixos/modules/services/backup/btrbk.nix b/nixos/modules/services/backup/btrbk.nix
index 06ca4236eaf22..fa6c67ff7cbfa 100644
--- a/nixos/modules/services/backup/btrbk.nix
+++ b/nixos/modules/services/backup/btrbk.nix
@@ -204,10 +204,6 @@ in
   };
   config = mkIf (sshEnabled || serviceEnabled) {
 
-    warnings = optional (cfg.extraPackages != []) ''
-      extraPackages option will be deprecated in future releases. Programs required for compression are now automatically selected depending on services.btrbk.instances.<name>.settings.stream_compress option.
-    '';
-
     environment.systemPackages = [ pkgs.btrbk ] ++ cfg.extraPackages;
 
     security.sudo.extraRules = mkIf (sudo_doas == "sudo") [ sudoRule ];
diff --git a/nixos/modules/services/backup/duplicity.nix b/nixos/modules/services/backup/duplicity.nix
index 033d0cffd8d6e..46625ec5460e4 100644
--- a/nixos/modules/services/backup/duplicity.nix
+++ b/nixos/modules/services/backup/duplicity.nix
@@ -42,6 +42,28 @@ in
       '';
     };
 
+    includeFileList = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      example = /path/to/fileList.txt;
+      description = ''
+        File containing newline-separated list of paths to include into the
+        backups. See the FILE SELECTION section in {manpage}`duplicity(1)` for
+        details on the syntax.
+      '';
+    };
+
+    excludeFileList = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      example = /path/to/fileList.txt;
+      description = ''
+        File containing newline-separated list of paths to exclude into the
+        backups. See the FILE SELECTION section in {manpage}`duplicity(1)` for
+        details on the syntax.
+      '';
+    };
+
     targetUrl = mkOption {
       type = types.str;
       example = "s3://host:port/prefix";
@@ -154,6 +176,8 @@ in
             ${lib.optionalString (cfg.cleanup.maxIncr != null) "${dup} remove-all-inc-of-but-n-full ${toString cfg.cleanup.maxIncr} ${target} --force ${extra}"}
             exec ${dup} ${if cfg.fullIfOlderThan == "always" then "full" else "incr"} ${lib.escapeShellArgs (
               [ cfg.root cfg.targetUrl ]
+              ++ lib.optionals (cfg.includeFileList != null) [ "--include-filelist" cfg.includeFileList ]
+              ++ lib.optionals (cfg.excludeFileList != null) [ "--exclude-filelist" cfg.excludeFileList ]
               ++ concatMap (p: [ "--include" p ]) cfg.include
               ++ concatMap (p: [ "--exclude" p ]) cfg.exclude
               ++ (lib.optionals (cfg.fullIfOlderThan != "never" && cfg.fullIfOlderThan != "always") [ "--full-if-older-than" cfg.fullIfOlderThan ])
diff --git a/nixos/modules/services/backup/mysql-backup.nix b/nixos/modules/services/backup/mysql-backup.nix
index 00381be4b75d3..e3fa7f45844f1 100644
--- a/nixos/modules/services/backup/mysql-backup.nix
+++ b/nixos/modules/services/backup/mysql-backup.nix
@@ -20,7 +20,7 @@ let
   '';
   backupDatabaseScript = db: ''
     dest="${cfg.location}/${db}.gz"
-    if ${mariadb}/bin/mysqldump ${optionalString cfg.singleTransaction "--single-transaction"} ${db} | ${gzip}/bin/gzip -c > $dest.tmp; then
+    if ${mariadb}/bin/mysqldump ${optionalString cfg.singleTransaction "--single-transaction"} ${db} | ${gzip}/bin/gzip -c ${cfg.gzipOptions} > $dest.tmp; then
       mv $dest.tmp $dest
       echo "Backed up to $dest"
     else
@@ -78,6 +78,14 @@ in
           Whether to create database dump in a single transaction
         '';
       };
+
+      gzipOptions = mkOption {
+        default = "--no-name --rsyncable";
+        type = types.str;
+        description = ''
+          Command line options to use when invoking `gzip`.
+        '';
+      };
     };
 
   };
diff --git a/nixos/modules/services/backup/restic-rest-server.nix b/nixos/modules/services/backup/restic-rest-server.nix
index 935907643bd2f..eb7b57800333e 100644
--- a/nixos/modules/services/backup/restic-rest-server.nix
+++ b/nixos/modules/services/backup/restic-rest-server.nix
@@ -15,7 +15,7 @@ in
       default = "8000";
       example = "127.0.0.1:8080";
       type = types.str;
-      description = "Listen on a specific IP address and port.";
+      description = "Listen on a specific IP address and port or unix socket.";
     };
 
     dataDir = mkOption {
diff --git a/nixos/modules/services/backup/restic.nix b/nixos/modules/services/backup/restic.nix
index 8be2649189b95..a7c2ef2eacd5b 100644
--- a/nixos/modules/services/backup/restic.nix
+++ b/nixos/modules/services/backup/restic.nix
@@ -83,6 +83,15 @@ in
           '';
         };
 
+        inhibitsSleep = mkOption {
+          default = false;
+          type = types.bool;
+          example = true;
+          description = ''
+            Prevents the system from sleeping while backing up.
+          '';
+        };
+
         repository = mkOption {
           type = with types; nullOr str;
           default = null;
@@ -299,7 +308,14 @@ in
         (name: backup:
           let
             extraOptions = concatMapStrings (arg: " -o ${arg}") backup.extraOptions;
-            resticCmd = "${backup.package}/bin/restic${extraOptions}";
+            inhibitCmd = concatStringsSep " " [
+              "${pkgs.systemd}/bin/systemd-inhibit"
+              "--mode='block'"
+              "--who='restic'"
+              "--what='sleep'"
+              "--why=${escapeShellArg "Scheduled backup ${name}"} "
+            ];
+            resticCmd = "${optionalString backup.inhibitsSleep inhibitCmd}${backup.package}/bin/restic${extraOptions}";
             excludeFlags = optional (backup.exclude != []) "--exclude-file=${pkgs.writeText "exclude-patterns" (concatStringsSep "\n" backup.exclude)}";
             filesFromTmpFile = "/run/restic-backups-${name}/includes";
             doBackup = (backup.dynamicFilesFrom != null) || (backup.paths != null && backup.paths != []);
@@ -355,10 +371,10 @@ in
                 ${pkgs.writeScript "backupPrepareCommand" backup.backupPrepareCommand}
               ''}
               ${optionalString (backup.initialize) ''
-                ${resticCmd} snapshots || ${resticCmd} init
+                ${resticCmd} cat config > /dev/null || ${resticCmd} init
               ''}
               ${optionalString (backup.paths != null && backup.paths != []) ''
-                cat ${pkgs.writeText "staticPaths" (concatStringsSep "\n" backup.paths)} >> ${filesFromTmpFile}
+                cat ${pkgs.writeText "staticPaths" (concatLines backup.paths)} >> ${filesFromTmpFile}
               ''}
               ${optionalString (backup.dynamicFilesFrom != null) ''
                 ${pkgs.writeScript "dynamicFilesFromScript" backup.dynamicFilesFrom} >> ${filesFromTmpFile}
diff --git a/nixos/modules/services/backup/tsm.nix b/nixos/modules/services/backup/tsm.nix
index dc5d8f09e069b..9e1abb85bfe54 100644
--- a/nixos/modules/services/backup/tsm.nix
+++ b/nixos/modules/services/backup/tsm.nix
@@ -90,7 +90,7 @@ in
       environment.HOME = "/var/lib/tsm-backup";
       serviceConfig = {
         # for exit status description see
-        # https://www.ibm.com/docs/en/storage-protect/8.1.22?topic=clients-client-return-codes
+        # https://www.ibm.com/docs/en/storage-protect/8.1.23?topic=clients-client-return-codes
         SuccessExitStatus = "4 8";
         # The `-se` option must come after the command.
         # The `-optfile` option suppresses a `dsm.opt`-not-found warning.
diff --git a/nixos/modules/services/blockchain/ethereum/lighthouse.nix b/nixos/modules/services/blockchain/ethereum/lighthouse.nix
index dcf56e187eaec..a5ace1a9450f3 100644
--- a/nixos/modules/services/blockchain/ethereum/lighthouse.nix
+++ b/nixos/modules/services/blockchain/ethereum/lighthouse.nix
@@ -192,7 +192,7 @@ in {
       };
 
       network = mkOption {
-        type = types.enum [ "mainnet" "prater" "goerli" "gnosis" "kiln" "ropsten" "sepolia" ];
+        type = types.enum [ "mainnet" "gnosis" "chiado" "sepolia" "holesky" ];
         default = "mainnet";
         description = ''
           The network to connect to. Mainnet is the default ethereum network.
diff --git a/nixos/modules/services/cluster/druid/default.nix b/nixos/modules/services/cluster/druid/default.nix
new file mode 100644
index 0000000000000..f28e5c90270cf
--- /dev/null
+++ b/nixos/modules/services/cluster/druid/default.nix
@@ -0,0 +1,296 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+let
+  cfg = config.services.druid;
+  inherit (lib)
+    concatStrings
+    concatStringsSep
+    mapAttrsToList
+    concatMap
+    attrByPath
+    mkIf
+    mkMerge
+    mkEnableOption
+    mkOption
+    types
+    mkPackageOption
+    ;
+
+  druidServiceOption = serviceName: {
+    enable = mkEnableOption serviceName;
+
+    restartIfChanged = mkOption {
+      type = types.bool;
+      description = ''
+        Automatically restart the service on config change.
+        This can be set to false to defer restarts on clusters running critical applications.
+        Please consider the security implications of inadvertently running an older version,
+        and the possibility of unexpected behavior caused by inconsistent versions across a cluster when disabling this option.
+      '';
+      default = false;
+    };
+
+    config = mkOption {
+      default = { };
+      type = types.attrsOf types.anything;
+      description = ''
+        (key=value) Configuration to be written to runtime.properties of the druid ${serviceName}
+        <https://druid.apache.org/docs/latest/configuration/index.html>
+      '';
+      example = {
+        "druid.plainTextPort" = "8082";
+        "druid.service" = "servicename";
+      };
+    };
+
+    jdk = mkPackageOption pkgs "JDK" { default = [ "jdk17_headless" ]; };
+
+    jvmArgs = mkOption {
+      type = types.str;
+      default = "";
+      description = "Arguments to pass to the JVM";
+    };
+
+    openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Open firewall ports for ${serviceName}.";
+    };
+
+    internalConfig = mkOption {
+      default = { };
+      type = types.attrsOf types.anything;
+      internal = true;
+      description = "Internal Option to add to runtime.properties for ${serviceName}.";
+    };
+  };
+
+  druidServiceConfig =
+    {
+      name,
+      serviceOptions ? cfg."${name}",
+      allowedTCPPorts ? [ ],
+      tmpDirs ? [ ],
+      extraConfig ? { },
+    }:
+    (mkIf serviceOptions.enable (mkMerge [
+      {
+        systemd = {
+          services."druid-${name}" = {
+            after = [ "network.target" ];
+
+            description = "Druid ${name}";
+
+            wantedBy = [ "multi-user.target" ];
+
+            inherit (serviceOptions) restartIfChanged;
+
+            path = [
+              cfg.package
+              serviceOptions.jdk
+            ];
+
+            script =
+              let
+                cfgFile =
+                  fileName: properties:
+                  pkgs.writeTextDir fileName (
+                    concatStringsSep "\n" (mapAttrsToList (n: v: "${n}=${toString v}") properties)
+                  );
+
+                commonConfigFile = cfgFile "common.runtime.properties" cfg.commonConfig;
+
+                configFile = cfgFile "runtime.properties" (serviceOptions.config // serviceOptions.internalConfig);
+
+                extraClassPath = concatStrings (map (path: ":" + path) cfg.extraClassPaths);
+
+                extraConfDir = concatStrings (map (dir: ":" + dir + "/*") cfg.extraConfDirs);
+              in
+              ''
+                run-java -Dlog4j.configurationFile=file:${cfg.log4j} \
+                  -Ddruid.extensions.directory=${cfg.package}/extensions \
+                  -Ddruid.extensions.hadoopDependenciesDir=${cfg.package}/hadoop-dependencies \
+                  -classpath  ${commonConfigFile}:${configFile}:${cfg.package}/lib/\*${extraClassPath}${extraConfDir} \
+                  ${serviceOptions.jvmArgs} \
+                  org.apache.druid.cli.Main server ${name}
+              '';
+
+            serviceConfig = {
+              User = "druid";
+              SyslogIdentifier = "druid-${name}";
+              Restart = "always";
+            };
+          };
+
+          tmpfiles.rules = concatMap (x: [ "d ${x} 0755 druid druid" ]) (cfg.commonTmpDirs ++ tmpDirs);
+        };
+        networking.firewall.allowedTCPPorts = mkIf (attrByPath [
+          "openFirewall"
+        ] false serviceOptions) allowedTCPPorts;
+
+        users = {
+          users.druid = {
+            description = "Druid user";
+            group = "druid";
+            isNormalUser = true;
+          };
+          groups.druid = { };
+        };
+      }
+      extraConfig
+    ]));
+in
+{
+  options.services.druid = {
+    package = mkPackageOption pkgs "apache-druid" { default = [ "druid" ]; };
+
+    commonConfig = mkOption {
+      default = { };
+
+      type = types.attrsOf types.anything;
+
+      description = "(key=value) Configuration to be written to common.runtime.properties";
+
+      example = {
+        "druid.zk.service.host" = "localhost:2181";
+        "druid.metadata.storage.type" = "mysql";
+        "druid.metadata.storage.connector.connectURI" = "jdbc:mysql://localhost:3306/druid";
+        "druid.extensions.loadList" = ''[ "mysql-metadata-storage" ]'';
+      };
+    };
+
+    commonTmpDirs = mkOption {
+      default = [ "/var/log/druid/requests" ];
+      type = types.listOf types.str;
+      description = "Common List of directories used by druid processes";
+    };
+
+    log4j = mkOption {
+      type = types.path;
+      description = "Log4j Configuration for the druid process";
+    };
+
+    extraClassPaths = mkOption {
+      default = [ ];
+      type = types.listOf types.str;
+      description = "Extra classpath to include in the jvm";
+    };
+
+    extraConfDirs = mkOption {
+      default = [ ];
+      type = types.listOf types.path;
+      description = "Extra Conf Dirs to include in the jvm";
+    };
+
+    overlord = druidServiceOption "Druid Overlord";
+
+    coordinator = druidServiceOption "Druid Coordinator";
+
+    broker = druidServiceOption "Druid Broker";
+
+    historical = (druidServiceOption "Druid Historical") // {
+      segmentLocations = mkOption {
+
+        default = null;
+
+        description = "Locations where the historical will store its data.";
+
+        type =
+          with types;
+          nullOr (
+            listOf (submodule {
+              options = {
+                path = mkOption {
+                  type = path;
+                  description = "the path to store the segments";
+                };
+
+                maxSize = mkOption {
+                  type = str;
+                  description = "Max size the druid historical can occupy";
+                };
+
+                freeSpacePercent = mkOption {
+                  type = float;
+                  default = 1.0;
+                  description = "Druid Historical will fail to write if it exceeds this value";
+                };
+              };
+            })
+          );
+
+      };
+    };
+
+    middleManager = druidServiceOption "Druid middleManager";
+    router = druidServiceOption "Druid Router";
+  };
+  config = mkMerge [
+    (druidServiceConfig rec {
+      name = "overlord";
+      allowedTCPPorts = [ (attrByPath [ "druid.plaintextPort" ] 8090 cfg."${name}".config) ];
+    })
+
+    (druidServiceConfig rec {
+      name = "coordinator";
+      allowedTCPPorts = [ (attrByPath [ "druid.plaintextPort" ] 8081 cfg."${name}".config) ];
+    })
+
+    (druidServiceConfig rec {
+      name = "broker";
+
+      tmpDirs = [ (attrByPath [ "druid.lookup.snapshotWorkingDir" ] "" cfg."${name}".config) ];
+
+      allowedTCPPorts = [ (attrByPath [ "druid.plaintextPort" ] 8082 cfg."${name}".config) ];
+    })
+
+    (druidServiceConfig rec {
+      name = "historical";
+
+      tmpDirs = [
+        (attrByPath [ "druid.lookup.snapshotWorkingDir" ] "" cfg."${name}".config)
+      ] ++ (map (x: x.path) cfg."${name}".segmentLocations);
+
+      allowedTCPPorts = [ (attrByPath [ "druid.plaintextPort" ] 8083 cfg."${name}".config) ];
+
+      extraConfig.services.druid.historical.internalConfig."druid.segmentCache.locations" = builtins.toJSON cfg.historical.segmentLocations;
+    })
+
+    (druidServiceConfig rec {
+      name = "middleManager";
+
+      tmpDirs = [
+        "/var/log/druid/indexer"
+      ] ++ [ (attrByPath [ "druid.indexer.task.baseTaskDir" ] "" cfg."${name}".config) ];
+
+      allowedTCPPorts = [ (attrByPath [ "druid.plaintextPort" ] 8091 cfg."${name}".config) ];
+
+      extraConfig = {
+        services.druid.middleManager.internalConfig = {
+          "druid.indexer.runner.javaCommand" = "${cfg.middleManager.jdk}/bin/java";
+          "druid.indexer.runner.javaOpts" =
+            (attrByPath [ "druid.indexer.runner.javaOpts" ] "" cfg.middleManager.config)
+            + " -Dlog4j.configurationFile=file:${cfg.log4j}";
+        };
+
+        networking.firewall.allowedTCPPortRanges = mkIf cfg.middleManager.openFirewall [
+          {
+            from = attrByPath [ "druid.indexer.runner.startPort" ] 8100 cfg.middleManager.config;
+            to = attrByPath [ "druid.indexer.runner.endPort" ] 65535 cfg.middleManager.config;
+          }
+        ];
+      };
+    })
+
+    (druidServiceConfig rec {
+      name = "router";
+
+      allowedTCPPorts = [ (attrByPath [ "druid.plaintextPort" ] 8888 cfg."${name}".config) ];
+    })
+  ];
+
+}
diff --git a/nixos/modules/services/cluster/k3s/default.nix b/nixos/modules/services/cluster/k3s/default.nix
index 4d18d378d7944..83dfe20671470 100644
--- a/nixos/modules/services/cluster/k3s/default.nix
+++ b/nixos/modules/services/cluster/k3s/default.nix
@@ -17,6 +17,115 @@ let
       ]
       ++ config
     ) instruction;
+
+  manifestDir = "/var/lib/rancher/k3s/server/manifests";
+  chartDir = "/var/lib/rancher/k3s/server/static/charts";
+  imageDir = "/var/lib/rancher/k3s/agent/images";
+  containerdConfigTemplateFile = "/var/lib/rancher/k3s/agent/etc/containerd/config.toml.tmpl";
+
+  manifestModule =
+    let
+      mkTarget =
+        name: if (lib.hasSuffix ".yaml" name || lib.hasSuffix ".yml" name) then name else name + ".yaml";
+    in
+    lib.types.submodule (
+      {
+        name,
+        config,
+        options,
+        ...
+      }:
+      {
+        options = {
+          enable = lib.mkOption {
+            type = lib.types.bool;
+            default = true;
+            description = "Whether this manifest file should be generated.";
+          };
+
+          target = lib.mkOption {
+            type = lib.types.nonEmptyStr;
+            example = lib.literalExpression "manifest.yaml";
+            description = ''
+              Name of the symlink (relative to {file}`${manifestDir}`).
+              Defaults to the attribute name.
+            '';
+          };
+
+          content = lib.mkOption {
+            type = with lib.types; nullOr (either attrs (listOf attrs));
+            default = null;
+            description = ''
+              Content of the manifest file. A single attribute set will
+              generate a single document YAML file. A list of attribute sets
+              will generate multiple documents separated by `---` in a single
+              YAML file.
+            '';
+          };
+
+          source = lib.mkOption {
+            type = lib.types.path;
+            example = lib.literalExpression "./manifests/app.yaml";
+            description = ''
+              Path of the source `.yaml` file.
+            '';
+          };
+        };
+
+        config = {
+          target = lib.mkDefault (mkTarget name);
+          source = lib.mkIf (config.content != null) (
+            let
+              name' = "k3s-manifest-" + builtins.baseNameOf name;
+              docName = "k3s-manifest-doc-" + builtins.baseNameOf name;
+              yamlDocSeparator = builtins.toFile "yaml-doc-separator" "\n---\n";
+              mkYaml = name: x: (pkgs.formats.yaml { }).generate name x;
+              mkSource =
+                value:
+                if builtins.isList value then
+                  pkgs.concatText name' (
+                    lib.concatMap (x: [
+                      yamlDocSeparator
+                      (mkYaml docName x)
+                    ]) value
+                  )
+                else
+                  mkYaml name' value;
+            in
+            lib.mkDerivedConfig options.content mkSource
+          );
+        };
+      }
+    );
+
+  enabledManifests = with builtins; filter (m: m.enable) (attrValues cfg.manifests);
+  linkManifestEntry = m: "${pkgs.coreutils-full}/bin/ln -sfn ${m.source} ${manifestDir}/${m.target}";
+  linkImageEntry = image: "${pkgs.coreutils-full}/bin/ln -sfn ${image} ${imageDir}/${image.name}";
+  linkChartEntry =
+    let
+      mkTarget = name: if (lib.hasSuffix ".tgz" name) then name else name + ".tgz";
+    in
+    name: value:
+    "${pkgs.coreutils-full}/bin/ln -sfn ${value} ${chartDir}/${mkTarget (builtins.baseNameOf name)}";
+
+  activateK3sContent = pkgs.writeShellScript "activate-k3s-content" ''
+    ${lib.optionalString (
+      builtins.length enabledManifests > 0
+    ) "${pkgs.coreutils-full}/bin/mkdir -p ${manifestDir}"}
+    ${lib.optionalString (cfg.charts != { }) "${pkgs.coreutils-full}/bin/mkdir -p ${chartDir}"}
+    ${lib.optionalString (
+      builtins.length cfg.images > 0
+    ) "${pkgs.coreutils-full}/bin/mkdir -p ${imageDir}"}
+
+    ${builtins.concatStringsSep "\n" (map linkManifestEntry enabledManifests)}
+    ${builtins.concatStringsSep "\n" (lib.mapAttrsToList linkChartEntry cfg.charts)}
+    ${builtins.concatStringsSep "\n" (map linkImageEntry cfg.images)}
+
+    ${lib.optionalString (cfg.containerdConfigTemplate != null) ''
+      mkdir -p $(dirname ${containerdConfigTemplateFile})
+      ${pkgs.coreutils-full}/bin/ln -sfn ${pkgs.writeText "config.toml.tmpl" cfg.containerdConfigTemplate} ${containerdConfigTemplateFile}
+    ''}
+  '';
 in
 {
   imports = [ (removeOption [ "docker" ] "k3s docker option is no longer supported.") ];
@@ -103,9 +212,12 @@ in
 
     extraFlags = mkOption {
       description = "Extra flags to pass to the k3s command.";
-      type = types.str;
-      default = "";
-      example = "--no-deploy traefik --cluster-cidr 10.24.0.0/16";
+      type = with types; either str (listOf str);
+      default = [ ];
+      example = [
+        "--no-deploy traefik"
+        "--cluster-cidr 10.24.0.0/16"
+      ];
     };
 
     disableAgent = mkOption {
@@ -127,11 +239,219 @@ in
       default = null;
       description = "File path containing the k3s YAML config. This is useful when the config is generated (for example on boot).";
     };
+
+    manifests = mkOption {
+      type = types.attrsOf manifestModule;
+      default = { };
+      example = lib.literalExpression ''
+        deployment.source = ../manifests/deployment.yaml;
+        my-service = {
+          enable = false;
+          target = "app-service.yaml";
+          content = {
+            apiVersion = "v1";
+            kind = "Service";
+            metadata = {
+              name = "app-service";
+            };
+            spec = {
+              selector = {
+                "app.kubernetes.io/name" = "MyApp";
+              };
+              ports = [
+                {
+                  name = "name-of-service-port";
+                  protocol = "TCP";
+                  port = 80;
+                  targetPort = "http-web-svc";
+                }
+              ];
+            };
+          }
+        };
+
+        nginx.content = [
+          {
+            apiVersion = "v1";
+            kind = "Pod";
+            metadata = {
+              name = "nginx";
+              labels = {
+                "app.kubernetes.io/name" = "MyApp";
+              };
+            };
+            spec = {
+              containers = [
+                {
+                  name = "nginx";
+                  image = "nginx:1.14.2";
+                  ports = [
+                    {
+                      containerPort = 80;
+                      name = "http-web-svc";
+                    }
+                  ];
+                }
+              ];
+            };
+          }
+          {
+            apiVersion = "v1";
+            kind = "Service";
+            metadata = {
+              name = "nginx-service";
+            };
+            spec = {
+              selector = {
+                "app.kubernetes.io/name" = "MyApp";
+              };
+              ports = [
+                {
+                  name = "name-of-service-port";
+                  protocol = "TCP";
+                  port = 80;
+                  targetPort = "http-web-svc";
+                }
+              ];
+            };
+          }
+        ];
+      '';
+      description = ''
+        Auto-deploying manifests that are linked to {file}`${manifestDir}` before k3s starts.
+        Note that deleting manifest files will not remove or otherwise modify the resources
+        it created. Please use the the `--disable` flag or `.skip` files to delete/disable AddOns,
+        as mentioned in the [docs](https://docs.k3s.io/installation/packaged-components#disabling-manifests).
+        This option only makes sense on server nodes (`role = server`).
+        Read the [auto-deploying manifests docs](https://docs.k3s.io/installation/packaged-components#auto-deploying-manifests-addons)
+        for further information.
+      '';
+    };
+
+    charts = mkOption {
+      type = with types; attrsOf (either path package);
+      default = { };
+      example = lib.literalExpression ''
+        nginx = ../charts/my-nginx-chart.tgz;
+        redis = ../charts/my-redis-chart.tgz;
+      '';
+      description = ''
+        Packaged Helm charts that are linked to {file}`${chartDir}` before k3s starts.
+        The attribute name will be used as the link target (relative to {file}`${chartDir}`).
+        The specified charts will only be placed on the file system and made available to the
+        Kubernetes APIServer from within the cluster, you may use the
+        [k3s Helm controller](https://docs.k3s.io/helm#using-the-helm-controller)
+        to deploy the charts. This option only makes sense on server nodes
+        (`role = server`).
+      '';
+    };
+
+    containerdConfigTemplate = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = lib.literalExpression ''
+        # Base K3s config
+        {{ template "base" . }}
+
+        # Add a custom runtime
+        [plugins."io.containerd.grpc.v1.cri".containerd.runtimes."custom"]
+          runtime_type = "io.containerd.runc.v2"
+        [plugins."io.containerd.grpc.v1.cri".containerd.runtimes."custom".options]
+          BinaryName = "/path/to/custom-container-runtime"
+      '';
+      description = ''
+        Config template for containerd, to be placed at
+        `/var/lib/rancher/k3s/agent/etc/containerd/config.toml.tmpl`.
+        See the K3s docs on [configuring containerd](https://docs.k3s.io/advanced#configuring-containerd).
+      '';
+    };
+
+    images = mkOption {
+      type = with types; listOf package;
+      default = [ ];
+      example = lib.literalExpression ''
+        [
+          (pkgs.dockerTools.pullImage {
+            imageName = "docker.io/bitnami/keycloak";
+            imageDigest = "sha256:714dfadc66a8e3adea6609bda350345bd3711657b7ef3cf2e8015b526bac2d6b";
+            sha256 = "0imblp0kw9vkcr7sp962jmj20fpmb3hvd3hmf4cs4x04klnq3k90";
+            finalImageTag = "21.1.2-debian-11-r0";
+          })
+
+          config.services.k3s.package.airgapImages
+        ]
+      '';
+      description = ''
+        List of derivations that provide container images.
+        All images are linked to {file}`${imageDir}` before k3s starts and consequently imported
+        by the k3s agent. Consider importing the k3s airgap images archive of the k3s package in
+        use, if you want to pre-provision this node with all k3s container images. This option
+        only makes sense on nodes with an enabled agent.
+      '';
+    };
+
+    gracefulNodeShutdown = {
+      enable = lib.mkEnableOption ''
+        graceful node shutdowns where the kubelet attempts to detect
+        node system shutdown and terminates pods running on the node. See the
+        [documentation](https://kubernetes.io/docs/concepts/cluster-administration/node-shutdown/#graceful-node-shutdown)
+        for further information.
+      '';
+
+      shutdownGracePeriod = lib.mkOption {
+        type = lib.types.nonEmptyStr;
+        default = "30s";
+        example = "1m30s";
+        description = ''
+          Specifies the total duration that the node should delay the shutdown by. This is the total
+          grace period for pod termination for both regular and critical pods.
+        '';
+      };
+
+      shutdownGracePeriodCriticalPods = lib.mkOption {
+        type = lib.types.nonEmptyStr;
+        default = "10s";
+        example = "15s";
+        description = ''
+          Specifies the duration used to terminate critical pods during a node shutdown. This should be
+          less than `shutdownGracePeriod`.
+        '';
+      };
+    };
+
+    extraKubeletConfig = lib.mkOption {
+      type = with lib.types; attrsOf anything;
+      default = { };
+      example = {
+        podsPerCore = 3;
+        memoryThrottlingFactor = 0.69;
+        containerLogMaxSize = "5Mi";
+      };
+      description = ''
+        Extra configuration to add to the kubelet's configuration file. The subset of the kubelet's
+        configuration that can be configured via a file is defined by the
+        [KubeletConfiguration](https://kubernetes.io/docs/reference/config-api/kubelet-config.v1beta1/)
+        struct. See the
+        [documentation](https://kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file/)
+        for further information.
+      '';
+    };
   };
 
   # implementation
 
   config = mkIf cfg.enable {
+    warnings =
+      (lib.optional (cfg.role != "server" && cfg.manifests != { })
+        "k3s: Auto deploying manifests are only installed on server nodes (role == server), they will be ignored by this node."
+      )
+      ++ (lib.optional (cfg.role != "server" && cfg.charts != { })
+        "k3s: Helm charts are only made available to the cluster on server nodes (role == server), they will be ignored by this node."
+      )
+      ++ (lib.optional (cfg.disableAgent && cfg.images != [ ])
+        "k3s: Images are only imported on nodes with an enabled agent, they will be ignored by this node"
+      );
+
     assertions = [
       {
         assertion = cfg.role == "agent" -> (cfg.configPath != null || cfg.serverAddr != "");
@@ -154,41 +474,60 @@ in
 
     environment.systemPackages = [ config.services.k3s.package ];
 
-    systemd.services.k3s = {
-      description = "k3s service";
-      after = [
-        "firewall.service"
-        "network-online.target"
-      ];
-      wants = [
-        "firewall.service"
-        "network-online.target"
-      ];
-      wantedBy = [ "multi-user.target" ];
-      path = optional config.boot.zfs.enabled config.boot.zfs.package;
-      serviceConfig = {
-        # See: https://github.com/rancher/k3s/blob/dddbd16305284ae4bd14c0aade892412310d7edc/install.sh#L197
-        Type = if cfg.role == "agent" then "exec" else "notify";
-        KillMode = "process";
-        Delegate = "yes";
-        Restart = "always";
-        RestartSec = "5s";
-        LimitNOFILE = 1048576;
-        LimitNPROC = "infinity";
-        LimitCORE = "infinity";
-        TasksMax = "infinity";
-        EnvironmentFile = cfg.environmentFile;
-        ExecStart = concatStringsSep " \\\n " (
-          [ "${cfg.package}/bin/k3s ${cfg.role}" ]
-          ++ (optional cfg.clusterInit "--cluster-init")
-          ++ (optional cfg.disableAgent "--disable-agent")
-          ++ (optional (cfg.serverAddr != "") "--server ${cfg.serverAddr}")
-          ++ (optional (cfg.token != "") "--token ${cfg.token}")
-          ++ (optional (cfg.tokenFile != null) "--token-file ${cfg.tokenFile}")
-          ++ (optional (cfg.configPath != null) "--config ${cfg.configPath}")
-          ++ [ cfg.extraFlags ]
+    systemd.services.k3s =
+      let
+        kubeletParams =
+          (lib.optionalAttrs (cfg.gracefulNodeShutdown.enable) {
+            inherit (cfg.gracefulNodeShutdown) shutdownGracePeriod shutdownGracePeriodCriticalPods;
+          })
+          // cfg.extraKubeletConfig;
+        kubeletConfig = (pkgs.formats.yaml { }).generate "k3s-kubelet-config" (
+          {
+            apiVersion = "kubelet.config.k8s.io/v1beta1";
+            kind = "KubeletConfiguration";
+          }
+          // kubeletParams
         );
+      in
+      {
+        description = "k3s service";
+        after = [
+          "firewall.service"
+          "network-online.target"
+        ];
+        wants = [
+          "firewall.service"
+          "network-online.target"
+        ];
+        wantedBy = [ "multi-user.target" ];
+        path = optional config.boot.zfs.enabled config.boot.zfs.package;
+        serviceConfig = {
+          # See: https://github.com/rancher/k3s/blob/dddbd16305284ae4bd14c0aade892412310d7edc/install.sh#L197
+          Type = if cfg.role == "agent" then "exec" else "notify";
+          KillMode = "process";
+          Delegate = "yes";
+          Restart = "always";
+          RestartSec = "5s";
+          LimitNOFILE = 1048576;
+          LimitNPROC = "infinity";
+          LimitCORE = "infinity";
+          TasksMax = "infinity";
+          EnvironmentFile = cfg.environmentFile;
+          ExecStartPre = activateK3sContent;
+          ExecStart = concatStringsSep " \\\n " (
+            [ "${cfg.package}/bin/k3s ${cfg.role}" ]
+            ++ (optional cfg.clusterInit "--cluster-init")
+            ++ (optional cfg.disableAgent "--disable-agent")
+            ++ (optional (cfg.serverAddr != "") "--server ${cfg.serverAddr}")
+            ++ (optional (cfg.token != "") "--token ${cfg.token}")
+            ++ (optional (cfg.tokenFile != null) "--token-file ${cfg.tokenFile}")
+            ++ (optional (cfg.configPath != null) "--config ${cfg.configPath}")
+            ++ (optional (kubeletParams != { }) "--kubelet-arg=config=${kubeletConfig}")
+            ++ (lib.flatten cfg.extraFlags)
+          );
+        };
       };
-    };
   };
+
+  meta.maintainers = lib.teams.k3s.members;
 }
diff --git a/nixos/modules/services/cluster/kubernetes/apiserver.nix b/nixos/modules/services/cluster/kubernetes/apiserver.nix
index fe9dacb8b93d5..81e359e0e642a 100644
--- a/nixos/modules/services/cluster/kubernetes/apiserver.nix
+++ b/nixos/modules/services/cluster/kubernetes/apiserver.nix
@@ -159,10 +159,10 @@ in
     };
 
     featureGates = mkOption {
-      description = "List set of feature gates";
+      description = "Attribute set of feature gates.";
       default = top.featureGates;
       defaultText = literalExpression "config.${otop.featureGates}";
-      type = listOf str;
+      type = attrsOf bool;
     };
 
     kubeletClientCaFile = mkOption {
@@ -349,8 +349,8 @@ in
                 "--etcd-certfile=${cfg.etcd.certFile}"} \
               ${optionalString (cfg.etcd.keyFile != null)
                 "--etcd-keyfile=${cfg.etcd.keyFile}"} \
-              ${optionalString (cfg.featureGates != [])
-                "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.featureGates}"} \
+              ${optionalString (cfg.featureGates != {})
+                "--feature-gates=${(concatStringsSep "," (builtins.attrValues (mapAttrs (n: v: "${n}=${trivial.boolToString v}") cfg.featureGates)))}"} \
               ${optionalString (cfg.basicAuthFile != null)
                 "--basic-auth-file=${cfg.basicAuthFile}"} \
               ${optionalString (cfg.kubeletClientCaFile != null)
diff --git a/nixos/modules/services/cluster/kubernetes/controller-manager.nix b/nixos/modules/services/cluster/kubernetes/controller-manager.nix
index 453043e507d97..b427de22bf896 100644
--- a/nixos/modules/services/cluster/kubernetes/controller-manager.nix
+++ b/nixos/modules/services/cluster/kubernetes/controller-manager.nix
@@ -44,10 +44,10 @@ in
     };
 
     featureGates = mkOption {
-      description = "List set of feature gates";
+      description = "Attribute set of feature gates.";
       default = top.featureGates;
       defaultText = literalExpression "config.${otop.featureGates}";
-      type = listOf str;
+      type = attrsOf bool;
     };
 
     kubeconfig = top.lib.mkKubeConfigOptions "Kubernetes controller manager";
@@ -121,8 +121,8 @@ in
           --bind-address=${cfg.bindAddress} \
           ${optionalString (cfg.clusterCidr!=null)
             "--cluster-cidr=${cfg.clusterCidr}"} \
-          ${optionalString (cfg.featureGates != [])
-            "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.featureGates}"} \
+          ${optionalString (cfg.featureGates != {})
+            "--feature-gates=${concatStringsSep "," (builtins.attrValues (mapAttrs (n: v: "${n}=${trivial.boolToString v}") cfg.featureGates))}"} \
           --kubeconfig=${top.lib.mkKubeConfig "kube-controller-manager" cfg.kubeconfig} \
           --leader-elect=${boolToString cfg.leaderElect} \
           ${optionalString (cfg.rootCaFile!=null)
diff --git a/nixos/modules/services/cluster/kubernetes/default.nix b/nixos/modules/services/cluster/kubernetes/default.nix
index 01760ffbc72da..208b2a864f024 100644
--- a/nixos/modules/services/cluster/kubernetes/default.nix
+++ b/nixos/modules/services/cluster/kubernetes/default.nix
@@ -61,13 +61,13 @@ let
   etcdEndpoints = ["https://${cfg.masterAddress}:2379"];
 
   mkCert = { name, CN, hosts ? [], fields ? {}, action ? "",
-             privateKeyOwner ? "kubernetes" }: rec {
+             privateKeyOwner ? "kubernetes", privateKeyGroup ? "kubernetes" }: rec {
     inherit name caCert CN hosts fields action;
     cert = secret name;
     key = secret "${name}-key";
     privateKeyOptions = {
       owner = privateKeyOwner;
-      group = "nogroup";
+      group = privateKeyGroup;
       mode = "0600";
       path = key;
     };
@@ -155,8 +155,8 @@ in {
 
     featureGates = mkOption {
       description = "List set of feature gates.";
-      default = [];
-      type = types.listOf types.str;
+      default = {};
+      type = types.attrsOf types.bool;
     };
 
     masterAddress = mkOption {
diff --git a/nixos/modules/services/cluster/kubernetes/kubelet.nix b/nixos/modules/services/cluster/kubernetes/kubelet.nix
index f36edeaf64ceb..fd9df556e7ec2 100644
--- a/nixos/modules/services/cluster/kubernetes/kubelet.nix
+++ b/nixos/modules/services/cluster/kubernetes/kubelet.nix
@@ -65,7 +65,7 @@ let
     // lib.optionalAttrs (cfg.tlsKeyFile != null)   { tlsPrivateKeyFile = cfg.tlsKeyFile; }
     // lib.optionalAttrs (cfg.clusterDomain != "")  { clusterDomain = cfg.clusterDomain; }
     // lib.optionalAttrs (cfg.clusterDns != "")     { clusterDNS = [ cfg.clusterDns ] ; }
-    // lib.optionalAttrs (cfg.featureGates != [])   { featureGates = cfg.featureGates; }
+    // lib.optionalAttrs (cfg.featureGates != {})   { featureGates = cfg.featureGates; }
   ));
 
   manifestPath = "kubernetes/manifests";
@@ -185,10 +185,10 @@ in
     };
 
     featureGates = mkOption {
-      description = "List set of feature gates";
+      description = "Attribute set of feature gate";
       default = top.featureGates;
       defaultText = literalExpression "config.${otop.featureGates}";
-      type = listOf str;
+      type = attrsOf bool;
     };
 
     healthz = {
diff --git a/nixos/modules/services/cluster/kubernetes/proxy.nix b/nixos/modules/services/cluster/kubernetes/proxy.nix
index c09e7695f2a42..2e3fdc87b4396 100644
--- a/nixos/modules/services/cluster/kubernetes/proxy.nix
+++ b/nixos/modules/services/cluster/kubernetes/proxy.nix
@@ -30,10 +30,10 @@ in
     };
 
     featureGates = mkOption {
-      description = "List set of feature gates";
+      description = "Attribute set of feature gates.";
       default = top.featureGates;
       defaultText = literalExpression "config.${otop.featureGates}";
-      type = listOf str;
+      type = attrsOf bool;
     };
 
     hostname = mkOption {
@@ -69,8 +69,8 @@ in
           --bind-address=${cfg.bindAddress} \
           ${optionalString (top.clusterCidr!=null)
             "--cluster-cidr=${top.clusterCidr}"} \
-          ${optionalString (cfg.featureGates != [])
-            "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.featureGates}"} \
+          ${optionalString (cfg.featureGates != {})
+            "--feature-gates=${concatStringsSep "," (builtins.attrValues (mapAttrs (n: v: "${n}=${trivial.boolToString v}") cfg.featureGates))}"} \
           --hostname-override=${cfg.hostname} \
           --kubeconfig=${top.lib.mkKubeConfig "kube-proxy" cfg.kubeconfig} \
           ${optionalString (cfg.verbosity != null) "--v=${toString cfg.verbosity}"} \
diff --git a/nixos/modules/services/cluster/kubernetes/scheduler.nix b/nixos/modules/services/cluster/kubernetes/scheduler.nix
index da2f39226a249..6fb90469c706b 100644
--- a/nixos/modules/services/cluster/kubernetes/scheduler.nix
+++ b/nixos/modules/services/cluster/kubernetes/scheduler.nix
@@ -26,10 +26,10 @@ in
     };
 
     featureGates = mkOption {
-      description = "List set of feature gates";
+      description = "Attribute set of feature gates.";
       default = top.featureGates;
       defaultText = literalExpression "config.${otop.featureGates}";
-      type = listOf str;
+      type = attrsOf bool;
     };
 
     kubeconfig = top.lib.mkKubeConfigOptions "Kubernetes scheduler";
@@ -67,8 +67,8 @@ in
         Slice = "kubernetes.slice";
         ExecStart = ''${top.package}/bin/kube-scheduler \
           --bind-address=${cfg.address} \
-          ${optionalString (cfg.featureGates != [])
-            "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.featureGates}"} \
+          ${optionalString (cfg.featureGates != {})
+            "--feature-gates=${concatStringsSep "," (builtins.attrValues (mapAttrs (n: v: "${n}=${trivial.boolToString v}") cfg.featureGates))}"} \
           --kubeconfig=${top.lib.mkKubeConfig "kube-scheduler" cfg.kubeconfig} \
           --leader-elect=${boolToString cfg.leaderElect} \
           --secure-port=${toString cfg.port} \
diff --git a/nixos/modules/services/cluster/patroni/default.nix b/nixos/modules/services/cluster/patroni/default.nix
index d1a165603fdaa..3b563bb89fffb 100644
--- a/nixos/modules/services/cluster/patroni/default.nix
+++ b/nixos/modules/services/cluster/patroni/default.nix
@@ -10,6 +10,15 @@ let
   configFile = format.generate configFileName cfg.settings;
 in
 {
+  imports = [
+    (lib.mkRemovedOptionModule [ "services" "patroni" "raft" ] ''
+      Raft has been deprecated by upstream.
+    '')
+    (lib.mkRemovedOptionModule [ "services" "patroni" "raftPort" ] ''
+      Raft has been deprecated by upstream.
+    '')
+  ];
+
   options.services.patroni = {
 
     enable = mkEnableOption "Patroni";
@@ -68,7 +77,7 @@ in
       type = types.path;
       default = "/var/lib/patroni";
       description = ''
-        Folder where Patroni data will be written, used by Raft as well if enabled.
+        Folder where Patroni data will be written, this is where the pgpass password file will be written.
       '';
     };
 
@@ -120,22 +129,6 @@ in
       '';
     };
 
-    raft = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        This will configure Patroni to use its own RAFT implementation instead of using a dedicated DCS.
-      '';
-    };
-
-    raftPort = mkOption {
-      type = types.port;
-      default = 5010;
-      description = ''
-        The port on which RAFT listens.
-      '';
-    };
-
     softwareWatchdog = mkOption {
       type = types.bool;
       default = false;
@@ -178,12 +171,6 @@ in
         connect_address = "${cfg.nodeIp}:${toString cfg.restApiPort}";
       };
 
-      raft = mkIf cfg.raft {
-        data_dir = "${cfg.dataDir}/raft";
-        self_addr = "${cfg.nodeIp}:5010";
-        partner_addrs = map (ip: ip + ":5010") cfg.otherNodesIps;
-      };
-
       postgresql = {
         listen = "${cfg.nodeIp}:${toString cfg.postgresqlPort}";
         connect_address = "${cfg.nodeIp}:${toString cfg.postgresqlPort}";
@@ -235,7 +222,7 @@ in
             KillMode = "process";
           }
           (mkIf (cfg.postgresqlDataDir == "/var/lib/postgresql/${cfg.postgresqlPackage.psqlSchema}" && cfg.dataDir == "/var/lib/patroni") {
-            StateDirectory = "patroni patroni/raft postgresql postgresql/${cfg.postgresqlPackage.psqlSchema}";
+            StateDirectory = "patroni postgresql postgresql/${cfg.postgresqlPackage.psqlSchema}";
             StateDirectoryMode = "0750";
           })
         ];
@@ -251,7 +238,6 @@ in
     environment.systemPackages = [
       pkgs.patroni
       cfg.postgresqlPackage
-      (mkIf cfg.raft pkgs.python310Packages.pysyncobj)
     ];
 
     environment.etc."${configFileName}".source = configFile;
diff --git a/nixos/modules/services/cluster/rke2/default.nix b/nixos/modules/services/cluster/rke2/default.nix
index 9ddbd299fdf8d..51b849ebcc802 100644
--- a/nixos/modules/services/cluster/rke2/default.nix
+++ b/nixos/modules/services/cluster/rke2/default.nix
@@ -241,7 +241,7 @@ in
       "kernel.panic_on_oops" = 1;
     };
 
-    systemd.services.rke2 = {
+    systemd.services."rke2-${cfg.role}" = {
       description = "Rancher Kubernetes Engine v2";
       documentation = [ "https://github.com/rancher/rke2#readme" ];
       after = [ "network-online.target" ];
diff --git a/nixos/modules/services/continuous-integration/buildkite-agents.nix b/nixos/modules/services/continuous-integration/buildkite-agents.nix
index fc30172c64999..eb7b7a1707854 100644
--- a/nixos/modules/services/continuous-integration/buildkite-agents.nix
+++ b/nixos/modules/services/continuous-integration/buildkite-agents.nix
@@ -205,6 +205,8 @@ in
       serviceConfig = {
         ExecStart = "${cfg.package}/bin/buildkite-agent start --config ${cfg.dataDir}/buildkite-agent.cfg";
         User = "buildkite-agent-${name}";
+        # Workaround https://github.com/buildkite/agent/issues/2916
+        PrivateTmp = lib.mkDefault true;
         RestartSec = 5;
         Restart = "on-failure";
         TimeoutSec = 10;
diff --git a/nixos/modules/services/continuous-integration/gitlab-runner.nix b/nixos/modules/services/continuous-integration/gitlab-runner.nix
index 1771ca0b980b9..34e9b6200783d 100644
--- a/nixos/modules/services/continuous-integration/gitlab-runner.nix
+++ b/nixos/modules/services/continuous-integration/gitlab-runner.nix
@@ -40,6 +40,7 @@ let
 
   cfg = config.services.gitlab-runner;
   hasDocker = config.virtualisation.docker.enable;
+  hasPodman = config.virtualisation.podman.enable && config.virtualisation.podman.dockerSocket.enable;
 
   /* The whole logic of this module is to diff the hashes of the desired vs existing runners
   The hash is recorded in the runner's name because we can't do better yet
@@ -137,8 +138,10 @@ let
             "--builds-dir ${service.buildsDir}"
             ++ optional (service.cloneUrl != null)
             "--clone-url ${service.cloneUrl}"
-            ++ optional (service.preCloneScript != null)
-            "--pre-clone-script ${service.preCloneScript}"
+            ++ optional (service.preGetSourcesScript != null)
+            "--pre-get-sources-script ${service.preGetSourcesScript}"
+            ++ optional (service.postGetSourcesScript != null)
+            "--post-get-sources-script ${service.postGetSourcesScript}"
             ++ optional (service.preBuildScript != null)
             "--pre-build-script ${service.preBuildScript}"
             ++ optional (service.postBuildScript != null)
@@ -495,13 +498,20 @@ in {
               Whitelist allowed services.
             '';
           };
-          preCloneScript = mkOption {
+          preGetSourcesScript = mkOption {
             type = types.nullOr types.path;
             default = null;
             description = ''
               Runner-specific command script executed before code is pulled.
             '';
           };
+          postGetSourcesScript = mkOption {
+            type = types.nullOr types.path;
+            default = null;
+            description = ''
+              Runner-specific command script executed after code is pulled.
+            '';
+          };
           preBuildScript = mkOption {
             type = types.nullOr types.path;
             default = null;
@@ -693,8 +703,11 @@ in {
       description = "Gitlab Runner";
       documentation = [ "https://docs.gitlab.com/runner/" ];
       after = [ "network.target" ]
-        ++ optional hasDocker "docker.service";
-      requires = optional hasDocker "docker.service";
+        ++ optional hasDocker "docker.service"
+        ++ optional hasPodman "podman.service";
+
+      requires = optional hasDocker "docker.service"
+        ++ optional hasPodman "podman.service";
       wantedBy = [ "multi-user.target" ];
       environment = config.networking.proxy.envVars // {
         HOME = "/var/lib/gitlab-runner";
@@ -720,7 +733,8 @@ in {
         # Make sure to restart service or changes won't apply.
         DynamicUser = true;
         StateDirectory = "gitlab-runner";
-        SupplementaryGroups = optional hasDocker "docker";
+        SupplementaryGroups = optional hasDocker "docker"
+          ++ optional hasPodman "podman";
         ExecStartPre = "!${configureScript}/bin/gitlab-runner-configure";
         ExecStart = "${startScript}/bin/gitlab-runner-start";
         ExecReload = "!${configureScript}/bin/gitlab-runner-configure";
diff --git a/nixos/modules/services/databases/monetdb.nix b/nixos/modules/services/databases/monetdb.nix
index 5025eb30369b4..ee24cf2b0fc20 100644
--- a/nixos/modules/services/databases/monetdb.nix
+++ b/nixos/modules/services/databases/monetdb.nix
@@ -6,7 +6,7 @@ let
   cfg = config.services.monetdb;
 
 in {
-  meta.maintainers = with maintainers; [ StillerHarpo primeos ];
+  meta.maintainers = with maintainers; [ StillerHarpo ];
 
   ###### interface
   options = {
diff --git a/nixos/modules/services/databases/redis.nix b/nixos/modules/services/databases/redis.nix
index ad88a4f589a20..7a3f408aa98e4 100644
--- a/nixos/modules/services/databases/redis.nix
+++ b/nixos/modules/services/databases/redis.nix
@@ -64,14 +64,7 @@ in {
       servers = mkOption {
         type = with types; attrsOf (submodule ({ config, name, ... }: {
           options = {
-            enable = mkEnableOption ''
-              Redis server.
-
-              Note that the NixOS module for Redis disables kernel support
-              for Transparent Huge Pages (THP),
-              because this features causes major performance problems for Redis,
-              e.g. (https://redis.io/topics/latency)
-            '';
+            enable = mkEnableOption "Redis server";
 
             user = mkOption {
               type = types.str;
diff --git a/nixos/modules/services/databases/surrealdb.nix b/nixos/modules/services/databases/surrealdb.nix
index 08a6cca043ca4..2118312d5a1b5 100644
--- a/nixos/modules/services/databases/surrealdb.nix
+++ b/nixos/modules/services/databases/surrealdb.nix
@@ -58,7 +58,7 @@ in {
     environment.systemPackages = [ cfg.package ] ;
 
     systemd.services.surrealdb = {
-      description = "A scalable, distributed, collaborative, document-graph database, for the realtime web ";
+      description = "A scalable, distributed, collaborative, document-graph database, for the realtime web";
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
 
diff --git a/nixos/modules/services/desktop-managers/lomiri.nix b/nixos/modules/services/desktop-managers/lomiri.nix
index 0b871aa38183e..bd43b4c1cfdae 100644
--- a/nixos/modules/services/desktop-managers/lomiri.nix
+++ b/nixos/modules/services/desktop-managers/lomiri.nix
@@ -21,8 +21,12 @@ in {
         history-service
         libusermetrics
         lomiri
+        lomiri-calculator-app
+        lomiri-camera-app
+        lomiri-clock-app
         lomiri-download-manager
         lomiri-filemanager-app
+        lomiri-polkit-agent
         lomiri-schemas # exposes some required dbus interfaces
         lomiri-session # wrappers to properly launch the session
         lomiri-sounds
@@ -35,7 +39,8 @@ in {
         morph-browser
         qtmir # not having its desktop file for Xwayland available causes any X11 application to crash the session
         suru-icon-theme
-        # telephony-service # currently broken: https://github.com/NixOS/nixpkgs/pull/314043
+        telephony-service
+        teleports
       ]);
       variables = {
         # To override the keyboard layouts in Lomiri
@@ -59,7 +64,7 @@ in {
 
     fonts.packages = with pkgs; [
       # Applications tend to default to Ubuntu font
-      ubuntu_font_family
+      ubuntu-classic
     ];
 
     # Copy-pasted basic stuff
@@ -84,7 +89,7 @@ in {
       ] ++ lib.optionals (config.hardware.pulseaudio.enable || config.services.pipewire.pulse.enable) [
         ayatana-indicator-sound
       ]) ++ (with pkgs.lomiri; [
-        # telephony-service # currently broken: https://github.com/NixOS/nixpkgs/pull/314043
+        telephony-service
       ] ++ lib.optionals config.networking.networkmanager.enable [
         lomiri-indicator-network
       ]);
@@ -145,6 +150,18 @@ in {
           ExecStart = "${pkgs.lomiri.lomiri-url-dispatcher}/libexec/lomiri-url-dispatcher/lomiri-update-directory /run/current-system/sw/share/lomiri-url-dispatcher/urls/";
         };
       };
+
+      "lomiri-polkit-agent" = rec {
+        description = "Lomiri Polkit agent";
+        wantedBy = [ "lomiri.service" "lomiri-full-greeter.service" "lomiri-full-shell.service" "lomiri-greeter.service" "lomiri-shell.service" ];
+        after = [ "graphical-session.target" ];
+        partOf = wantedBy;
+        serviceConfig = {
+          Type = "simple";
+          Restart = "always";
+          ExecStart = "${pkgs.lomiri.lomiri-polkit-agent}/libexec/lomiri-polkit-agent/policykit-agent";
+        };
+      };
     };
 
     systemd.services = {
diff --git a/nixos/modules/services/desktop-managers/plasma6.nix b/nixos/modules/services/desktop-managers/plasma6.nix
index 01a5bfa1dee27..3b36d3a905d06 100644
--- a/nixos/modules/services/desktop-managers/plasma6.nix
+++ b/nixos/modules/services/desktop-managers/plasma6.nix
@@ -213,6 +213,7 @@ in {
     };
 
     programs.gnupg.agent.pinentryPackage = mkDefault pkgs.pinentry-qt;
+    programs.kde-pim.enable = mkDefault true;
     programs.ssh.askPassword = mkDefault "${kdePackages.ksshaskpass.out}/bin/ksshaskpass";
 
     # Enable helpful DBus services.
@@ -237,6 +238,8 @@ in {
     systemd.packages = [kdePackages.drkonqi];
     systemd.services."drkonqi-coredump-processor@".wantedBy = ["systemd-coredump@.service"];
 
+    xdg.icons.enable = true;
+
     xdg.portal.enable = true;
     xdg.portal.extraPortals = [kdePackages.xdg-desktop-portal-kde];
     xdg.portal.configPackages = mkDefault [kdePackages.xdg-desktop-portal-kde];
@@ -253,6 +256,7 @@ in {
       extraPackages = with kdePackages; [
         breeze-icons
         kirigami
+        libplasma
         plasma5support
         qtsvg
         qtvirtualkeyboard
diff --git a/nixos/modules/services/desktops/ayatana-indicators.nix b/nixos/modules/services/desktops/ayatana-indicators.nix
index 613a2f03ea054..5a3d4eed8f65f 100644
--- a/nixos/modules/services/desktops/ayatana-indicators.nix
+++ b/nixos/modules/services/desktops/ayatana-indicators.nix
@@ -1,7 +1,8 @@
-{ config
-, pkgs
-, lib
-, ...
+{
+  config,
+  pkgs,
+  lib,
+  ...
 }:
 
 let
@@ -32,26 +33,27 @@ in
     environment = {
       systemPackages = cfg.packages;
 
-      pathsToLink = [
-        "/share/ayatana"
-      ];
+      pathsToLink = [ "/share/ayatana" ];
     };
 
     # libayatana-common's ayatana-indicators.target with explicit Wants & Before to bring up requested indicator services
-    systemd.user.targets."ayatana-indicators" =
+    systemd.user.targets =
       let
-        indicatorServices = lib.lists.flatten
-          (map
-            (pkg:
-              (map (ind: "${ind}.service") pkg.passthru.ayatana-indicators))
-            cfg.packages);
+        indicatorServices = lib.lists.flatten (
+          map (pkg: (map (ind: "${ind}.service") pkg.passthru.ayatana-indicators)) cfg.packages
+        );
       in
-      {
-        description = "Target representing the lifecycle of the Ayatana Indicators. Each indicator should be bound to it in its individual service file";
-        partOf = [ "graphical-session.target" ];
-        wants = indicatorServices;
-        before = indicatorServices;
-      };
+      lib.attrsets.mapAttrs
+        (_: desc: {
+          description = "Target representing the lifecycle of the ${desc}. Each indicator should be bound to it in its individual service file";
+          partOf = [ "graphical-session.target" ];
+          wants = indicatorServices;
+          before = indicatorServices;
+        })
+        {
+          ayatana-indicators = "Ayatana Indicators";
+          lomiri-indicators = "Ayatana/Lomiri Indicators that shall be run in Lomiri";
+        };
   };
 
   meta.maintainers = with lib.maintainers; [ OPNA2608 ];
diff --git a/nixos/modules/services/desktops/espanso.nix b/nixos/modules/services/desktops/espanso.nix
index a2c4b77b90464..4a9bff9267a43 100644
--- a/nixos/modules/services/desktops/espanso.nix
+++ b/nixos/modules/services/desktops/espanso.nix
@@ -3,7 +3,7 @@
 with lib;
 let cfg = config.services.espanso;
 in {
-  meta = { maintainers = with lib.maintainers; [ numkem ]; };
+  meta = { maintainers = with lib.maintainers; [ n8henrie numkem ]; };
 
   options = {
     services.espanso = {
diff --git a/nixos/modules/services/desktops/flatpak.nix b/nixos/modules/services/desktops/flatpak.nix
index cda0a17d04758..b386bafcfe6f5 100644
--- a/nixos/modules/services/desktops/flatpak.nix
+++ b/nixos/modules/services/desktops/flatpak.nix
@@ -15,6 +15,8 @@ in {
   options = {
     services.flatpak = {
       enable = mkEnableOption "flatpak";
+
+      package = mkPackageOption pkgs "flatpak" { };
     };
   };
 
@@ -28,16 +30,16 @@ in {
       }
     ];
 
-    environment.systemPackages = [ pkgs.flatpak ];
+    environment.systemPackages = [ cfg.package ];
 
     security.polkit.enable = true;
 
     fonts.fontDir.enable = true;
 
-    services.dbus.packages = [ pkgs.flatpak ];
+    services.dbus.packages = [ cfg.package ];
 
-    systemd.packages = [ pkgs.flatpak ];
-    systemd.tmpfiles.packages = [ pkgs.flatpak ];
+    systemd.packages = [ cfg.package ];
+    systemd.tmpfiles.packages = [ cfg.package ];
 
     environment.profiles = [
       "$HOME/.local/share/flatpak/exports"
diff --git a/nixos/modules/services/development/livebook.nix b/nixos/modules/services/development/livebook.nix
index c7a6e35375791..8590836352013 100644
--- a/nixos/modules/services/development/livebook.nix
+++ b/nixos/modules/services/development/livebook.nix
@@ -89,6 +89,13 @@ in
         EnvironmentFile = cfg.environmentFile;
         ExecStart = "${cfg.package}/bin/livebook start";
         KillMode = "mixed";
+
+        # Fix for the issue described here:
+        # https://github.com/livebook-dev/livebook/issues/2691
+        #
+        # Without this, the livebook service fails to start and gets
+        # stuck running a `cat /dev/urandom | tr | fold` pipeline.
+        IgnoreSIGPIPE = false;
       };
       environment = mapAttrs (name: value:
         if isBool value then boolToString value else toString value)
@@ -98,5 +105,8 @@ in
     };
   };
 
-  meta.doc = ./livebook.md;
+  meta = {
+    doc = ./livebook.md;
+    maintainers = with lib.maintainers; [ munksgaard scvalex ];
+  };
 }
diff --git a/nixos/modules/services/display-managers/default.nix b/nixos/modules/services/display-managers/default.nix
index 9a7bd6c84b15b..894370199e79e 100644
--- a/nixos/modules/services/display-managers/default.nix
+++ b/nixos/modules/services/display-managers/default.nix
@@ -204,7 +204,8 @@ in
           noDmUsed = !(dmConf.gdm.enable
                     || cfg.sddm.enable
                     || dmConf.xpra.enable
-                    || dmConf.lightdm.enable);
+                    || dmConf.lightdm.enable
+                    || cfg.ly.enable);
       in lib.mkIf noDmUsed (lib.mkDefault false);
 
     systemd.services.display-manager = {
diff --git a/nixos/modules/services/display-managers/ly.nix b/nixos/modules/services/display-managers/ly.nix
new file mode 100644
index 0000000000000..5d42467a6cd8a
--- /dev/null
+++ b/nixos/modules/services/display-managers/ly.nix
@@ -0,0 +1,147 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  dmcfg = config.services.displayManager;
+  xcfg = config.services.xserver;
+  xdmcfg = xcfg.displayManager;
+  cfg = config.services.displayManager.ly;
+  xEnv = config.systemd.services.display-manager.environment;
+
+  ly = cfg.package;
+
+  iniFmt = pkgs.formats.iniWithGlobalSection { };
+
+  inherit (lib)
+    concatMapStrings
+    attrNames
+    getAttr
+    mkIf
+    mkOption
+    mkEnableOption
+    mkPackageOption
+    ;
+
+  xserverWrapper = pkgs.writeShellScript "xserver-wrapper" ''
+    ${concatMapStrings (n: ''
+      export ${n}="${getAttr n xEnv}"
+    '') (attrNames xEnv)}
+    exec systemd-cat -t xserver-wrapper ${xdmcfg.xserverBin} ${toString xdmcfg.xserverArgs} "$@"
+  '';
+
+  defaultConfig = {
+    shutdown_cmd = "/run/current-system/systemd/bin/systemctl poweroff";
+    restart_cmd = "/run/current-system/systemd/bin/systemctl reboot";
+    tty = 2;
+    service_name = "ly";
+    path = "/run/current-system/sw/bin";
+    term_reset_cmd = "${pkgs.ncurses}/bin/tput reset";
+    term_restore_cursor_cmd = "${pkgs.ncurses}/bin/tput cnorm";
+    mcookie_cmd = "/run/current-system/sw/bin/mcookie";
+    waylandsessions = "${dmcfg.sessionData.desktops}/share/wayland-sessions";
+    wayland_cmd = dmcfg.sessionData.wrapper;
+    xsessions = "${dmcfg.sessionData.desktops}/share/xsessions";
+    xauth_cmd = lib.optionalString xcfg.enable "${pkgs.xorg.xauth}/bin/xauth";
+    x_cmd = lib.optionalString xcfg.enable xserverWrapper;
+    x_cmd_setup = dmcfg.sessionData.wrapper;
+  };
+
+  finalConfig = defaultConfig // cfg.settings;
+
+  cfgFile = iniFmt.generate "config.ini" { globalSection = finalConfig; };
+
+in
+{
+  options = {
+    services.displayManager.ly = {
+      enable = mkEnableOption "ly as the display manager";
+
+      package = mkPackageOption pkgs [ "ly" ] { };
+
+      settings = mkOption {
+        type =
+          with lib.types;
+          attrsOf (oneOf [
+            str
+            int
+            bool
+          ]);
+        default = { };
+        example = {
+          load = false;
+          save = false;
+        };
+        description = ''
+          Extra settings merged in and overwriting defaults in config.ini.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      {
+        assertion = !dmcfg.autoLogin.enable;
+        message = ''
+          ly doesn't support auto login.
+        '';
+      }
+    ];
+
+    security.pam.services.ly = {
+      startSession = true;
+      unixAuth = true;
+    };
+
+    environment = {
+      etc."ly/config.ini".source = cfgFile;
+      systemPackages = [ ly ];
+
+      pathsToLink = [ "/share/ly" ];
+    };
+
+    services = {
+      dbus.packages = [ ly ];
+
+      displayManager = {
+        enable = true;
+        execCmd = "exec /run/current-system/sw/bin/ly";
+      };
+
+      xserver = {
+        # To enable user switching, allow ly to allocate TTYs/displays dynamically.
+        tty = null;
+        display = null;
+      };
+    };
+
+    systemd = {
+      # We're not using the upstream unit, so copy these:
+      # https://github.com/fairyglade/ly/blob/master/res/ly.service
+      services.display-manager = {
+        after = [
+          "systemd-user-sessions.service"
+          "plymouth-quit-wait.service"
+          "getty@tty${toString finalConfig.tty}.service"
+        ];
+
+        conflicts = [ "getty@tty7.service" ];
+
+        serviceConfig = {
+          Type = "idle";
+          StandardInput = "tty";
+          TTYPath = "/dev/tty${toString finalConfig.tty}";
+          TTYReset = "yes";
+          TTYVHangup = "yes";
+        };
+      };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ vonfry ];
+}
diff --git a/nixos/modules/services/finance/odoo.nix b/nixos/modules/services/finance/odoo.nix
index 45fb9c7c2397a..074617d35e3d9 100644
--- a/nixos/modules/services/finance/odoo.nix
+++ b/nixos/modules/services/finance/odoo.nix
@@ -20,6 +20,17 @@ in
         description = "Odoo addons.";
       };
 
+      autoInit = mkEnableOption "automatically initialize the DB";
+
+      autoInitExtraFlags = mkOption {
+        type = with types; listOf str;
+        default = [ ];
+        example = literalExpression /*nix*/ ''
+          [ "--without-demo=all" ]
+        '';
+        description = "Extra flags passed to odoo when run for the first time by autoInit";
+      };
+
       settings = mkOption {
         type = format.type;
         default = {};
@@ -84,8 +95,11 @@ in
     };
 
     services.odoo.settings.options = {
+      data_dir = "/var/lib/private/odoo/data";
       proxy_mode = cfg.domain != null;
-    };
+    } // (lib.optionalAttrs (cfg.addons != []) {
+      addons_path = concatMapStringsSep "," escapeShellArg cfg.addons;
+    });
 
     users.users.odoo = {
       isSystemUser = true;
@@ -101,12 +115,40 @@ in
       path = [ config.services.postgresql.package ];
 
       requires = [ "postgresql.service" ];
-      script = "HOME=$STATE_DIRECTORY ${cfg.package}/bin/odoo ${optionalString (cfg.addons != []) "--addons-path=${concatMapStringsSep "," escapeShellArg cfg.addons}"} -c ${cfgFile}";
 
       serviceConfig = {
+        ExecStart = "${cfg.package}/bin/odoo";
+        ExecStartPre = pkgs.writeShellScript "odoo-start-pre.sh" (
+          ''
+          set -euo pipefail
+
+          cd "$STATE_DIRECTORY"
+
+          # Auto-migrate old deployments
+          if [[ -d .local/share/Odoo ]]; then
+            echo "pre-start: migrating state directory from $STATE_DIRECTORY/.local/share/Odoo to $STATE_DIRECTORY/data"
+            mv .local/share/Odoo ./data
+            rmdir .local/share
+            rmdir .local
+          fi
+          ''
+          + (lib.optionalString cfg.autoInit
+          ''
+          echo "pre-start: auto-init"
+          INITIALIZED="${cfg.settings.options.data_dir}/.odoo.initialized"
+          if [ ! -e "$INITIALIZED" ]; then
+            ${cfg.package}/bin/odoo  --init=INIT --database=odoo --db_user=odoo --stop-after-init ${concatStringsSep " " cfg.autoInitExtraFlags}
+            touch "$INITIALIZED"
+          fi
+          '')
+          + "echo pre-start: OK"
+        );
         DynamicUser = true;
         User = "odoo";
         StateDirectory = "odoo";
+        Environment = [
+          "ODOO_RC=${cfgFile}"
+        ];
       };
     };
 
diff --git a/nixos/modules/services/games/archisteamfarm.nix b/nixos/modules/services/games/archisteamfarm.nix
index 7062332db34ab..630329175968d 100644
--- a/nixos/modules/services/games/archisteamfarm.nix
+++ b/nixos/modules/services/games/archisteamfarm.nix
@@ -164,11 +164,8 @@ in
   };
 
   config = lib.mkIf cfg.enable {
-    services.archisteamfarm = {
-      # TODO: drop with 24.11
-      dataDir = lib.mkIf (lib.versionAtLeast config.system.stateVersion "24.05") (lib.mkDefault "/var/lib/asf");
-      settings.IPC = lib.mkIf (!cfg.web-ui.enable) false;
-    };
+    # TODO: drop with 24.11
+    services.archisteamfarm.dataDir = lib.mkIf (lib.versionAtLeast config.system.stateVersion "24.05") (lib.mkDefault "/var/lib/asf");
 
     users = {
       users.archisteamfarm = {
diff --git a/nixos/modules/services/games/terraria.nix b/nixos/modules/services/games/terraria.nix
index 57417b614f713..4c32cc92a1b94 100644
--- a/nixos/modules/services/games/terraria.nix
+++ b/nixos/modules/services/games/terraria.nix
@@ -19,15 +19,28 @@ let
     (boolFlag "secure" cfg.secure)
     (boolFlag "noupnp" cfg.noUPnP)
   ];
-  stopScript = pkgs.writeScript "terraria-stop" ''
-    #!${pkgs.runtimeShell}
 
+  tmuxCmd = "${lib.getExe pkgs.tmux} -S ${lib.escapeShellArg cfg.dataDir}/terraria.sock";
+
+  stopScript = pkgs.writeShellScript "terraria-stop" ''
     if ! [ -d "/proc/$1" ]; then
       exit 0
     fi
 
-    ${getBin pkgs.tmux}/bin/tmux -S ${cfg.dataDir}/terraria.sock send-keys Enter exit Enter
-    ${getBin pkgs.coreutils}/bin/tail --pid="$1" -f /dev/null
+    lastline=$(${tmuxCmd} capture-pane -p | grep . | tail -n1)
+
+    # If the service is not configured to auto-start a world, it will show the world selection prompt
+    # If the last non-empty line on-screen starts with "Choose World", we know the prompt is open
+    if [[ "$lastline" =~ ^'Choose World' ]]; then
+      # In this case, nothing needs to be saved, so we can kill the process
+      ${tmuxCmd} kill-session
+    else
+      # Otherwise, we send the `exit` command
+      ${tmuxCmd} send-keys Enter exit Enter
+    fi
+
+    # Wait for the process to stop
+    tail --pid="$1" -f /dev/null
   '';
 in
 {
@@ -152,7 +165,7 @@ in
         Type = "forking";
         GuessMainPID = true;
         UMask = 007;
-        ExecStart = "${getBin pkgs.tmux}/bin/tmux -S ${cfg.dataDir}/terraria.sock new -d ${pkgs.terraria-server}/bin/TerrariaServer ${concatStringsSep " " flags}";
+        ExecStart = "${tmuxCmd} new -d ${pkgs.terraria-server}/bin/TerrariaServer ${concatStringsSep " " flags}";
         ExecStop = "${stopScript} $MAINPID";
       };
     };
diff --git a/nixos/modules/services/hardware/display.md b/nixos/modules/services/hardware/display.md
new file mode 100644
index 0000000000000..019c4cf146eb8
--- /dev/null
+++ b/nixos/modules/services/hardware/display.md
@@ -0,0 +1,130 @@
+# Customizing display configuration {#module-hardware-display}
+
+This section describes how to customize display configuration using:
+- kernel modes
+- EDID files
+
+Example situations it can help you with:
+- display controllers (external hardware) not advertising EDID at all,
+- misbehaving graphics drivers,
+- loading custom display configuration before the Display Manager is running,
+
+## Forcing display modes {#module-hardware-display-modes}
+
+In case of very wrong monitor controller and/or video driver combination you can
+[force the display to be enabled](https://mjmwired.net/kernel/Documentation/fb/modedb.txt#41)
+and skip some driver-side checks by adding `video=<OUTPUT>:e` to `boot.kernelParams`.
+This is exactly the case with [`amdgpu` drivers](https://gitlab.freedesktop.org/drm/amd/-/issues/615#note_1987392)
+
+```nix
+{
+  # force enabled output to skip `amdgpu` checks
+  hardware.display.outputs."DP-1".mode = "e";
+  # completely disable output no matter what is connected to it
+  hardware.display.outputs."VGA-2".mode = "d";
+
+  /* equals
+  boot.kernelParams = [ "video=DP-1:e" "video=VGA-2:d" ];
+  */
+}
+```
+
+## Crafting custom EDID files {#module-hardware-display-edid-custom}
+
+To make custom EDID binaries discoverable you should first create a derivation storing them at
+`$out/lib/firmware/edid/` and secondly add that derivation to `hardware.display.edid.packages` NixOS option:
+
+```nix
+{
+  hardware.display.edid.packages = [
+    (pkgs.runCommand "edid-custom" {} ''
+       mkdir -p $out/lib/firmware/edid
+       base64 -d > "$out/lib/firmware/edid/custom1.bin" <<'EOF'
+       <insert your base64 encoded EDID file here `base64 < /sys/class/drm/card0-.../edid`>
+       EOF
+       base64 -d > "$out/lib/firmware/edid/custom2.bin" <<'EOF'
+       <insert your base64 encoded EDID file here `base64 < /sys/class/drm/card1-.../edid`>
+       EOF
+    '')
+  ];
+}
+```
+
+There are 2 options significantly easing preparation of EDID files:
+- `hardware.display.edid.linuxhw`
+- `hardware.display.edid.modelines`
+
+## Assigning EDID files to displays {#module-hardware-display-edid-assign}
+
+To assign available custom EDID binaries to your monitor (video output) use `hardware.display.outputs."<NAME>".edid` option.
+Under the hood it adds `drm.edid_firmware` entry to `boot.kernelParams` NixOS option for each configured output:
+
+```nix
+{
+  hardware.display.outputs."VGA-1".edid = "custom1.bin";
+  hardware.display.outputs."VGA-2".edid = "custom2.bin";
+  /* equals:
+  boot.kernelParams = [ "drm.edid_firmware=VGA-1:edid/custom1.bin,VGA-2:edid/custom2.bin" ];
+  */
+}
+```
+
+## Pulling files from linuxhw/EDID database {#module-hardware-display-edid-linuxhw}
+
+`hardware.display.edid.linuxhw` utilizes `pkgs.linuxhw-edid-fetcher` to extract EDID files
+from https://github.com/linuxhw/EDID based on simple string/regexp search identifying exact entries:
+
+```nix
+{
+  hardware.display.edid.linuxhw."PG278Q_2014" = [ "PG278Q" "2014" ];
+
+  /* equals:
+  hardware.display.edid.packages = [
+    (pkgs.linuxhw-edid-fetcher.override {
+      displays = {
+        "PG278Q_2014" = [ "PG278Q" "2014" ];
+      };
+    })
+  ];
+  */
+}
+```
+
+
+## Using XFree86 Modeline definitions {#module-hardware-display-edid-modelines}
+
+`hardware.display.edid.modelines` utilizes `pkgs.edid-generator` package allowing you to
+conveniently use [`XFree86 Modeline`](https://en.wikipedia.org/wiki/XFree86_Modeline) entries as EDID binaries:
+
+```nix
+{
+  hardware.display.edid.modelines."PG278Q_60" = "    241.50   2560 2608 2640 2720   1440 1443 1448 1481   -hsync +vsync";
+  hardware.display.edid.modelines."PG278Q_120" = "   497.75   2560 2608 2640 2720   1440 1443 1448 1525   +hsync -vsync";
+
+  /* equals:
+  hardware.display.edid.packages = [
+    (pkgs.edid-generator.overrideAttrs {
+      clean = true;
+      modelines = ''
+        Modeline "PG278Q_60"      241.50   2560 2608 2640 2720   1440 1443 1448 1481   -hsync +vsync
+        Modeline "PG278Q_120"     497.75   2560 2608 2640 2720   1440 1443 1448 1525   +hsync -vsync
+      '';
+    })
+  ];
+  */
+}
+```
+
+## Complete example for Asus PG278Q {#module-hardware-display-pg278q}
+
+And finally this is a complete working example for a 2014 (first) batch of [Asus PG278Q monitor with `amdgpu` drivers](https://gitlab.freedesktop.org/drm/amd/-/issues/615#note_1987392):
+
+```nix
+{
+  hardware.display.edid.modelines."PG278Q_60" = "   241.50   2560 2608 2640 2720   1440 1443 1448 1481   -hsync +vsync";
+  hardware.display.edid.modelines."PG278Q_120" = "  497.75   2560 2608 2640 2720   1440 1443 1448 1525   +hsync -vsync";
+
+  hardware.display.outputs."DP-1".edid = "PG278Q_60.bin";
+  hardware.display.outputs."DP-1".mode = "e";
+}
+```
diff --git a/nixos/modules/services/hardware/display.nix b/nixos/modules/services/hardware/display.nix
new file mode 100644
index 0000000000000..3b3118f132e92
--- /dev/null
+++ b/nixos/modules/services/hardware/display.nix
@@ -0,0 +1,193 @@
+{ config, lib, pkgs, ... }:
+let
+  cfg = config.hardware.display;
+in
+{
+  meta.doc = ./display.md;
+  meta.maintainers = with lib.maintainers; [
+    nazarewk
+  ];
+
+  options = {
+    hardware.display.edid.enable = lib.mkOption {
+      type = with lib.types; bool;
+      default = cfg.edid.packages != null;
+      defaultText = lib.literalExpression "config.hardware.display.edid.packages != null";
+      description = ''
+        Enables handling of EDID files
+      '';
+    };
+
+    hardware.display.edid.packages = lib.mkOption {
+      type = with lib.types; listOf package;
+      default = [ ];
+      description = ''
+        List of packages containing EDID binary files at `$out/lib/firmware/edid`.
+        Such files will be available for use in `drm.edid_firmware` kernel
+        parameter as `edid/<filename>`.
+
+        You can craft one directly here or use sibling options `linuxhw` and `modelines`.
+      '';
+      example = lib.literalExpression ''
+        [
+          (pkgs.runCommand "edid-custom" {} '''
+            mkdir -p "$out/lib/firmware/edid"
+            base64 -d > "$out/lib/firmware/edid/custom1.bin" <<'EOF'
+            <insert your base64 encoded EDID file here `base64 < /sys/class/drm/card0-.../edid`>
+            EOF
+          ''')
+        ]
+      '';
+      apply = list:
+        if list == [ ] then null else
+        (pkgs.buildEnv {
+          name = "firmware-edid";
+          paths = list;
+          pathsToLink = [ "/lib/firmware/edid" ];
+          ignoreCollisions = true;
+        }) // {
+          compressFirmware = false;
+        };
+    };
+
+    hardware.display.edid.linuxhw = lib.mkOption {
+      type = with lib.types; attrsOf (listOf str);
+      default = { };
+      description = ''
+        Exposes EDID files from users-sourced database at https://github.com/linuxhw/EDID
+
+        Attribute names will be mapped to EDID filenames `<NAME>.bin`.
+
+        Attribute values are lists of `awk` regexp patterns that (together) must match
+        exactly one line in either of:
+        - [AnalogDisplay.md](https://raw.githubusercontent.com/linuxhw/EDID/master/AnalogDisplay.md)
+        - [DigitalDisplay.md](https://raw.githubusercontent.com/linuxhw/EDID/master/DigitalDisplay.md)
+
+        There is no universal way of locating your device config, but here are some practical tips:
+        1. locate your device:
+          - find your model number (second column)
+          - locate manufacturer (first column) and go through the list manually
+        2. narrow down results using other columns until there is only one left:
+          - `Name` column
+          - production date (`Made` column)
+          - resolution `Res`
+          - screen diagonal (`Inch` column)
+          - as a last resort use `ID` from the last column
+      '';
+      example = lib.literalExpression ''
+        {
+          PG278Q_2014 = [ "PG278Q" "2014" ];
+        }
+      '';
+      apply = displays:
+        if displays == { } then null else
+        pkgs.linuxhw-edid-fetcher.override { inherit displays; };
+    };
+
+    hardware.display.edid.modelines = lib.mkOption {
+      type = with lib.types; attrsOf str;
+      default = { };
+      description = ''
+        Attribute set of XFree86 Modelines automatically converted
+        and exposed as `edid/<name>.bin` files in initrd.
+        See for more information:
+        - https://en.wikipedia.org/wiki/XFree86_Modeline
+      '';
+      example = lib.literalExpression ''
+        {
+          "PG278Q_60" = "    241.50   2560 2608 2640 2720   1440 1443 1448 1481   -hsync +vsync";
+          "PG278Q_120" = "   497.75   2560 2608 2640 2720   1440 1443 1448 1525   +hsync -vsync";
+          "U2711_60" = "     241.50   2560 2600 2632 2720   1440 1443 1448 1481   -hsync +vsync";
+        }
+      '';
+      apply = modelines:
+        if modelines == { } then null else
+        pkgs.edid-generator.overrideAttrs {
+          clean = true;
+          passthru.config = modelines;
+          modelines = lib.trivial.pipe modelines [
+            (lib.mapAttrsToList (name: value:
+              lib.throwIfNot (builtins.stringLength name <= 12) "Modeline name must be 12 characters or less"
+                ''Modeline "${name}" ${value}''
+            ))
+            (builtins.map (line: "${line}\n"))
+            (lib.strings.concatStringsSep "")
+          ];
+        };
+    };
+
+    hardware.display.outputs = lib.mkOption {
+      type = lib.types.attrsOf (lib.types.submodule ({
+        options = {
+          edid = lib.mkOption {
+            type = with lib.types; nullOr str;
+            default = null;
+            description = ''
+              An EDID filename to be used for configured display, as in `edid/<filename>`.
+              See for more information:
+              - `hardware.display.edid.packages`
+              - https://wiki.archlinux.org/title/Kernel_mode_setting#Forcing_modes_and_EDID
+            '';
+          };
+          mode = lib.mkOption {
+            type = with lib.types; nullOr str;
+            default = null;
+            description = ''
+              A `video` kernel parameter (framebuffer mode) configuration for the specific output:
+
+                  <xres>x<yres>[M][R][-<bpp>][@<refresh>][i][m][eDd]
+
+              See for more information:
+              - https://docs.kernel.org/fb/modedb.html
+              - https://wiki.archlinux.org/title/Kernel_mode_setting#Forcing_modes
+            '';
+            example = lib.literalExpression ''
+              "e"
+            '';
+          };
+        };
+      }));
+      description = ''
+        Hardware/kernel-level configuration of specific outputs.
+      '';
+      default = { };
+
+      example = lib.literalExpression ''
+        {
+          edid.modelines."PG278Q_60" = "241.50   2560 2608 2640 2720   1440 1443 1448 1481   -hsync +vsync";
+          outputs."DP-1".edid = "PG278Q_60.bin";
+          outputs."DP-1".mode = "e";
+        }
+      '';
+    };
+  };
+
+  config = lib.mkMerge [
+    {
+      hardware.display.edid.packages =
+        lib.optional (cfg.edid.modelines != null) cfg.edid.modelines
+        ++ lib.optional (cfg.edid.linuxhw != null) cfg.edid.linuxhw;
+
+      boot.kernelParams =
+        # forcing video modes
+        lib.trivial.pipe cfg.outputs [
+          (lib.attrsets.filterAttrs (_: spec: spec.mode != null))
+          (lib.mapAttrsToList (output: spec: "video=${output}:${spec.mode}"))
+        ]
+        ++
+        # selecting EDID for displays
+        lib.trivial.pipe cfg.outputs [
+          (lib.attrsets.filterAttrs (_: spec: spec.edid != null))
+          (lib.mapAttrsToList (output: spec: "${output}:edid/${spec.edid}"))
+          (builtins.concatStringsSep ",")
+          (p: lib.optional (p != "") "drm.edid_firmware=${p}")
+        ]
+      ;
+    }
+    (lib.mkIf (cfg.edid.packages != null) {
+      # services.udev implements hardware.firmware option
+      services.udev.enable = true;
+      hardware.firmware = [ cfg.edid.packages ];
+    })
+  ];
+}
diff --git a/nixos/modules/services/hardware/nvidia-container-toolkit/cdi-generate.nix b/nixos/modules/services/hardware/nvidia-container-toolkit/cdi-generate.nix
index ca769cc44e5c9..360a832e28cbe 100644
--- a/nixos/modules/services/hardware/nvidia-container-toolkit/cdi-generate.nix
+++ b/nixos/modules/services/hardware/nvidia-container-toolkit/cdi-generate.nix
@@ -1,4 +1,5 @@
 {
+  deviceNameStrategy,
   glibc,
   jq,
   lib,
@@ -25,6 +26,7 @@ writeScriptBin "nvidia-cdi-generator"
 function cdiGenerate {
   ${lib.getExe' nvidia-container-toolkit "nvidia-ctk"} cdi generate \
     --format json \
+    --device-name-strategy ${deviceNameStrategy} \
     --ldconfig-path ${lib.getExe' glibc "ldconfig"} \
     --library-search-path ${lib.getLib nvidia-driver}/lib \
     --nvidia-ctk-path ${lib.getExe' nvidia-container-toolkit "nvidia-ctk"}
diff --git a/nixos/modules/services/hardware/nvidia-container-toolkit/default.nix b/nixos/modules/services/hardware/nvidia-container-toolkit/default.nix
index bd12667a56474..fe4999bfbdbbe 100644
--- a/nixos/modules/services/hardware/nvidia-container-toolkit/default.nix
+++ b/nixos/modules/services/hardware/nvidia-container-toolkit/default.nix
@@ -52,6 +52,17 @@
         '';
       };
 
+      device-name-strategy = lib.mkOption {
+        default = "index";
+        type = lib.types.enum [ "index" "uuid" "type-index" ];
+        description = ''
+          Specify the strategy for generating device names,
+          passed to `nvidia-ctk cdi generate`. This will affect how
+          you reference the device using `nvidia.com/gpu=` in
+          the container runtime.
+        '';
+      };
+
       mount-nvidia-docker-1-directories = lib.mkOption {
         default = true;
         type = lib.types.bool;
@@ -119,6 +130,7 @@
             script = pkgs.callPackage ./cdi-generate.nix {
               inherit (config.hardware.nvidia-container-toolkit) mounts;
               nvidia-driver = config.hardware.nvidia.package;
+              deviceNameStrategy = config.hardware.nvidia-container-toolkit.device-name-strategy;
             };
           in
           lib.getExe script;
diff --git a/nixos/modules/services/hardware/openrgb.nix b/nixos/modules/services/hardware/openrgb.nix
index 1f7e4ffb9265a..4dc521aa97800 100644
--- a/nixos/modules/services/hardware/openrgb.nix
+++ b/nixos/modules/services/hardware/openrgb.nix
@@ -51,5 +51,5 @@ in {
     };
   };
 
-  meta.maintainers = with lib.maintainers; [ ];
+  meta.maintainers = [ ];
 }
diff --git a/nixos/modules/services/hardware/sane.nix b/nixos/modules/services/hardware/sane.nix
index 5f166bb320baf..b6b36b6b60f46 100644
--- a/nixos/modules/services/hardware/sane.nix
+++ b/nixos/modules/services/hardware/sane.nix
@@ -177,6 +177,10 @@ in
 
       users.groups.scanner.gid = config.ids.gids.scanner;
       networking.firewall.allowedUDPPorts = mkIf config.hardware.sane.openFirewall [ 8612 ];
+
+      systemd.tmpfiles.rules = [
+        "d /var/lock/sane 0770 root scanner - -"
+      ];
     })
 
     (mkIf config.services.saned.enable {
diff --git a/nixos/modules/services/hardware/udev.nix b/nixos/modules/services/hardware/udev.nix
index 62603d20e2d30..67956fdd6c763 100644
--- a/nixos/modules/services/hardware/udev.nix
+++ b/nixos/modules/services/hardware/udev.nix
@@ -29,9 +29,6 @@ let
   };
 
   nixosRules = ''
-    # Miscellaneous devices.
-    KERNEL=="kvm",                  MODE="0666"
-
     # Needed for gpm.
     SUBSYSTEM=="input", KERNEL=="mice", TAG+="systemd"
   '';
@@ -93,7 +90,7 @@ let
       echo "OK"
 
       echo -n "Checking that all programs called by absolute paths in udev rules exist... "
-      import_progs=$(grep 'IMPORT{program}="\/' $out/* |
+      import_progs=$(grep 'IMPORT{program}="/' $out/* |
         sed -e 's/.*IMPORT{program}="\([^ "]*\)[ "].*/\1/' | uniq)
       run_progs=$(grep -v '^[[:space:]]*#' $out/* | grep 'RUN+="/' |
         sed -e 's/.*RUN+="\([^ "]*\)[ "].*/\1/' | uniq)
diff --git a/nixos/modules/services/home-automation/home-assistant.nix b/nixos/modules/services/home-automation/home-assistant.nix
index c58a31539ed81..6ffe314d45747 100644
--- a/nixos/modules/services/home-automation/home-assistant.nix
+++ b/nixos/modules/services/home-automation/home-assistant.nix
@@ -192,7 +192,12 @@ in {
     };
 
     customComponents = mkOption {
-      type = types.listOf types.package;
+      type = types.listOf (
+        types.addCheck types.package (p: p.isHomeAssistantComponent or false) // {
+          name = "home-assistant-component";
+          description = "package that is a Home Assistant component";
+        }
+      );
       default = [];
       example = literalExpression ''
         with pkgs.home-assistant-custom-components; [
diff --git a/nixos/modules/services/logging/graylog.nix b/nixos/modules/services/logging/graylog.nix
index 25982022c0684..caeac16815f42 100644
--- a/nixos/modules/services/logging/graylog.nix
+++ b/nixos/modules/services/logging/graylog.nix
@@ -15,6 +15,7 @@ let
     message_journal_dir = ${cfg.messageJournalDir}
     mongodb_uri = ${cfg.mongodbUri}
     plugin_dir = /var/lib/graylog/plugins
+    data_dir = ${cfg.dataDir}
 
     ${cfg.extraConfig}
   '';
@@ -93,6 +94,12 @@ in
         description = "List of valid URIs of the http ports of your elastic nodes. If one or more of your elasticsearch hosts require authentication, include the credentials in each node URI that requires authentication";
       };
 
+      dataDir = mkOption {
+        type = types.str;
+        default = "/var/lib/graylog/data";
+        description = "Directory used to store Graylog server state.";
+      };
+
       messageJournalDir = mkOption {
         type = types.str;
         default = "/var/lib/graylog/data/journal";
diff --git a/nixos/modules/services/mail/dovecot.nix b/nixos/modules/services/mail/dovecot.nix
index b2a6b3ab6784b..1d17dd66e0716 100644
--- a/nixos/modules/services/mail/dovecot.nix
+++ b/nixos/modules/services/mail/dovecot.nix
@@ -328,7 +328,7 @@ in
 
     createMailUser = mkEnableOption ''automatically creating the user
       given in {option}`services.dovecot.user` and the group
-      given in {option}`services.dovecot.group`.'' // { default = true; };
+      given in {option}`services.dovecot.group`'' // { default = true; };
 
     modules = mkOption {
       type = types.listOf types.package;
diff --git a/nixos/modules/services/mail/mailman.nix b/nixos/modules/services/mail/mailman.nix
index ab10206fea42e..b55a7422702fc 100644
--- a/nixos/modules/services/mail/mailman.nix
+++ b/nixos/modules/services/mail/mailman.nix
@@ -1,4 +1,4 @@
-{ config, pkgs, lib, ... }:          # mailman.nix
+{ config, pkgs, lib, ... }:
 
 with lib;
 
@@ -367,7 +367,7 @@ in {
           for more info.
         '';
       }
-      (requirePostfixHash [ "relayDomains" ] "postfix_domains")
+      (requirePostfixHash [ "config" "relay_domains" ] "postfix_domains")
       (requirePostfixHash [ "config" "transport_maps" ] "postfix_lmtp")
       (requirePostfixHash [ "config" "local_recipient_maps" ] "postfix_lmtp")
     ]);
@@ -445,6 +445,7 @@ in {
           "${removeSuffix "/" cfg.serve.virtualRoot}/static/".alias = webSettings.STATIC_ROOT + "/";
         };
       });
+      proxyTimeout = mkDefault "120s";
     };
 
     environment.systemPackages = [ (pkgs.buildEnv {
diff --git a/nixos/modules/services/mail/postfix.nix b/nixos/modules/services/mail/postfix.nix
index fd78c98d0cb41..6bb501959f95d 100644
--- a/nixos/modules/services/mail/postfix.nix
+++ b/nixos/modules/services/mail/postfix.nix
@@ -484,7 +484,7 @@ in
       };
 
       config = mkOption {
-        type = with types; attrsOf (oneOf [ bool str (listOf str) ]);
+        type = with types; attrsOf (oneOf [ bool int str (listOf str) ]);
         description = ''
           The main.cf configuration file as key value set.
         '';
diff --git a/nixos/modules/services/mail/protonmail-bridge.nix b/nixos/modules/services/mail/protonmail-bridge.nix
new file mode 100644
index 0000000000000..da58d9731f049
--- /dev/null
+++ b/nixos/modules/services/mail/protonmail-bridge.nix
@@ -0,0 +1,60 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  cfg = config.services.protonmail-bridge;
+in
+{
+  options.services.protonmail-bridge = {
+    enable = lib.mkEnableOption "protonmail bridge";
+
+    package = lib.mkPackageOption pkgs "protonmail-bridge" { };
+
+    path = lib.mkOption {
+      type = lib.types.listOf lib.types.path;
+      default = [ ];
+      example = lib.literalExpression "with pkgs; [ pass gnome-keyring ]";
+      description = "List of derivations to put in protonmail-bride's path.";
+    };
+
+    logLevel = lib.mkOption {
+      type = lib.types.nullOr (
+        lib.types.enum [
+          "panic"
+          "fatal"
+          "error"
+          "warn"
+          "info"
+          "debug"
+        ]
+      );
+      default = null;
+      description = "Log level of the Proton Mail Bridge service. If set to null then the service uses it's default log level.";
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.user.services.protonmail-bridge = {
+      description = "protonmail bridge";
+      wantedBy = [ "graphical-session.target" ];
+      after = [ "graphical-session.target" ];
+
+      serviceConfig =
+        let
+          logLevel = lib.optionalString (cfg.logLevel != null) "--log-level ${cfg.logLevel}";
+        in
+        {
+          ExecStart = "${lib.getExe cfg.package} --noninteractive ${logLevel}";
+          Restart = "always";
+        };
+
+      path = cfg.path;
+    };
+    environment.systemPackages = [ cfg.package ];
+  };
+  meta.maintainers = with lib.maintainers; [ mzacho ];
+}
diff --git a/nixos/modules/services/mail/roundcube.nix b/nixos/modules/services/mail/roundcube.nix
index 78f627d33e2df..2914877bdccde 100644
--- a/nixos/modules/services/mail/roundcube.nix
+++ b/nixos/modules/services/mail/roundcube.nix
@@ -93,13 +93,17 @@ in
     maxAttachmentSize = mkOption {
       type = types.int;
       default = 18;
+      apply = configuredMaxAttachmentSize: "${toString (configuredMaxAttachmentSize * 1.37)}M";
       description = ''
         The maximum attachment size in MB.
-
-        Note: Since roundcube only uses 70% of max upload values configured in php
-        30% is added automatically to [](#opt-services.roundcube.maxAttachmentSize).
+        [upstream issue comment]: https://github.com/roundcube/roundcubemail/issues/7979#issuecomment-808879209
+        ::: {.note}
+        Since there is some overhead in base64 encoding applied to attachments, + 37% will be added
+        to the value set in this option in order to offset the overhead. For example, setting
+        `maxAttachmentSize` to `100` would result in `137M` being the real value in the configuration.
+        See [upstream issue comment] for more details on the motivations behind this.
+        :::
       '';
-      apply = configuredMaxAttachmentSize: "${toString (configuredMaxAttachmentSize * 1.3)}M";
     };
 
     configureNginx = lib.mkOption {
diff --git a/nixos/modules/services/misc/airsonic.nix b/nixos/modules/services/misc/airsonic.nix
index 6095268eb9608..d980069608e79 100644
--- a/nixos/modules/services/misc/airsonic.nix
+++ b/nixos/modules/services/misc/airsonic.nix
@@ -141,7 +141,7 @@ in {
           -Dairsonic.home=${cfg.home} \
           -Dserver.address=${cfg.listenAddress} \
           -Dserver.port=${toString cfg.port} \
-          -Dairsonic.contextPath=${cfg.contextPath} \
+          -Dserver.context-path=${cfg.contextPath} \
           -Djava.awt.headless=true \
           ${optionalString (cfg.virtualHost != null)
             "-Dserver.use-forward-headers=true"} \
diff --git a/nixos/modules/services/misc/ananicy.nix b/nixos/modules/services/misc/ananicy.nix
index f7ab41fcce61d..2129868a2919b 100644
--- a/nixos/modules/services/misc/ananicy.nix
+++ b/nixos/modules/services/misc/ananicy.nix
@@ -1,85 +1,119 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
 
 let
   cfg = config.services.ananicy;
-  configFile = pkgs.writeText "ananicy.conf" (generators.toKeyValue { } cfg.settings);
-  extraRules = pkgs.writeText "extraRules" (concatMapStringsSep "\n" (l: builtins.toJSON l) cfg.extraRules);
-  extraTypes = pkgs.writeText "extraTypes" (concatMapStringsSep "\n" (l: builtins.toJSON l) cfg.extraTypes);
-  extraCgroups = pkgs.writeText "extraCgroups" (concatMapStringsSep "\n" (l: builtins.toJSON l) cfg.extraCgroups);
-  servicename = if ((lib.getName cfg.package) == (lib.getName pkgs.ananicy-cpp)) then "ananicy-cpp" else "ananicy";
+  configFile = pkgs.writeText "ananicy.conf" (lib.generators.toKeyValue { } cfg.settings);
+  extraRules = pkgs.writeText "extraRules" (
+    lib.concatMapStringsSep "\n" (l: builtins.toJSON l) cfg.extraRules
+  );
+  extraTypes = pkgs.writeText "extraTypes" (
+    lib.concatMapStringsSep "\n" (l: builtins.toJSON l) cfg.extraTypes
+  );
+  extraCgroups = pkgs.writeText "extraCgroups" (
+    lib.concatMapStringsSep "\n" (l: builtins.toJSON l) cfg.extraCgroups
+  );
+  servicename =
+    if ((lib.getName cfg.package) == (lib.getName pkgs.ananicy-cpp)) then "ananicy-cpp" else "ananicy";
+  # Ananicy-CPP with BPF is not supported on hardened kernels https://github.com/NixOS/nixpkgs/issues/327382
+  finalPackage =
+    if (servicename == "ananicy-cpp" && config.boot.kernelPackages.isHardened) then
+      (cfg.package { withBpf = false; })
+    else
+      cfg.package;
 in
 {
-  options = {
-    services.ananicy = {
-      enable = mkEnableOption "Ananicy, an auto nice daemon";
+  options.services.ananicy = {
+    enable = lib.mkEnableOption "Ananicy, an auto nice daemon";
 
-      package = mkPackageOption pkgs "ananicy" {
-        example = "ananicy-cpp";
-      };
+    package = lib.mkPackageOption pkgs "ananicy" { example = "ananicy-cpp"; };
 
-      rulesProvider = mkPackageOption pkgs "ananicy" {
-        example = "ananicy-cpp";
-      } // {
-        description = ''
-          Which package to copy default rules,types,cgroups from.
-        '';
-      };
+    rulesProvider = lib.mkPackageOption pkgs "ananicy" { example = "ananicy-cpp"; } // {
+      description = ''
+        Which package to copy default rules,types,cgroups from.
+      '';
+    };
 
-      settings = mkOption {
-        type = with types; attrsOf (oneOf [ int bool str ]);
-        default = { };
-        example = {
-          apply_nice = false;
-        };
-        description = ''
-          See <https://github.com/Nefelim4ag/Ananicy/blob/master/ananicy.d/ananicy.conf>
-        '';
+    settings = lib.mkOption {
+      type =
+        with lib.types;
+        attrsOf (oneOf [
+          int
+          bool
+          str
+        ]);
+      default = { };
+      example = {
+        apply_nice = false;
       };
+      description = ''
+        See <https://github.com/Nefelim4ag/Ananicy/blob/master/ananicy.d/ananicy.conf>
+      '';
+    };
 
-      extraRules = mkOption {
-        type = with types; listOf attrs;
-        default = [ ];
-        description = ''
-          Rules to write in 'nixRules.rules'. See:
-          <https://github.com/Nefelim4ag/Ananicy#configuration>
-          <https://gitlab.com/ananicy-cpp/ananicy-cpp/#global-configuration>
-        '';
-        example = [
-          { name = "eog"; type = "Image-Viewer"; }
-          { name = "fdupes"; type = "BG_CPUIO"; }
-        ];
-      };
-      extraTypes = mkOption {
-        type = with types; listOf attrs;
-        default = [ ];
-        description = ''
-          Types to write in 'nixTypes.types'. See:
-          <https://gitlab.com/ananicy-cpp/ananicy-cpp/#types>
-        '';
-        example = [
-          { type = "my_type"; nice = 19; other_parameter = "value"; }
-          { type = "compiler"; nice = 19; sched = "batch"; ioclass = "idle"; }
-        ];
-      };
-      extraCgroups = mkOption {
-        type = with types; listOf attrs;
-        default = [ ];
-        description = ''
-          Cgroups to write in 'nixCgroups.cgroups'. See:
-          <https://gitlab.com/ananicy-cpp/ananicy-cpp/#cgroups>
-        '';
-        example = [
-          { cgroup = "cpu80"; CPUQuota = 80; }
-        ];
-      };
+    extraRules = lib.mkOption {
+      type = with lib.types; listOf attrs;
+      default = [ ];
+      description = ''
+        Rules to write in 'nixRules.rules'. See:
+        <https://github.com/Nefelim4ag/Ananicy#configuration>
+        <https://gitlab.com/ananicy-cpp/ananicy-cpp/#global-configuration>
+      '';
+      example = [
+        {
+          name = "eog";
+          type = "Image-Viewer";
+        }
+        {
+          name = "fdupes";
+          type = "BG_CPUIO";
+        }
+      ];
+    };
+    extraTypes = lib.mkOption {
+      type = with lib.types; listOf attrs;
+      default = [ ];
+      description = ''
+        Types to write in 'nixTypes.types'. See:
+        <https://gitlab.com/ananicy-cpp/ananicy-cpp/#types>
+      '';
+      example = [
+        {
+          type = "my_type";
+          nice = 19;
+          other_parameter = "value";
+        }
+        {
+          type = "compiler";
+          nice = 19;
+          sched = "batch";
+          ioclass = "idle";
+        }
+      ];
+    };
+    extraCgroups = lib.mkOption {
+      type = with lib.types; listOf attrs;
+      default = [ ];
+      description = ''
+        Cgroups to write in 'nixCgroups.cgroups'. See:
+        <https://gitlab.com/ananicy-cpp/ananicy-cpp/#cgroups>
+      '';
+      example = [
+        {
+          cgroup = "cpu80";
+          CPUQuota = 80;
+        }
+      ];
     };
   };
 
-  config = mkIf cfg.enable {
+  config = lib.mkIf cfg.enable {
     environment = {
-      systemPackages = [ cfg.package ];
+      systemPackages = [ finalPackage ];
       etc."ananicy.d".source = pkgs.runCommandLocal "ananicyfiles" { } ''
         mkdir -p $out
         # ananicy-cpp does not include rules or settings on purpose
@@ -92,16 +126,16 @@ in
         # configured through .setings
         rm -f $out/ananicy.conf
         cp ${configFile} $out/ananicy.conf
-        ${optionalString (cfg.extraRules != [ ]) "cp ${extraRules} $out/nixRules.rules"}
-        ${optionalString (cfg.extraTypes != [ ]) "cp ${extraTypes} $out/nixTypes.types"}
-        ${optionalString (cfg.extraCgroups != [ ]) "cp ${extraCgroups} $out/nixCgroups.cgroups"}
+        ${lib.optionalString (cfg.extraRules != [ ]) "cp ${extraRules} $out/nixRules.rules"}
+        ${lib.optionalString (cfg.extraTypes != [ ]) "cp ${extraTypes} $out/nixTypes.types"}
+        ${lib.optionalString (cfg.extraCgroups != [ ]) "cp ${extraCgroups} $out/nixCgroups.cgroups"}
       '';
     };
 
     # ananicy and ananicy-cpp have different default settings
     services.ananicy.settings =
       let
-        mkOD = mkOptionDefault;
+        mkOD = lib.mkOptionDefault;
       in
       {
         cgroup_load = mkOD true;
@@ -113,28 +147,30 @@ in
         apply_sched = mkOD true;
         apply_oom_score_adj = mkOD true;
         apply_cgroup = mkOD true;
-      } // (if ((lib.getName cfg.package) == (lib.getName pkgs.ananicy-cpp)) then {
-        # https://gitlab.com/ananicy-cpp/ananicy-cpp/-/blob/master/src/config.cpp#L12
-        loglevel = mkOD "warn"; # default is info but its spammy
-        cgroup_realtime_workaround = mkOD config.systemd.enableUnifiedCgroupHierarchy;
-        log_applied_rule = mkOD false;
-      } else {
-        # https://github.com/Nefelim4ag/Ananicy/blob/master/ananicy.d/ananicy.conf
-        check_disks_schedulers = mkOD true;
-        check_freq = mkOD 5;
-      });
+      }
+      // (
+        if servicename == "ananicy-cpp" then
+          {
+            # https://gitlab.com/ananicy-cpp/ananicy-cpp/-/blob/master/src/config.cpp#L12
+            loglevel = mkOD "warn"; # default is info but its spammy
+            cgroup_realtime_workaround = true;
+            log_applied_rule = mkOD false;
+          }
+        else
+          {
+            # https://github.com/Nefelim4ag/Ananicy/blob/master/ananicy.d/ananicy.conf
+            check_disks_schedulers = mkOD true;
+            check_freq = mkOD 5;
+          }
+      );
 
     systemd = {
-      # https://gitlab.com/ananicy-cpp/ananicy-cpp/#cgroups applies to both ananicy and -cpp
-      enableUnifiedCgroupHierarchy = mkDefault false;
-      packages = [ cfg.package ];
+      packages = [ finalPackage ];
       services."${servicename}" = {
         wantedBy = [ "default.target" ];
       };
     };
   };
 
-  meta = {
-    maintainers = with maintainers; [ artturin ];
-  };
+  meta.maintainers = with lib.maintainers; [ artturin ];
 }
diff --git a/nixos/modules/services/misc/blenderfarm.nix b/nixos/modules/services/misc/blenderfarm.nix
new file mode 100644
index 0000000000000..0d8ecf7af8e20
--- /dev/null
+++ b/nixos/modules/services/misc/blenderfarm.nix
@@ -0,0 +1,141 @@
+{ config
+, lib
+, pkgs
+, ...
+}:
+let
+  cfg = config.services.blendfarm;
+  json = pkgs.formats.json { };
+  configFile = json.generate "ServerSettings" (defaultConfig // cfg.serverConfig);
+  defaultConfig = {
+    Port = 15000;
+    BroadcastPort = 16342;
+    BypassScriptUpdate = false;
+    BasicSecurityPassword = null;
+  };
+in
+{
+  meta.maintainers = with lib.maintainers; [ gador ];
+
+  options.services.blendfarm = with lib.types; {
+    enable = lib.mkEnableOption "Blendfarm, a render farm management software for Blender";
+    package = lib.mkPackageOption pkgs "blendfarm" { };
+    openFirewall = lib.mkEnableOption "allowing blendfarm network access through the firewall";
+
+    user = lib.mkOption {
+      description = "User under which blendfarm runs.";
+      default = "blendfarm";
+      type = str;
+    };
+
+    group = lib.mkOption {
+      description = "Group under which blendfarm runs.";
+      default = "blendfarm";
+      type = str;
+    };
+
+    basicSecurityPasswordFile = lib.mkOption {
+      description = ''Path to the password file the client needs to connect to the server.
+      The password must not contain a forward slash.'';
+      default = null;
+      type = nullOr str;
+    };
+
+    blenderPackage = lib.mkPackageOption pkgs "blender" { };
+
+    serverConfig = lib.mkOption {
+      description = "Server configuration";
+      default = defaultConfig;
+      type = submodule {
+        freeformType = attrsOf anything;
+        options = {
+          Port = lib.mkOption {
+            description = "Default port blendfarm server listens on.";
+            default = 15000;
+            type = types.port;
+          };
+          BroadcastPort = lib.mkOption {
+            description = "Default port blendfarm server advertises itself on.";
+            default = 16342;
+            type = types.port;
+          };
+
+          BypassScriptUpdate = lib.mkOption {
+            description = "Prevents blendfarm from replacing the .py self-generated scripts.";
+            default = false;
+            type = bool;
+          };
+        };
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+    networking.firewall = lib.optionalAttrs (cfg.openFirewall) {
+      allowedTCPPorts = [ cfg.serverConfig.Port ];
+      allowedUDPPorts = [ cfg.serverConfig.BroadcastPort ];
+    };
+
+    systemd.services.blendfarm-server = {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" ];
+      wants = [ "network-online.target" ];
+      description = "blendfarm server";
+      path = [ cfg.blenderPackage ];
+      preStart = ''
+        rm -f ServerSettings
+        install -m640 ${configFile} ServerSettings
+        if [ ! -d "BlenderData/nix-blender-linux64" ]; then
+          mkdir -p BlenderData/nix-blender-linux64
+          echo "nix-blender" > VersionCustom
+        fi
+        rm -f BlenderData/nix-blender-linux64/blender
+        ln -s ${lib.getExe cfg.blenderPackage} BlenderData/nix-blender-linux64/blender
+      '' +
+      lib.optionalString (cfg.basicSecurityPasswordFile != null) ''
+        BLENDFARM_PASSWORD=$(${pkgs.systemd}/bin/systemd-creds cat BLENDFARM_PASS_FILE)
+        sed -i "s/null/\"$BLENDFARM_PASSWORD\"/g" ServerSettings
+      '';
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/LogicReinc.BlendFarm.Server";
+        DynamicUser = true;
+        LogsDirectory = "blendfarm";
+        StateDirectory = "blendfarm";
+        WorkingDirectory = "/var/lib/blendfarm";
+        User = cfg.user;
+        Group = cfg.group;
+        StateDirectoryMode = "0755";
+        LoadCredential = lib.optional (cfg.basicSecurityPasswordFile != null) "BLENDFARM_PASS_FILE:${cfg.basicSecurityPasswordFile}";
+        ReadWritePaths = "";
+        CapabilityBoundingSet = "";
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [
+          "@system-service"
+          "~@privileged"
+          "@chown"
+        ];
+        RestrictRealtime = true;
+        LockPersonality = true;
+        UMask = "0066";
+        ProtectHostname = true;
+      };
+    };
+
+    users.users.blendfarm = {
+      isSystemUser = true;
+      group = "blendfarm";
+    };
+    users.groups.blendfarm = { };
+  };
+}
diff --git a/nixos/modules/services/misc/dictd.nix b/nixos/modules/services/misc/dictd.nix
index 8cb51bb0b7a7f..6660d5e977ffb 100644
--- a/nixos/modules/services/misc/dictd.nix
+++ b/nixos/modules/services/misc/dictd.nix
@@ -62,6 +62,9 @@ in
       description = "DICT.org Dictionary Server";
       wantedBy = [ "multi-user.target" ];
       environment = { LOCALE_ARCHIVE = "/run/current-system/sw/lib/locale/locale-archive"; };
+      # Work around the fact that dictd doesn't handle SIGTERM; it terminates
+      # with code 143 instead of exiting with code 0.
+      serviceConfig.SuccessExitStatus = [ 143 ];
       serviceConfig.Type = "forking";
       script = "${pkgs.dict}/sbin/dictd -s -c ${dictdb}/share/dictd/dictd.conf --locale en_US.UTF-8";
     };
diff --git a/nixos/modules/services/misc/etebase-server.nix b/nixos/modules/services/misc/etebase-server.nix
index 7b6b5249f230c..9b2ba34cc30ba 100644
--- a/nixos/modules/services/misc/etebase-server.nix
+++ b/nixos/modules/services/misc/etebase-server.nix
@@ -45,7 +45,7 @@ in
 
       package = mkOption {
         type = types.package;
-        default = pkgs.python3.pkgs.etebase-server;
+        default = pkgs.etebase-server;
         defaultText = literalExpression "pkgs.python3.pkgs.etebase-server";
         description = "etebase-server package to use.";
       };
diff --git a/nixos/modules/services/misc/flaresolverr.nix b/nixos/modules/services/misc/flaresolverr.nix
new file mode 100644
index 0000000000000..7967580307f99
--- /dev/null
+++ b/nixos/modules/services/misc/flaresolverr.nix
@@ -0,0 +1,58 @@
+{
+  config,
+  pkgs,
+  lib,
+  ...
+}:
+
+let
+  cfg = config.services.flaresolverr;
+in
+{
+  options = {
+    services.flaresolverr = {
+      enable = lib.mkEnableOption "FlareSolverr, a proxy server to bypass Cloudflare protection";
+
+      package = lib.mkPackageOption pkgs "flaresolverr" { };
+
+      openFirewall = lib.mkOption {
+        type = lib.types.bool;
+        default = false;
+        description = "Open the port in the firewall for FlareSolverr.";
+      };
+
+      port = lib.mkOption {
+        type = lib.types.port;
+        default = 8191;
+        description = "The port on which FlareSolverr will listen for incoming HTTP traffic.";
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.flaresolverr = {
+      description = "FlareSolverr";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      environment = {
+        HOME = "/run/flaresolverr";
+        PORT = toString cfg.port;
+      };
+
+      serviceConfig = {
+        SyslogIdentifier = "flaresolverr";
+        Restart = "always";
+        RestartSec = 5;
+        Type = "simple";
+        DynamicUser = true;
+        RuntimeDirectory = "flaresolverr";
+        WorkingDirectory = "/run/flaresolverr";
+        ExecStart = lib.getExe cfg.package;
+        TimeoutStopSec = 30;
+      };
+    };
+
+    networking.firewall = lib.mkIf cfg.openFirewall { allowedTCPPorts = [ cfg.port ]; };
+  };
+}
diff --git a/nixos/modules/services/misc/forgejo.nix b/nixos/modules/services/misc/forgejo.nix
index 9a102918f35e3..9aa7b13b02e19 100644
--- a/nixos/modules/services/misc/forgejo.nix
+++ b/nixos/modules/services/misc/forgejo.nix
@@ -66,7 +66,7 @@ in
     services.forgejo = {
       enable = mkEnableOption "Forgejo, a software forge";
 
-      package = mkPackageOption pkgs "forgejo" { };
+      package = mkPackageOption pkgs "forgejo-lts" { };
 
       useWizard = mkOption {
         default = false;
diff --git a/nixos/modules/services/misc/fstrim.nix b/nixos/modules/services/misc/fstrim.nix
index d2dda2636ef1b..10dced2c4e64b 100644
--- a/nixos/modules/services/misc/fstrim.nix
+++ b/nixos/modules/services/misc/fstrim.nix
@@ -41,5 +41,5 @@ in {
 
   };
 
-  meta.maintainers = with maintainers; [ ];
+  meta.maintainers = [ ];
 }
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index 492c669f180a9..9fd6014f2c71c 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -9,6 +9,8 @@ let
   toml = pkgs.formats.toml {};
   yaml = pkgs.formats.yaml {};
 
+  git = cfg.packages.gitaly.git;
+
   postgresqlPackage = if config.services.postgresql.enable then
                         config.services.postgresql.package
                       else
@@ -51,7 +53,7 @@ let
     prometheus_listen_addr = "localhost:9236"
 
     [git]
-    bin_path = "${pkgs.git}/bin/git"
+    bin_path = "${git}/bin/git"
 
     [gitlab-shell]
     dir = "${cfg.packages.gitlab-shell}"
@@ -184,16 +186,15 @@ let
     MALLOC_ARENA_MAX = "2";
   } // cfg.extraEnv;
 
-  runtimeDeps = with pkgs; [
+  runtimeDeps = [ git ] ++ (with pkgs; [
     nodejs
     gzip
-    git
     gnutar
     postgresqlPackage
     coreutils
     procps
     findutils # Needed for gitlab:cleanup:orphan_job_artifact_files
-  ];
+  ]);
 
   gitlab-rake = pkgs.stdenv.mkDerivation {
     name = "gitlab-rake";
@@ -1124,7 +1125,7 @@ in {
       }
     ];
 
-    environment.systemPackages = [ pkgs.git gitlab-rake gitlab-rails cfg.packages.gitlab-shell ];
+    environment.systemPackages = [ gitlab-rake gitlab-rails cfg.packages.gitlab-shell ];
 
     systemd.targets.gitlab = {
       description = "Common target for all GitLab services.";
@@ -1295,12 +1296,11 @@ in {
     systemd.services.gitlab-config = {
       wantedBy = [ "gitlab.target" ];
       partOf = [ "gitlab.target" ];
-      path = with pkgs; [
+      path = [ git ] ++ (with pkgs; [
         jq
         openssl
         replace-secret
-        git
-      ];
+      ]);
       serviceConfig = {
         Type = "oneshot";
         User = cfg.user;
@@ -1338,7 +1338,7 @@ in {
           ln -sf ${cableYml} ${cfg.statePath}/config/cable.yml
           ln -sf ${resqueYml} ${cfg.statePath}/config/resque.yml
 
-          ${cfg.packages.gitlab-shell}/bin/install
+          ${cfg.packages.gitlab-shell}/bin/gitlab-shell-install
 
           ${optionalString cfg.smtp.enable ''
               install -m u=rw ${smtpSettings} ${cfg.statePath}/config/initializers/smtp_settings.rb
@@ -1458,9 +1458,8 @@ in {
         SIDEKIQ_MEMORY_KILLER_GRACE_TIME = cfg.sidekiq.memoryKiller.graceTime;
         SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT = cfg.sidekiq.memoryKiller.shutdownWait;
       });
-      path = with pkgs; [
+      path = [ git ] ++ (with pkgs; [
         postgresqlPackage
-        git
         ruby
         openssh
         nodejs
@@ -1473,7 +1472,7 @@ in {
         gzip
 
         procps # Sidekiq MemoryKiller
-      ];
+      ]);
       serviceConfig = {
         Type = "simple";
         User = cfg.user;
@@ -1500,12 +1499,11 @@ in {
       bindsTo = [ "gitlab-config.service" ];
       wantedBy = [ "gitlab.target" ];
       partOf = [ "gitlab.target" ];
-      path = with pkgs; [
+      path = [ git ] ++ (with pkgs; [
         openssh
-        git
         gzip
         bzip2
-      ];
+      ]);
       serviceConfig = {
         Type = "simple";
         User = cfg.user;
@@ -1582,7 +1580,7 @@ in {
       after = [ "network.target" ];
       wantedBy = [ "gitlab.target" ];
       partOf = [ "gitlab.target" ];
-      path = with pkgs; [
+      path = [ git ] ++ (with pkgs; [
         remarshal
         exiftool
         git
@@ -1590,7 +1588,7 @@ in {
         gzip
         openssh
         cfg.packages.gitlab-workhorse
-      ];
+      ]);
       serviceConfig = {
         Type = "simple";
         User = cfg.user;
@@ -1658,15 +1656,14 @@ in {
       requiredBy = [ "gitlab.target" ];
       partOf = [ "gitlab.target" ];
       environment = gitlabEnv;
-      path = with pkgs; [
+      path = [ git ] ++ (with pkgs; [
         postgresqlPackage
-        git
         openssh
         nodejs
         procps
         gnupg
         gzip
-      ];
+      ]);
       serviceConfig = {
         Type = "notify";
         User = cfg.user;
diff --git a/nixos/modules/services/misc/gitweb.nix b/nixos/modules/services/misc/gitweb.nix
index ec08ab51a4574..8f4869ce5d559 100644
--- a/nixos/modules/services/misc/gitweb.nix
+++ b/nixos/modules/services/misc/gitweb.nix
@@ -55,6 +55,6 @@ in
 
   };
 
-  meta.maintainers = with maintainers; [ ];
+  meta.maintainers = [ ];
 
 }
diff --git a/nixos/modules/services/misc/gollum.nix b/nixos/modules/services/misc/gollum.nix
index f320e78a91060..fb9b9e19813f1 100644
--- a/nixos/modules/services/misc/gollum.nix
+++ b/nixos/modules/services/misc/gollum.nix
@@ -1,4 +1,9 @@
-{ config, lib, pkgs, ... }:
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
 
 with lib;
 
@@ -7,6 +12,17 @@ let
 in
 
 {
+  imports = [
+    (mkRemovedOptionModule
+      [
+        "services"
+        "gollum"
+        "mathjax"
+      ]
+      "MathJax rendering might be discontinued in the future, use services.gollum.math instead to enable KaTeX rendering or file a PR if you really need Mathjax"
+    )
+  ];
+
   options.services.gollum = {
     enable = mkEnableOption "Gollum, a git-powered wiki service";
 
@@ -28,20 +44,30 @@ in
       description = "Content of the configuration file";
     };
 
-    mathjax = mkOption {
+    math = mkOption {
       type = types.bool;
       default = false;
-      description = "Enable support for math rendering using MathJax";
+      description = "Enable support for math rendering using KaTeX";
     };
 
     allowUploads = mkOption {
-      type = types.nullOr (types.enum [ "dir" "page" ]);
+      type = types.nullOr (
+        types.enum [
+          "dir"
+          "page"
+        ]
+      );
       default = null;
       description = "Enable uploads of external files";
     };
 
     user-icons = mkOption {
-      type = types.nullOr (types.enum [ "gravatar" "identicon" ]);
+      type = types.nullOr (
+        types.enum [
+          "gravatar"
+          "identicon"
+        ]
+      );
       default = null;
       description = "Enable specific user icons for history view";
     };
@@ -109,9 +135,7 @@ in
 
     users.groups."${cfg.group}" = { };
 
-    systemd.tmpfiles.rules = [
-      "d '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -"
-    ];
+    systemd.tmpfiles.rules = [ "d '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -" ];
 
     systemd.services.gollum = {
       description = "Gollum wiki";
@@ -134,7 +158,7 @@ in
             --host ${cfg.address} \
             --config ${pkgs.writeText "gollum-config.rb" cfg.extraConfig} \
             --ref ${cfg.branch} \
-            ${optionalString cfg.mathjax "--mathjax"} \
+            ${optionalString cfg.math "--math"} \
             ${optionalString cfg.emoji "--emoji"} \
             ${optionalString cfg.h1-title "--h1-title"} \
             ${optionalString cfg.no-edit "--no-edit"} \
@@ -147,5 +171,8 @@ in
     };
   };
 
-  meta.maintainers = with lib.maintainers; [ erictapen bbenno ];
+  meta.maintainers = with lib.maintainers; [
+    erictapen
+    bbenno
+  ];
 }
diff --git a/nixos/modules/services/misc/gotenberg.nix b/nixos/modules/services/misc/gotenberg.nix
new file mode 100644
index 0000000000000..57932c656d632
--- /dev/null
+++ b/nixos/modules/services/misc/gotenberg.nix
@@ -0,0 +1,258 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+let
+  cfg = config.services.gotenberg;
+
+  args =
+    [
+      "--api-port=${toString cfg.port}"
+      "--api-timeout=${cfg.timeout}"
+      "--api-root-path=${cfg.rootPath}"
+      "--log-level=${cfg.logLevel}"
+      "--chromium-max-queue-size=${toString cfg.chromium.maxQueueSize}"
+      "--libreoffice-restart-after=${toString cfg.libreoffice.restartAfter}"
+      "--libreoffice-max-queue-size=${toString cfg.libreoffice.maxQueueSize}"
+      "--pdfengines-engines=${lib.concatStringsSep "," cfg.pdfEngines}"
+    ]
+    ++ optional cfg.enableBasicAuth "--api-enable-basic-auth"
+    ++ optional cfg.chromium.autoStart "--chromium-auto-start"
+    ++ optional cfg.chromium.disableJavascript "--chromium-disable-javascript"
+    ++ optional cfg.chromium.disableRoutes "--chromium-disable-routes"
+    ++ optional cfg.libreoffice.autoStart "--libreoffice-auto-start"
+    ++ optional cfg.libreoffice.disableRoutes "--libreoffice-disable-routes";
+
+  inherit (lib)
+    mkEnableOption
+    mkPackageOption
+    mkOption
+    types
+    mkIf
+    optional
+    optionalAttrs
+    ;
+in
+{
+  options = {
+    services.gotenberg = {
+      enable = mkEnableOption "Gotenberg, a stateless API for PDF files";
+
+      # Users can override only gotenberg, libreoffice and chromium if they want to (eg. ungoogled-chromium, different LO version, etc)
+      # Don't allow setting the qpdf, pdftk, or unoconv paths, as those are very stable
+      # and there's only one version of each.
+      package = mkPackageOption pkgs "gotenberg" { };
+
+      port = mkOption {
+        type = types.port;
+        default = 3000;
+        description = "Port on which the API should listen.";
+      };
+
+      timeout = mkOption {
+        type = types.nullOr types.str;
+        default = "30s";
+        description = "Timeout for API requests.";
+      };
+
+      rootPath = mkOption {
+        type = types.str;
+        default = "/";
+        description = "Root path for the Gotenberg API.";
+      };
+
+      enableBasicAuth = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          HTTP Basic Authentication.
+
+          If you set this, be sure to set `GOTENBERG_API_BASIC_AUTH_USERNAME`and `GOTENBERG_API_BASIC_AUTH_PASSWORD`
+          in your `services.gotenberg.environmentFile` file.
+        '';
+      };
+
+      extraFontPackages = mkOption {
+        type = types.listOf types.package;
+        default = [ ];
+        description = "Extra fonts to make available.";
+      };
+
+      chromium = {
+        package = mkPackageOption pkgs "chromium" { };
+
+        maxQueueSize = mkOption {
+          type = types.int;
+          default = 0;
+          description = "Maximum queue size for chromium-based conversions. Setting to 0 disables the limit.";
+        };
+
+        autoStart = mkOption {
+          type = types.bool;
+          default = false;
+          description = "Automatically start chromium when Gotenberg starts. If false, Chromium will start on the first conversion request that uses it.";
+        };
+
+        disableJavascript = mkOption {
+          type = types.bool;
+          default = false;
+          description = "Disable Javascript execution.";
+        };
+
+        disableRoutes = mkOption {
+          type = types.bool;
+          default = false;
+          description = "Disable all routes allowing Chromium-based conversion.";
+        };
+      };
+
+      libreoffice = {
+        package = mkPackageOption pkgs "libreoffice" { };
+
+        restartAfter = mkOption {
+          type = types.int;
+          default = 10;
+          description = "Restart LibreOffice after this many conversions. Setting to 0 disables this feature.";
+        };
+
+        maxQueueSize = mkOption {
+          type = types.int;
+          default = 0;
+          description = "Maximum queue size for LibreOffice-based conversions. Setting to 0 disables the limit.";
+        };
+
+        autoStart = mkOption {
+          type = types.bool;
+          default = false;
+          description = "Automatically start LibreOffice when Gotenberg starts. If false, Chromium will start on the first conversion request that uses it.";
+        };
+
+        disableRoutes = mkOption {
+          type = types.bool;
+          default = false;
+          description = "Disable all routes allowing LibreOffice-based conversion.";
+        };
+      };
+
+      pdfEngines = mkOption {
+        type = types.listOf (
+          types.enum [
+            "pdftk"
+            "qpdf"
+            "libreoffice-pdfengine"
+            "exiftool"
+            "pdfcpu"
+          ]
+        );
+        default = [
+          "pdftk"
+          "qpdf"
+          "libreoffice-pdfengine"
+          "exiftool"
+          "pdfcpu"
+        ];
+        description = ''
+          PDF engines to enable. Each one can be used to perform a specific task.
+          See [the documentation](https://gotenberg.dev/docs/configuration#pdf-engines) for more details.
+          Defaults to all possible PDF engines.
+        '';
+      };
+
+      logLevel = mkOption {
+        type = types.enum [
+          "error"
+          "warn"
+          "info"
+          "debug"
+        ];
+        default = "info";
+        description = "The logging level for Gotenberg.";
+      };
+
+      environmentFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = "Environment file to load extra environment variables from.";
+      };
+
+      extraArgs = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        description = "Any extra command-line flags to pass to the Gotenberg service.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = cfg.enableBasicAuth -> cfg.environmentFile != null;
+        message = ''
+          When enabling HTTP Basic Authentication with `services.gotenberg.enableBasicAuth`,
+          you must provide an environment file via `services.gotenberg.environmentFile` with the appropriate environment variables set in it.
+
+          See `services.gotenberg.enableBasicAuth` for the names of those variables.
+        '';
+      }
+    ];
+
+    systemd.services.gotenberg = {
+      description = "Gotenberg API server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      path = [ cfg.package ];
+      environment = {
+        LIBREOFFICE_BIN_PATH = "${cfg.libreoffice.package}/lib/libreoffice/program/soffice.bin";
+        CHROMIUM_BIN_PATH = lib.getExe cfg.chromium.package;
+        FONTCONFIG_FILE = pkgs.makeFontsConf {
+          fontDirectories = [ pkgs.liberation_ttf_v2 ] ++ cfg.extraFontPackages;
+        };
+      };
+      serviceConfig = {
+        Type = "simple";
+        DynamicUser = true;
+        ExecStart = "${lib.getExe cfg.package} ${lib.escapeShellArgs args}";
+
+        # Hardening options
+        PrivateDevices = true;
+        PrivateIPC = true;
+        PrivateUsers = true;
+
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        ProcSubset = "pid";
+
+        RestrictAddressFamilies = [
+          "AF_UNIX"
+          "AF_INET"
+          "AF_INET6"
+          "AF_NETLINK"
+        ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+
+        SystemCallFilter = [
+          "@system-service"
+          "~@resources"
+          "~@privileged"
+        ];
+        SystemCallArchitectures = "native";
+
+        UMask = 77;
+      } // optionalAttrs (cfg.environmentFile != null) { EnvironmentFile = cfg.environmentFile; };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ pyrox0 ];
+}
diff --git a/nixos/modules/services/misc/graphical-desktop.nix b/nixos/modules/services/misc/graphical-desktop.nix
index c8fe0d921c6ad..246310195edc2 100644
--- a/nixos/modules/services/misc/graphical-desktop.nix
+++ b/nixos/modules/services/misc/graphical-desktop.nix
@@ -42,6 +42,8 @@ in
 
     programs.gnupg.agent.pinentryPackage = lib.mkOverride 1100 pkgs.pinentry-gnome3;
 
+    services.speechd.enable = lib.mkDefault true;
+
     systemd.defaultUnit = lib.mkIf (xcfg.autorun || dmcfg.enable) "graphical.target";
 
     xdg = {
diff --git a/nixos/modules/services/misc/jackett.nix b/nixos/modules/services/misc/jackett.nix
index 8b5011ce0d814..a843f400b0314 100644
--- a/nixos/modules/services/misc/jackett.nix
+++ b/nixos/modules/services/misc/jackett.nix
@@ -11,6 +11,14 @@ in
     services.jackett = {
       enable = mkEnableOption "Jackett, API support for your favorite torrent trackers";
 
+      port = mkOption {
+        default = 9117;
+        type = types.port;
+        description = ''
+          Port serving the web interface
+        '';
+      };
+
       dataDir = mkOption {
         type = types.str;
         default = "/var/lib/jackett/.config/Jackett";
@@ -53,13 +61,13 @@ in
         Type = "simple";
         User = cfg.user;
         Group = cfg.group;
-        ExecStart = "${cfg.package}/bin/Jackett --NoUpdates --DataFolder '${cfg.dataDir}'";
+        ExecStart = "${cfg.package}/bin/Jackett --NoUpdates --Port ${toString cfg.port} --DataFolder '${cfg.dataDir}'";
         Restart = "on-failure";
       };
     };
 
     networking.firewall = mkIf cfg.openFirewall {
-      allowedTCPPorts = [ 9117 ];
+      allowedTCPPorts = [ cfg.port ];
     };
 
     users.users = mkIf (cfg.user == "jackett") {
diff --git a/nixos/modules/services/misc/jellyseerr.nix b/nixos/modules/services/misc/jellyseerr.nix
index 7599a1af33840..9aab517e0493b 100644
--- a/nixos/modules/services/misc/jellyseerr.nix
+++ b/nixos/modules/services/misc/jellyseerr.nix
@@ -9,6 +9,7 @@ in
 
   options.services.jellyseerr = {
     enable = mkEnableOption ''Jellyseerr, a requests manager for Jellyfin'';
+    package = mkPackageOption pkgs "jellyseerr" { };
 
     openFirewall = mkOption {
       type = types.bool;
@@ -32,10 +33,10 @@ in
       serviceConfig = {
         Type = "exec";
         StateDirectory = "jellyseerr";
-        WorkingDirectory = "${pkgs.jellyseerr}/libexec/jellyseerr/deps/jellyseerr";
+        WorkingDirectory = "${cfg.package}/libexec/jellyseerr/deps/jellyseerr";
         DynamicUser = true;
-        ExecStart = "${pkgs.jellyseerr}/bin/jellyseerr";
-        BindPaths = [ "/var/lib/jellyseerr/:${pkgs.jellyseerr}/libexec/jellyseerr/deps/jellyseerr/config/" ];
+        ExecStart = lib.getExe cfg.package;
+        BindPaths = [ "/var/lib/jellyseerr/:${cfg.package}/libexec/jellyseerr/deps/jellyseerr/config/" ];
         Restart = "on-failure";
         ProtectHome = true;
         ProtectSystem = "strict";
diff --git a/nixos/modules/services/misc/languagetool.nix b/nixos/modules/services/misc/languagetool.nix
index ba563dace4737..2a7e68c9053a3 100644
--- a/nixos/modules/services/misc/languagetool.nix
+++ b/nixos/modules/services/misc/languagetool.nix
@@ -1,14 +1,17 @@
-{ config, lib, options, pkgs, ... }:
+{ config, lib, pkgs, ... }:
 
 with lib;
 
 let
   cfg = config.services.languagetool;
-  settingsFormat = pkgs.formats.javaProperties {};
-in {
+  settingsFormat = pkgs.formats.javaProperties { };
+in
+{
   options.services.languagetool = {
     enable = mkEnableOption "the LanguageTool server, a multilingual spelling, style, and grammar checker that helps correct or paraphrase texts";
 
+    package = mkPackageOption pkgs "languagetool" { };
+
     port = mkOption {
       type = types.port;
       default = 8081;
@@ -31,7 +34,7 @@ in {
       '';
     };
 
-    settings = lib.mkOption {
+    settings = mkOption {
       type = types.submodule {
         freeformType = settingsFormat.type;
 
@@ -49,11 +52,25 @@ in {
         for supported settings.
       '';
     };
+
+    jrePackage = mkPackageOption pkgs "jre" { };
+
+    jvmOptions = mkOption {
+      description = ''
+        Extra command line options for the JVM running languagetool.
+        More information can be found here: https://docs.oracle.com/en/java/javase/19/docs/specs/man/java.html#standard-options-for-java
+      '';
+      default = [ ];
+      type = types.listOf types.str;
+      example = [
+        "-Xmx512m"
+      ];
+    };
   };
 
   config = mkIf cfg.enable {
 
-    systemd.services.languagetool =  {
+    systemd.services.languagetool = {
       description = "LanguageTool HTTP server";
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
@@ -65,13 +82,17 @@ in {
         RestrictNamespaces = [ "" ];
         SystemCallFilter = [ "@system-service" "~ @privileged" ];
         ProtectHome = "yes";
+        Restart = "on-failure";
         ExecStart = ''
-          ${pkgs.languagetool}/bin/languagetool-http-server \
-            --port ${toString cfg.port} \
-            ${optionalString cfg.public "--public"} \
-            ${optionalString (cfg.allowOrigin != null) "--allow-origin ${cfg.allowOrigin}"} \
-            "--config" ${settingsFormat.generate "languagetool.conf" cfg.settings}
-          '';
+          ${cfg.jrePackage}/bin/java \
+            -cp ${cfg.package}/share/languagetool-server.jar \
+            ${toString cfg.jvmOptions} \
+            org.languagetool.server.HTTPServer \
+              --port ${toString cfg.port} \
+              ${optionalString cfg.public "--public"} \
+              ${optionalString (cfg.allowOrigin != null) "--allow-origin ${cfg.allowOrigin}"} \
+              "--config" ${settingsFormat.generate "languagetool.conf" cfg.settings}
+        '';
       };
     };
   };
diff --git a/nixos/modules/services/misc/mame.nix b/nixos/modules/services/misc/mame.nix
index 6c7f08d48be10..38b4dd290ed56 100644
--- a/nixos/modules/services/misc/mame.nix
+++ b/nixos/modules/services/misc/mame.nix
@@ -65,5 +65,5 @@ in
     };
   };
 
-  meta.maintainers = with lib.maintainers; [ ];
+  meta.maintainers = [ ];
 }
diff --git a/nixos/modules/services/misc/ollama.nix b/nixos/modules/services/misc/ollama.nix
index a0a32f1702bf3..f8dbfe9c5692d 100644
--- a/nixos/modules/services/misc/ollama.nix
+++ b/nixos/modules/services/misc/ollama.nix
@@ -1,75 +1,91 @@
-{ config, lib, pkgs, ... }:
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
 let
-  inherit (lib) types mkBefore;
+  inherit (lib) literalExpression types mkBefore;
 
   cfg = config.services.ollama;
-  ollamaPackage = cfg.package.override {
-    inherit (cfg) acceleration;
-  };
+  ollamaPackage = cfg.package.override { inherit (cfg) acceleration; };
+
+  staticUser = cfg.user != null && cfg.group != null;
 in
 {
   imports = [
-    (lib.mkRemovedOptionModule [ "services" "ollama" "listenAddress" ]
-      "Use `services.ollama.host` and `services.ollama.port` instead.")
+    (lib.mkRemovedOptionModule [
+      "services"
+      "ollama"
+      "listenAddress"
+    ] "Use `services.ollama.host` and `services.ollama.port` instead.")
+    (lib.mkRemovedOptionModule [
+      "services"
+      "ollama"
+      "sandbox"
+    ] "Set `services.ollama.user` and `services.ollama.group` instead.")
+    (lib.mkRemovedOptionModule
+      [
+        "services"
+        "ollama"
+        "writablePaths"
+      ]
+      "The `models` directory is now always writable. To make other directories writable, use `systemd.services.ollama.serviceConfig.ReadWritePaths`."
+    )
   ];
 
   options = {
     services.ollama = {
       enable = lib.mkEnableOption "ollama server for local large language models";
       package = lib.mkPackageOption pkgs "ollama" { };
+
+      user = lib.mkOption {
+        type = with types; nullOr str;
+        default = null;
+        example = "ollama";
+        description = ''
+          User account under which to run ollama. Defaults to [`DynamicUser`](https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#DynamicUser=)
+          when set to `null`.
+
+          The user will automatically be created, if this option is set to a non-null value.
+        '';
+      };
+
+      group = lib.mkOption {
+        type = with types; nullOr str;
+        default = cfg.user;
+        defaultText = literalExpression "config.services.ollama.user";
+        example = "ollama";
+        description = ''
+          Group under which to run ollama. Only used when `services.ollama.user` is set.
+
+          The group will automatically be created, if this option is set to a non-null value.
+        '';
+      };
+
       home = lib.mkOption {
         type = types.str;
-        default = "%S/ollama";
+        default = "/var/lib/ollama";
         example = "/home/foo";
         description = ''
           The home directory that the ollama service is started in.
-
-          See also `services.ollama.writablePaths` and `services.ollama.sandbox`.
         '';
       };
+
       models = lib.mkOption {
         type = types.str;
-        default = "%S/ollama/models";
+        default = "${cfg.home}/models";
+        defaultText = "\${config.services.ollama.home}/models";
         example = "/path/to/ollama/models";
         description = ''
           The directory that the ollama service will read models from and download new models to.
-
-          See also `services.ollama.writablePaths` and `services.ollama.sandbox`
-          if downloading models or other mutation of the filesystem is required.
         '';
       };
-      sandbox = lib.mkOption {
-        type = types.bool;
-        default = true;
-        example = false;
-        description = ''
-          Whether to enable systemd's sandboxing capabilities.
 
-          This sets [`DynamicUser`](
-          https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#DynamicUser=
-          ), which runs the server as a unique user with read-only access to most of the filesystem.
-
-          See also `services.ollama.writablePaths`.
-        '';
-      };
-      writablePaths = lib.mkOption {
-        type = types.listOf types.str;
-        default = [ ];
-        example = [ "/home/foo" "/mnt/foo" ];
-        description = ''
-          Paths that the server should have write access to.
-
-          This sets [`ReadWritePaths`](
-          https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#ReadWritePaths=
-          ), which allows specified paths to be written to through the default sandboxing.
-
-          See also `services.ollama.sandbox`.
-        '';
-      };
       host = lib.mkOption {
         type = types.str;
         default = "127.0.0.1";
-        example = "0.0.0.0";
+        example = "[::]";
         description = ''
           The host address which the ollama server HTTP interface listens to.
         '';
@@ -83,7 +99,13 @@ in
         '';
       };
       acceleration = lib.mkOption {
-        type = types.nullOr (types.enum [ false "rocm" "cuda" ]);
+        type = types.nullOr (
+          types.enum [
+            false
+            "rocm"
+            "cuda"
+          ]
+        );
         default = null;
         example = "rocm";
         description = ''
@@ -149,23 +171,90 @@ in
   };
 
   config = lib.mkIf cfg.enable {
+    users = lib.mkIf staticUser {
+      users.${cfg.user} = {
+        inherit (cfg) home;
+        isSystemUser = true;
+        group = cfg.group;
+      };
+      groups.${cfg.group} = { };
+    };
+
     systemd.services.ollama = {
       description = "Server for local large language models";
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
-      environment = cfg.environmentVariables // {
-        HOME = cfg.home;
-        OLLAMA_MODELS = cfg.models;
-        OLLAMA_HOST = "${cfg.host}:${toString cfg.port}";
-        HSA_OVERRIDE_GFX_VERSION = lib.mkIf (cfg.rocmOverrideGfx != null) cfg.rocmOverrideGfx;
-      };
-      serviceConfig = {
-        ExecStart = "${lib.getExe ollamaPackage} serve";
-        WorkingDirectory = cfg.home;
-        StateDirectory = [ "ollama" ];
-        DynamicUser = cfg.sandbox;
-        ReadWritePaths = cfg.writablePaths;
-      };
+      environment =
+        cfg.environmentVariables
+        // {
+          HOME = cfg.home;
+          OLLAMA_MODELS = cfg.models;
+          OLLAMA_HOST = "${cfg.host}:${toString cfg.port}";
+        }
+        // lib.optionalAttrs (cfg.rocmOverrideGfx != null) {
+          HSA_OVERRIDE_GFX_VERSION = cfg.rocmOverrideGfx;
+        };
+      serviceConfig =
+        lib.optionalAttrs staticUser {
+          User = cfg.user;
+          Group = cfg.group;
+        }
+        // {
+          DynamicUser = true;
+          ExecStart = "${lib.getExe ollamaPackage} serve";
+          WorkingDirectory = cfg.home;
+          StateDirectory = [ "ollama" ];
+          ReadWritePaths = [
+            cfg.home
+            cfg.models
+          ];
+
+          CapabilityBoundingSet = [ "" ];
+          DeviceAllow = [
+            # CUDA
+            # https://docs.nvidia.com/dgx/pdf/dgx-os-5-user-guide.pdf
+            "char-nvidiactl"
+            "char-nvidia-caps"
+            "char-nvidia-frontend"
+            "char-nvidia-uvm"
+            # ROCm
+            "char-drm"
+            "char-kfd"
+          ];
+          DevicePolicy = "closed";
+          LockPersonality = true;
+          MemoryDenyWriteExecute = true;
+          NoNewPrivileges = true;
+          PrivateDevices = false; # hides acceleration devices
+          PrivateTmp = true;
+          PrivateUsers = true;
+          ProcSubset = "all"; # /proc/meminfo
+          ProtectClock = true;
+          ProtectControlGroups = true;
+          ProtectHome = true;
+          ProtectHostname = true;
+          ProtectKernelLogs = true;
+          ProtectKernelModules = true;
+          ProtectKernelTunables = true;
+          ProtectProc = "invisible";
+          ProtectSystem = "strict";
+          RemoveIPC = true;
+          RestrictNamespaces = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+          RestrictAddressFamilies = [
+            "AF_INET"
+            "AF_INET6"
+            "AF_UNIX"
+          ];
+          SupplementaryGroups = [ "render" ]; # for rocm to access /dev/dri/renderD* devices
+          SystemCallArchitectures = "native";
+          SystemCallFilter = [
+            "@system-service @resources"
+            "~@privileged"
+          ];
+          UMask = "0077";
+        };
       postStart = mkBefore ''
         set -x
         export OLLAMA_HOST=${lib.escapeShellArg cfg.host}:${builtins.toString cfg.port}
@@ -181,5 +270,8 @@ in
     environment.systemPackages = [ ollamaPackage ];
   };
 
-  meta.maintainers = with lib.maintainers; [ abysssol onny ];
+  meta.maintainers = with lib.maintainers; [
+    abysssol
+    onny
+  ];
 }
diff --git a/nixos/modules/services/misc/open-webui.nix b/nixos/modules/services/misc/open-webui.nix
index b4016d03f675f..e2ed7080c9223 100644
--- a/nixos/modules/services/misc/open-webui.nix
+++ b/nixos/modules/services/misc/open-webui.nix
@@ -54,7 +54,10 @@ in
             WEBUI_AUTH = "False";
           }
         '';
-        description = "Extra environment variables for Open-WebUI";
+        description = ''
+          Extra environment variables for Open-WebUI.
+          For more details see https://docs.openwebui.com/getting-started/env-configuration/
+        '';
       };
 
       openFirewall = lib.mkOption {
diff --git a/nixos/modules/services/misc/private-gpt.nix b/nixos/modules/services/misc/private-gpt.nix
index ad9b6f5ffa80f..7bd2e492f5b56 100644
--- a/nixos/modules/services/misc/private-gpt.nix
+++ b/nixos/modules/services/misc/private-gpt.nix
@@ -117,5 +117,5 @@ in
     };
   };
 
-  meta.maintainers = with lib.maintainers; [ ];
+  meta.maintainers = [ ];
 }
diff --git a/nixos/modules/services/misc/radicle.nix b/nixos/modules/services/misc/radicle.nix
new file mode 100644
index 0000000000000..3a393bf0f1f20
--- /dev/null
+++ b/nixos/modules/services/misc/radicle.nix
@@ -0,0 +1,358 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.radicle;
+
+  json = pkgs.formats.json { };
+
+  env = rec {
+    # rad fails if it cannot stat $HOME/.gitconfig
+    HOME = "/var/lib/radicle";
+    RAD_HOME = HOME;
+  };
+
+  # Convenient wrapper to run `rad` in the namespaces of `radicle-node.service`
+  rad-system = pkgs.writeShellScriptBin "rad-system" ''
+    set -o allexport
+    ${toShellVars env}
+    # Note that --env is not used to preserve host's envvars like $TERM
+    exec ${getExe' pkgs.util-linux "nsenter"} -a \
+      -t "$(${getExe' config.systemd.package "systemctl"} show -P MainPID radicle-node.service)" \
+      -S "$(${getExe' config.systemd.package "systemctl"} show -P UID radicle-node.service)" \
+      -G "$(${getExe' config.systemd.package "systemctl"} show -P GID radicle-node.service)" \
+      ${getExe' cfg.package "rad"} "$@"
+  '';
+
+  commonServiceConfig = serviceName: {
+    environment = env // {
+      RUST_LOG = mkDefault "info";
+    };
+    path = [
+      pkgs.gitMinimal
+    ];
+    documentation = [
+      "https://docs.radicle.xyz/guides/seeder"
+    ];
+    after = [
+      "network.target"
+      "network-online.target"
+    ];
+    requires = [
+      "network-online.target"
+    ];
+    wantedBy = [ "multi-user.target" ];
+    serviceConfig = mkMerge [
+      {
+        BindReadOnlyPaths = [
+          "${cfg.configFile}:${env.RAD_HOME}/config.json"
+          "${if types.path.check cfg.publicKey then cfg.publicKey else pkgs.writeText "radicle.pub" cfg.publicKey}:${env.RAD_HOME}/keys/radicle.pub"
+        ];
+        KillMode = "process";
+        StateDirectory = [ "radicle" ];
+        User = config.users.users.radicle.name;
+        Group = config.users.groups.radicle.name;
+        WorkingDirectory = env.HOME;
+      }
+      # The following options are only for optimizing:
+      # systemd-analyze security ${serviceName}
+      {
+        BindReadOnlyPaths = [
+          "-/etc/resolv.conf"
+          "/etc/ssl/certs/ca-certificates.crt"
+          "/run/systemd"
+        ];
+        AmbientCapabilities = "";
+        CapabilityBoundingSet = "";
+        DeviceAllow = ""; # ProtectClock= adds DeviceAllow=char-rtc r
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        NoNewPrivileges = true;
+        PrivateTmp = true;
+        ProcSubset = "pid";
+        ProtectClock = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectProc = "invisible";
+        ProtectSystem = "strict";
+        RemoveIPC = true;
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        RuntimeDirectoryMode = "700";
+        SocketBindDeny = [ "any" ];
+        StateDirectoryMode = "0750";
+        SystemCallFilter = [
+          "@system-service"
+          "~@aio"
+          "~@chown"
+          "~@keyring"
+          "~@memlock"
+          "~@privileged"
+          "~@resources"
+          "~@setuid"
+          "~@timer"
+        ];
+        SystemCallArchitectures = "native";
+        # This is for BindPaths= and BindReadOnlyPaths=
+        # to allow traversal of directories they create inside RootDirectory=
+        UMask = "0066";
+      }
+    ];
+    confinement = {
+      enable = true;
+      mode = "full-apivfs";
+      packages = [
+        pkgs.gitMinimal
+        cfg.package
+        pkgs.iana-etc
+        (getLib pkgs.nss)
+        pkgs.tzdata
+      ];
+    };
+  };
+in
+{
+  options = {
+    services.radicle = {
+      enable = mkEnableOption "Radicle Seed Node";
+      package = mkPackageOption pkgs "radicle-node" { };
+      privateKeyFile = mkOption {
+        # Note that a key encrypted by systemd-creds is not a path but a str.
+        type = with types; either path str;
+        description = ''
+          Absolute file path to an SSH private key,
+          usually generated by `rad auth`.
+
+          If it contains a colon (`:`) the string before the colon
+          is taken as the credential name
+          and the string after as a path encrypted with `systemd-creds`.
+        '';
+      };
+      publicKey = mkOption {
+        type = with types; either path str;
+        description = ''
+          An SSH public key (as an absolute file path or directly as a string),
+          usually generated by `rad auth`.
+        '';
+      };
+      node = {
+        listenAddress = mkOption {
+          type = types.str;
+          default = "[::]";
+          example = "127.0.0.1";
+          description = "The IP address on which `radicle-node` listens.";
+        };
+        listenPort = mkOption {
+          type = types.port;
+          default = 8776;
+          description = "The port on which `radicle-node` listens.";
+        };
+        openFirewall = mkEnableOption "opening the firewall for `radicle-node`";
+        extraArgs = mkOption {
+          type = with types; listOf str;
+          default = [ ];
+          description = "Extra arguments for `radicle-node`";
+        };
+      };
+      configFile = mkOption {
+        type = types.package;
+        internal = true;
+        default = (json.generate "config.json" cfg.settings).overrideAttrs (previousAttrs: {
+          preferLocalBuild = true;
+          # None of the usual phases are run here because runCommandWith uses buildCommand,
+          # so just append to buildCommand what would usually be a checkPhase.
+          buildCommand = previousAttrs.buildCommand + optionalString cfg.checkConfig ''
+            ln -s $out config.json
+            install -D -m 644 /dev/stdin keys/radicle.pub <<<"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBgFMhajUng+Rjj/sCFXI9PzG8BQjru2n7JgUVF1Kbv5 snakeoil"
+            export RAD_HOME=$PWD
+            ${getExe' pkgs.buildPackages.radicle-node "rad"} config >/dev/null || {
+              cat -n config.json
+              echo "Invalid config.json according to rad."
+              echo "Please double-check your services.radicle.settings (producing the config.json above),"
+              echo "some settings may be missing or have the wrong type."
+              exit 1
+            } >&2
+          '';
+        });
+      };
+      checkConfig = mkEnableOption "checking the {file}`config.json` file resulting from {option}`services.radicle.settings`" // { default = true; };
+      settings = mkOption {
+        description = ''
+          See https://app.radicle.xyz/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/radicle/src/node/config.rs#L275
+        '';
+        default = { };
+        example = literalExpression ''
+          {
+            web.pinned.repositories = [
+              "rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5" # heartwood
+              "rad:z3trNYnLWS11cJWC6BbxDs5niGo82" # rips
+            ];
+          }
+        '';
+        type = types.submodule {
+          freeformType = json.type;
+        };
+      };
+      httpd = {
+        enable = mkEnableOption "Radicle HTTP gateway to radicle-node";
+        package = mkPackageOption pkgs "radicle-httpd" { };
+        listenAddress = mkOption {
+          type = types.str;
+          default = "127.0.0.1";
+          description = "The IP address on which `radicle-httpd` listens.";
+        };
+        listenPort = mkOption {
+          type = types.port;
+          default = 8080;
+          description = "The port on which `radicle-httpd` listens.";
+        };
+        nginx = mkOption {
+          # Type of a single virtual host, or null.
+          type = types.nullOr (types.submodule (
+            recursiveUpdate (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }) {
+              options.serverName = {
+                default = "radicle-${config.networking.hostName}.${config.networking.domain}";
+                defaultText = "radicle-\${config.networking.hostName}.\${config.networking.domain}";
+              };
+            }
+          ));
+          default = null;
+          example = literalExpression ''
+            {
+              serverAliases = [
+                "seed.''${config.networking.domain}"
+              ];
+              enableACME = false;
+              useACMEHost = config.networking.domain;
+            }
+          '';
+          description = ''
+            With this option, you can customize an nginx virtual host which already has sensible defaults for `radicle-httpd`.
+            Set to `{}` if you do not need any customization to the virtual host.
+            If enabled, then by default, the {option}`serverName` is
+            `radicle-''${config.networking.hostName}.''${config.networking.domain}`,
+            TLS is active, and certificates are acquired via ACME.
+            If this is set to null (the default), no nginx virtual host will be configured.
+          '';
+        };
+        extraArgs = mkOption {
+          type = with types; listOf str;
+          default = [ ];
+          description = "Extra arguments for `radicle-httpd`";
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable (mkMerge [
+    {
+      systemd.services.radicle-node = mkMerge [
+        (commonServiceConfig "radicle-node")
+        {
+          description = "Radicle Node";
+          documentation = [ "man:radicle-node(1)" ];
+          serviceConfig = {
+            ExecStart = "${getExe' cfg.package "radicle-node"} --force --listen ${cfg.node.listenAddress}:${toString cfg.node.listenPort} ${escapeShellArgs cfg.node.extraArgs}";
+            Restart = mkDefault "on-failure";
+            RestartSec = "30";
+            SocketBindAllow = [ "tcp:${toString cfg.node.listenPort}" ];
+            SystemCallFilter = mkAfter [
+              # Needed by git upload-pack which calls alarm() and setitimer() when providing a rad clone
+              "@timer"
+            ];
+          };
+          confinement.packages = [
+            cfg.package
+          ];
+        }
+        # Give only access to the private key to radicle-node.
+        {
+          serviceConfig =
+            let keyCred = builtins.split ":" "${cfg.privateKeyFile}"; in
+            if length keyCred > 1
+            then {
+              LoadCredentialEncrypted = [ cfg.privateKeyFile ];
+              # Note that neither %d nor ${CREDENTIALS_DIRECTORY} works in BindReadOnlyPaths=
+              BindReadOnlyPaths = [ "/run/credentials/radicle-node.service/${head keyCred}:${env.RAD_HOME}/keys/radicle" ];
+            }
+            else {
+              LoadCredential = [ "radicle:${cfg.privateKeyFile}" ];
+              BindReadOnlyPaths = [ "/run/credentials/radicle-node.service/radicle:${env.RAD_HOME}/keys/radicle" ];
+            };
+        }
+      ];
+
+      environment.systemPackages = [
+        rad-system
+      ];
+
+      networking.firewall = mkIf cfg.node.openFirewall {
+        allowedTCPPorts = [ cfg.node.listenPort ];
+      };
+
+      users = {
+        users.radicle = {
+          description = "Radicle";
+          group = "radicle";
+          home = env.HOME;
+          isSystemUser = true;
+        };
+        groups.radicle = {
+        };
+      };
+    }
+
+    (mkIf cfg.httpd.enable (mkMerge [
+      {
+        systemd.services.radicle-httpd = mkMerge [
+          (commonServiceConfig "radicle-httpd")
+          {
+            description = "Radicle HTTP gateway to radicle-node";
+            documentation = [ "man:radicle-httpd(1)" ];
+            serviceConfig = {
+              ExecStart = "${getExe' cfg.httpd.package "radicle-httpd"} --listen ${cfg.httpd.listenAddress}:${toString cfg.httpd.listenPort} ${escapeShellArgs cfg.httpd.extraArgs}";
+              Restart = mkDefault "on-failure";
+              RestartSec = "10";
+              SocketBindAllow = [ "tcp:${toString cfg.httpd.listenPort}" ];
+              SystemCallFilter = mkAfter [
+                # Needed by git upload-pack which calls alarm() and setitimer() when providing a git clone
+                "@timer"
+              ];
+            };
+          confinement.packages = [
+            cfg.httpd.package
+          ];
+          }
+        ];
+      }
+
+      (mkIf (cfg.httpd.nginx != null) {
+        services.nginx.virtualHosts.${cfg.httpd.nginx.serverName} = lib.mkMerge [
+          cfg.httpd.nginx
+          {
+            forceSSL = mkDefault true;
+            enableACME = mkDefault true;
+            locations."/" = {
+              proxyPass = "http://${cfg.httpd.listenAddress}:${toString cfg.httpd.listenPort}";
+              recommendedProxySettings = true;
+            };
+          }
+        ];
+
+        services.radicle.settings = {
+          node.alias = mkDefault cfg.httpd.nginx.serverName;
+          node.externalAddresses = mkDefault [
+            "${cfg.httpd.nginx.serverName}:${toString cfg.node.listenPort}"
+          ];
+        };
+      })
+    ]))
+  ]);
+
+  meta.maintainers = with lib.maintainers; [
+    julm
+    lorenzleutgeb
+  ];
+}
diff --git a/nixos/modules/services/misc/libreddit.nix b/nixos/modules/services/misc/redlib.nix
index c1f6b276ad9fa..0da85df46bf72 100644
--- a/nixos/modules/services/misc/libreddit.nix
+++ b/nixos/modules/services/misc/redlib.nix
@@ -3,7 +3,7 @@
 with lib;
 
 let
-  cfg = config.services.libreddit;
+  cfg = config.services.redlib;
 
   args = concatStringsSep " " ([
     "--port ${toString cfg.port}"
@@ -11,11 +11,15 @@ let
   ]);
 in
 {
+  imports = [
+    (mkRenamedOptionModule [ "services" "libreddit" ] [ "services" "redlib" ])
+  ];
+
   options = {
-    services.libreddit = {
+    services.redlib = {
       enable = mkEnableOption "Private front-end for Reddit";
 
-      package = mkPackageOption pkgs "libreddit" { };
+      package = mkPackageOption pkgs "redlib" { };
 
       address = mkOption {
         default = "0.0.0.0";
@@ -34,14 +38,14 @@ in
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = "Open ports in the firewall for the libreddit web interface";
+        description = "Open ports in the firewall for the redlib web interface";
       };
 
     };
   };
 
   config = mkIf cfg.enable {
-    systemd.services.libreddit = {
+    systemd.services.redlib = {
         description = "Private front-end for Reddit";
         wantedBy = [ "multi-user.target" ];
         after = [ "network.target" ];
diff --git a/nixos/modules/services/misc/rkvm.nix b/nixos/modules/services/misc/rkvm.nix
index b149c3d3979f5..ac747253635e8 100644
--- a/nixos/modules/services/misc/rkvm.nix
+++ b/nixos/modules/services/misc/rkvm.nix
@@ -7,7 +7,7 @@ let
   toml = pkgs.formats.toml { };
 in
 {
-  meta.maintainers = with maintainers; [ ];
+  meta.maintainers = [ ];
 
   options.services.rkvm = {
     enable = mkOption {
diff --git a/nixos/modules/services/misc/snapper.nix b/nixos/modules/services/misc/snapper.nix
index 1b16ef7958ad2..fc57683de3280 100644
--- a/nixos/modules/services/misc/snapper.nix
+++ b/nixos/modules/services/misc/snapper.nix
@@ -96,48 +96,48 @@ let
     };
 
     TIMELINE_LIMIT_HOURLY = mkOption {
-      type = types.str;
-      default = "10";
+      type = types.int;
+      default = 10;
       description = ''
         Limits for timeline cleanup.
       '';
     };
 
     TIMELINE_LIMIT_DAILY = mkOption {
-      type = types.str;
-      default = "10";
+      type = types.int;
+      default = 10;
       description = ''
         Limits for timeline cleanup.
       '';
     };
 
     TIMELINE_LIMIT_WEEKLY = mkOption {
-      type = types.str;
-      default = "0";
+      type = types.int;
+      default = 0;
       description = ''
         Limits for timeline cleanup.
       '';
     };
 
     TIMELINE_LIMIT_MONTHLY = mkOption {
-      type = types.str;
-      default = "10";
+      type = types.int;
+      default = 10;
       description = ''
         Limits for timeline cleanup.
       '';
     };
 
     TIMELINE_LIMIT_QUARTERLY = mkOption {
-      type = types.str;
-      default = "0";
+      type = types.int;
+      default = 0;
       description = ''
         Limits for timeline cleanup.
       '';
     };
 
     TIMELINE_LIMIT_YEARLY = mkOption {
-      type = types.str;
-      default = "10";
+      type = types.int;
+      default = 10;
       description = ''
         Limits for timeline cleanup.
       '';
@@ -353,4 +353,6 @@ in
       ) (attrNames cfg.configs);
     }
   );
+
+  meta.maintainers = with lib.maintainers; [ Djabx ];
 }
diff --git a/nixos/modules/services/misc/sonarr.nix b/nixos/modules/services/misc/sonarr.nix
index 228a2d48f5a9c..60e73198d60de 100644
--- a/nixos/modules/services/misc/sonarr.nix
+++ b/nixos/modules/services/misc/sonarr.nix
@@ -1,4 +1,4 @@
-{ config, pkgs, lib, ... }:
+{ config, pkgs, lib, utils, ... }:
 
 with lib;
 
@@ -54,7 +54,11 @@ in
         Type = "simple";
         User = cfg.user;
         Group = cfg.group;
-        ExecStart = "${cfg.package}/bin/NzbDrone -nobrowser -data='${cfg.dataDir}'";
+        ExecStart = utils.escapeSystemdExecArgs [
+          (lib.getExe cfg.package)
+          "-nobrowser"
+          "-data=${cfg.dataDir}"
+        ];
         Restart = "on-failure";
       };
     };
diff --git a/nixos/modules/services/misc/sssd.nix b/nixos/modules/services/misc/sssd.nix
index 4429b20174d94..f2798c1f30b45 100644
--- a/nixos/modules/services/misc/sssd.nix
+++ b/nixos/modules/services/misc/sssd.nix
@@ -145,7 +145,7 @@ in {
         # https://github.com/krb5/krb5/blob/krb5-1.19.3-final/src/include/kcm.h#L43
         listenStreams = [ "/var/run/.heim_org.h5l.kcm-socket" ];
       };
-      krb5.libdefaults.default_ccache_name = "KCM:";
+      security.krb5.settings.libdefaults.default_ccache_name = "KCM:";
     })
 
     (mkIf cfg.sshAuthorizedKeysIntegration {
diff --git a/nixos/modules/services/misc/xmr-stak.nix b/nixos/modules/services/misc/xmr-stak.nix
deleted file mode 100644
index 3015e3cb12a87..0000000000000
--- a/nixos/modules/services/misc/xmr-stak.nix
+++ /dev/null
@@ -1,89 +0,0 @@
-{ lib, config, pkgs, ... }:
-
-with lib;
-
-let
-
-  cfg = config.services.xmr-stak;
-
-  pkg = pkgs.xmr-stak.override {
-    inherit (cfg) openclSupport;
-  };
-
-in
-
-{
-  options = {
-    services.xmr-stak = {
-      enable = mkEnableOption "xmr-stak miner";
-      openclSupport = mkEnableOption "support for OpenCL (AMD/ATI graphics cards)";
-
-      extraArgs = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        example = [ "--noCPU" "--currency monero" ];
-        description = "List of parameters to pass to xmr-stak.";
-      };
-
-      configFiles = mkOption {
-        type = types.attrsOf types.str;
-        default = {};
-        example = literalExpression ''
-          {
-            "config.txt" = '''
-              "verbose_level" : 4,
-              "h_print_time" : 60,
-              "tls_secure_algo" : true,
-            ''';
-            "pools.txt" = '''
-              "currency" : "monero7",
-              "pool_list" :
-              [ { "pool_address" : "pool.supportxmr.com:443",
-                  "wallet_address" : "my-wallet-address",
-                  "rig_id" : "",
-                  "pool_password" : "nixos",
-                  "use_nicehash" : false,
-                  "use_tls" : true,
-                  "tls_fingerprint" : "",
-                  "pool_weight" : 23
-                },
-              ],
-            ''';
-          }
-        '';
-        description = ''
-          Content of config files like config.txt, pools.txt or cpu.txt.
-        '';
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    systemd.services.xmr-stak = {
-      wantedBy = [ "multi-user.target" ];
-      bindsTo = [ "network-online.target" ];
-      after = [ "network-online.target" ];
-
-      preStart = concatStrings (flip mapAttrsToList cfg.configFiles (fn: content: ''
-        ln -sf '${pkgs.writeText "xmr-stak-${fn}" content}' '${fn}'
-      ''));
-
-      serviceConfig = let rootRequired = cfg.openclSupport; in {
-        ExecStart = "${pkg}/bin/xmr-stak ${concatStringsSep " " cfg.extraArgs}";
-        # xmr-stak generates cpu and/or gpu configuration files
-        WorkingDirectory = "/tmp";
-        PrivateTmp = true;
-        DynamicUser = !rootRequired;
-        LimitMEMLOCK = toString (1024*1024);
-      };
-    };
-  };
-
-  imports = [
-    (mkRemovedOptionModule ["services" "xmr-stak" "configText"] ''
-      This option was removed in favour of `services.xmr-stak.configFiles`
-      because the new config file `pools.txt` was introduced. You are
-      now able to define all other config files like cpu.txt or amd.txt.
-    '')
-  ];
-}
diff --git a/nixos/modules/services/misc/zoneminder.nix b/nixos/modules/services/misc/zoneminder.nix
index 8db63d5386332..5b0b1448f6856 100644
--- a/nixos/modules/services/misc/zoneminder.nix
+++ b/nixos/modules/services/misc/zoneminder.nix
@@ -202,10 +202,11 @@ in {
     ];
 
     services = {
-      fcgiwrap.zoneminder = lib.mkIf useNginx {
+      fcgiwrap.instances.zoneminder = lib.mkIf useNginx {
         process.prefork = cfg.cameras;
         process.user = user;
         process.group = group;
+        socket = { inherit (config.services.nginx) user group; };
       };
 
       mysql = lib.mkIf cfg.database.createLocally {
@@ -255,7 +256,7 @@ in {
                   fastcgi_param HTTP_PROXY "";
                   fastcgi_intercept_errors on;
 
-                  fastcgi_pass unix:${config.services.fcgiwrap.zoneminder.socket.address};
+                  fastcgi_pass unix:${config.services.fcgiwrap.instances.zoneminder.socket.address};
                 }
 
                 location /cache/ {
@@ -372,5 +373,5 @@ in {
     };
   };
 
-  meta.maintainers = with lib.maintainers; [ ];
+  meta.maintainers = [ ];
 }
diff --git a/nixos/modules/services/monitoring/librenms.nix b/nixos/modules/services/monitoring/librenms.nix
index 08a46754e0e86..b06dbe66fbdee 100644
--- a/nixos/modules/services/monitoring/librenms.nix
+++ b/nixos/modules/services/monitoring/librenms.nix
@@ -2,7 +2,7 @@
 
 let
   cfg = config.services.librenms;
-  settingsFormat = pkgs.formats.json {};
+  settingsFormat = pkgs.formats.json { };
   configJson = settingsFormat.generate "librenms-config.json" cfg.settings;
 
   package = pkgs.librenms.override {
@@ -16,12 +16,13 @@ let
     upload_max_filesize = 100M
     date.timezone = "${config.time.timeZone}"
   '';
-  phpIni = pkgs.runCommand "php.ini" {
-    inherit (package) phpPackage;
-    inherit phpOptions;
-    preferLocalBuild = true;
-    passAsFile = [ "phpOptions" ];
-  } ''
+  phpIni = pkgs.runCommand "php.ini"
+    {
+      inherit (package) phpPackage;
+      inherit phpOptions;
+      preferLocalBuild = true;
+      passAsFile = [ "phpOptions" ];
+    } ''
     cat $phpPackage/etc/php.ini $phpOptionsPath > $out
   '';
 
@@ -31,14 +32,20 @@ let
     if [[ "$USER" != ${cfg.user} ]]; then
       sudo='exec /run/wrappers/bin/sudo -u ${cfg.user}'
     fi
-    $sudo ${package}/artisan $*
+    $sudo ${package}/artisan "$@"
   '';
 
   lnmsWrapper = pkgs.writeShellScriptBin "lnms" ''
     cd ${package}
-    exec ${package}/lnms $*
+    sudo=exec
+    if [[ "$USER" != ${cfg.user} ]]; then
+    sudo='exec /run/wrappers/bin/sudo -u ${cfg.user}'
+    fi
+    $sudo ${package}/lnms "$@"
   '';
 
+
+
   configFile = pkgs.writeText "config.php" ''
     <?php
     $new_config = json_decode(file_get_contents("${cfg.dataDir}/config.json"), true);
@@ -47,7 +54,8 @@ let
     ${lib.optionalString (cfg.extraConfig != null) cfg.extraConfig}
   '';
 
-in {
+in
+{
   options.services.librenms = with lib; {
     enable = mkEnableOption "LibreNMS network monitoring system";
 
@@ -191,7 +199,8 @@ in {
     nginx = mkOption {
       type = types.submodule (
         recursiveUpdate
-          (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }) {}
+          (import ../web-servers/nginx/vhost-options.nix { inherit config lib; })
+          { }
       );
       default = { };
       example = literalExpression ''
@@ -240,6 +249,7 @@ in {
         default = "localhost";
         description = ''
           Hostname or IP of the MySQL/MariaDB server.
+          Ignored if 'socket' is defined.
         '';
       };
 
@@ -248,6 +258,7 @@ in {
         default = 3306;
         description = ''
           Port of the MySQL/MariaDB server.
+          Ignored if 'socket' is defined.
         '';
       };
 
@@ -264,15 +275,28 @@ in {
         default = "librenms";
         description = ''
           Name of the user on the MySQL/MariaDB server.
+          Ignored if 'socket' is defined.
         '';
       };
 
       passwordFile = mkOption {
-        type = types.path;
+        type = types.nullOr types.path;
+        default = null;
         example = "/run/secrets/mysql.pass";
         description = ''
           A file containing the password for the user of the MySQL/MariaDB server.
           Must be readable for the LibreNMS user.
+          Ignored if 'socket' is defined, mandatory otherwise.
+        '';
+      };
+
+      socket = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/run/mysqld/mysqld.sock";
+        description = ''
+          A unix socket to mysql, accessible by the librenms user.
+          Useful when mysql is on the localhost.
         '';
       };
     };
@@ -289,7 +313,7 @@ in {
     settings = mkOption {
       type = types.submodule {
         freeformType = settingsFormat.type;
-        options = {};
+        options = { };
       };
       description = ''
         Attrset of the LibreNMS configuration.
@@ -483,13 +507,15 @@ in {
         EnvironmentFile = lib.mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
         User = cfg.user;
         Group = cfg.group;
-        ExecStartPre = lib.mkIf cfg.database.createLocally [ "!${pkgs.writeShellScript "librenms-db-init" ''
+        ExecStartPre = lib.mkIf cfg.database.createLocally [
+          "!${pkgs.writeShellScript "librenms-db-init" ''
           DB_PASSWORD=$(cat ${cfg.database.passwordFile} | tr -d '\n')
           echo "ALTER USER '${cfg.database.username}'@'localhost' IDENTIFIED BY '$DB_PASSWORD';" | ${pkgs.mariadb}/bin/mysql
           ${lib.optionalString cfg.useDistributedPollers ''
             echo "ALTER USER '${cfg.database.username}'@'%' IDENTIFIED BY '$DB_PASSWORD';" | ${pkgs.mariadb}/bin/mysql
           ''}
-        ''}"];
+        ''}"
+        ];
       };
       script = ''
         set -euo pipefail
@@ -516,13 +542,24 @@ in {
         ${lib.optionalString (cfg.useDistributedPollers || cfg.distributedPoller.enable) ''
           echo "CACHE_DRIVER=memcached" >> ${cfg.dataDir}/.env
         ''}
-        echo "DB_HOST=${cfg.database.host}" >> ${cfg.dataDir}/.env
-        echo "DB_PORT=${toString cfg.database.port}" >> ${cfg.dataDir}/.env
         echo "DB_DATABASE=${cfg.database.database}" >> ${cfg.dataDir}/.env
-        echo "DB_USERNAME=${cfg.database.username}" >> ${cfg.dataDir}/.env
-        echo -n "DB_PASSWORD=" >> ${cfg.dataDir}/.env
-        cat ${cfg.database.passwordFile} >> ${cfg.dataDir}/.env
-
+      ''
+      + (
+        if ! isNull cfg.database.socket
+        then ''
+          # use socket connection
+          echo "DB_SOCKET=${cfg.database.socket}" >> ${cfg.dataDir}/.env
+        ''
+        else ''
+          # use TCP connection
+          echo "DB_HOST=${cfg.database.host}" >> ${cfg.dataDir}/.env
+          echo "DB_PORT=${toString cfg.database.port}" >> ${cfg.dataDir}/.env
+          echo "DB_USERNAME=${cfg.database.username}" >> ${cfg.dataDir}/.env
+          echo -n "DB_PASSWORD=" >> ${cfg.dataDir}/.env
+          cat ${cfg.database.passwordFile} >> ${cfg.dataDir}/.env
+        ''
+      )
+      + ''
         # clear cache after update
         OLD_VERSION=$(cat ${cfg.dataDir}/version)
         if [[ $OLD_VERSION != "${package.version}" ]]; then
@@ -560,29 +597,31 @@ in {
 
     services.cron = {
       enable = true;
-      systemCronJobs = let
-        env = "PHPRC=${phpIni}";
-      in [
-        # based on crontab provided by LibreNMS
-        "33 */6 * * * ${cfg.user} ${env} ${package}/cronic ${package}/discovery-wrapper.py 1"
-        "*/5 * * * * ${cfg.user} ${env} ${package}/discovery.php -h new >> /dev/null 2>&1"
-
-        "${if cfg.enableOneMinutePolling then "*" else "*/5"} * * * * ${cfg.user} ${env} ${package}/cronic ${package}/poller-wrapper.py ${toString cfg.pollerThreads}"
-        "* * * * * ${cfg.user} ${env} ${package}/alerts.php >> /dev/null 2>&1"
-
-        "*/5 * * * * ${cfg.user} ${env} ${package}/poll-billing.php >> /dev/null 2>&1"
-        "01 * * * * ${cfg.user} ${env} ${package}/billing-calculate.php >> /dev/null 2>&1"
-        "*/5 * * * * ${cfg.user} ${env} ${package}/check-services.php >> /dev/null 2>&1"
-
-        # extra: fast ping
-        "* * * * * ${cfg.user} ${env} ${package}/ping.php >> /dev/null 2>&1"
-
-        # daily.sh tasks are split to exclude update
-        "19 0 * * * ${cfg.user} ${env} ${package}/daily.sh cleanup >> /dev/null 2>&1"
-        "19 0 * * * ${cfg.user} ${env} ${package}/daily.sh notifications >> /dev/null 2>&1"
-        "19 0 * * * ${cfg.user} ${env} ${package}/daily.sh peeringdb >> /dev/null 2>&1"
-        "19 0 * * * ${cfg.user} ${env} ${package}/daily.sh mac_oui >> /dev/null 2>&1"
-      ];
+      systemCronJobs =
+        let
+          env = "PHPRC=${phpIni}";
+        in
+        [
+          # based on crontab provided by LibreNMS
+          "33 */6 * * * ${cfg.user} ${env} ${package}/cronic ${package}/discovery-wrapper.py 1"
+          "*/5 * * * * ${cfg.user} ${env} ${package}/discovery.php -h new >> /dev/null 2>&1"
+
+          "${if cfg.enableOneMinutePolling then "*" else "*/5"} * * * * ${cfg.user} ${env} ${package}/cronic ${package}/poller-wrapper.py ${toString cfg.pollerThreads}"
+          "* * * * * ${cfg.user} ${env} ${package}/alerts.php >> /dev/null 2>&1"
+
+          "*/5 * * * * ${cfg.user} ${env} ${package}/poll-billing.php >> /dev/null 2>&1"
+          "01 * * * * ${cfg.user} ${env} ${package}/billing-calculate.php >> /dev/null 2>&1"
+          "*/5 * * * * ${cfg.user} ${env} ${package}/check-services.php >> /dev/null 2>&1"
+
+          # extra: fast ping
+          "* * * * * ${cfg.user} ${env} ${package}/ping.php >> /dev/null 2>&1"
+
+          # daily.sh tasks are split to exclude update
+          "19 0 * * * ${cfg.user} ${env} ${package}/daily.sh cleanup >> /dev/null 2>&1"
+          "19 0 * * * ${cfg.user} ${env} ${package}/daily.sh notifications >> /dev/null 2>&1"
+          "19 0 * * * ${cfg.user} ${env} ${package}/daily.sh peeringdb >> /dev/null 2>&1"
+          "19 0 * * * ${cfg.user} ${env} ${package}/daily.sh mac_oui >> /dev/null 2>&1"
+        ];
     };
 
     security.wrappers = {
diff --git a/nixos/modules/services/monitoring/nagios.nix b/nixos/modules/services/monitoring/nagios.nix
index 27fc0a1ff3b9c..e475d41270b10 100644
--- a/nixos/modules/services/monitoring/nagios.nix
+++ b/nixos/modules/services/monitoring/nagios.nix
@@ -88,7 +88,7 @@ in
 
   options = {
     services.nagios = {
-      enable = mkEnableOption ''[Nagios](https://www.nagios.org/) to monitor your system or network.'';
+      enable = mkEnableOption ''[Nagios](https://www.nagios.org/) to monitor your system or network'';
 
       objectDefs = mkOption {
         description = ''
@@ -175,7 +175,7 @@ in
 
   config = mkIf cfg.enable {
     users.users.nagios = {
-      description = "Nagios user ";
+      description = "Nagios user";
       uid         = config.ids.uids.nagios;
       home        = nagiosState;
       group       = "nagios";
diff --git a/nixos/modules/services/monitoring/netdata.nix b/nixos/modules/services/monitoring/netdata.nix
index 8f89408bdea59..c47da2cc075de 100644
--- a/nixos/modules/services/monitoring/netdata.nix
+++ b/nixos/modules/services/monitoring/netdata.nix
@@ -247,7 +247,8 @@ in {
         ++ lib.optional cfg.python.enable (pkgs.python3.withPackages cfg.python.extraPackages)
         ++ lib.optional config.virtualisation.libvirtd.enable config.virtualisation.libvirtd.package
         ++ lib.optional config.virtualisation.docker.enable config.virtualisation.docker.package
-        ++ lib.optionals config.virtualisation.podman.enable [ pkgs.jq config.virtualisation.podman.package ];
+        ++ lib.optionals config.virtualisation.podman.enable [ pkgs.jq config.virtualisation.podman.package ]
+        ++ lib.optional config.boot.zfs.enabled config.boot.zfs.package;
       environment = {
         PYTHONPATH = "${cfg.package}/libexec/netdata/python.d/python_modules";
         NETDATA_PIPENAME = "/run/netdata/ipc";
diff --git a/nixos/modules/services/monitoring/opentelemetry-collector.nix b/nixos/modules/services/monitoring/opentelemetry-collector.nix
index 459cc85324902..d9b8c27ccdfe3 100644
--- a/nixos/modules/services/monitoring/opentelemetry-collector.nix
+++ b/nixos/modules/services/monitoring/opentelemetry-collector.nix
@@ -6,8 +6,9 @@ let
   cfg = config.services.opentelemetry-collector;
   opentelemetry-collector = cfg.package;
 
-  settingsFormat = pkgs.formats.yaml {};
-in {
+  settingsFormat = pkgs.formats.yaml { };
+in
+{
   options.services.opentelemetry-collector = {
     enable = mkEnableOption "Opentelemetry Collector";
 
@@ -15,7 +16,7 @@ in {
 
     settings = mkOption {
       type = settingsFormat.type;
-      default = {};
+      default = { };
       description = ''
         Specify the configuration for Opentelemetry Collector in Nix.
 
@@ -35,9 +36,9 @@ in {
   config = mkIf cfg.enable {
     assertions = [{
       assertion = (
-        (cfg.settings == {}) != (cfg.configFile == null)
+        (cfg.settings == { }) != (cfg.configFile == null)
       );
-      message  = ''
+      message = ''
         Please specify a configuration for Opentelemetry Collector with either
         'services.opentelemetry-collector.settings' or
         'services.opentelemetry-collector.configFile'.
@@ -48,21 +49,27 @@ in {
       description = "Opentelemetry Collector Service Daemon";
       wantedBy = [ "multi-user.target" ];
 
-      serviceConfig = let
-        conf = if cfg.configFile == null
-               then settingsFormat.generate "config.yaml" cfg.settings
-               else cfg.configFile;
-      in
-      {
-        ExecStart = "${getExe opentelemetry-collector} --config=file:${conf}";
-        DynamicUser = true;
-        Restart = "always";
-        ProtectSystem = "full";
-        DevicePolicy = "closed";
-        NoNewPrivileges = true;
-        WorkingDirectory = "/var/lib/opentelemetry-collector";
-        StateDirectory = "opentelemetry-collector";
-      };
+      serviceConfig =
+        let
+          conf =
+            if cfg.configFile == null
+            then settingsFormat.generate "config.yaml" cfg.settings
+            else cfg.configFile;
+        in
+        {
+          ExecStart = "${getExe opentelemetry-collector} --config=file:${conf}";
+          DynamicUser = true;
+          Restart = "always";
+          ProtectSystem = "full";
+          DevicePolicy = "closed";
+          NoNewPrivileges = true;
+          WorkingDirectory = "%S/opentelemetry-collector";
+          StateDirectory = "opentelemetry-collector";
+          SupplementaryGroups = [
+            # allow to read the systemd journal for opentelemetry-collector
+            "systemd-journal"
+          ];
+        };
     };
   };
 }
diff --git a/nixos/modules/services/monitoring/prometheus/alertmanager-webhook-logger.nix b/nixos/modules/services/monitoring/prometheus/alertmanager-webhook-logger.nix
index b4307a76e1b02..b3665b66ba406 100644
--- a/nixos/modules/services/monitoring/prometheus/alertmanager-webhook-logger.nix
+++ b/nixos/modules/services/monitoring/prometheus/alertmanager-webhook-logger.nix
@@ -32,9 +32,15 @@ in
           ${escapeShellArgs cfg.extraFlags}
         '';
 
+        CapabilityBoundingSet = [ "" ];
+        DeviceAllow = [ "" ];
         DynamicUser = true;
         NoNewPrivileges = true;
 
+        MemoryDenyWriteExecute = true;
+
+        LockPersonality = true;
+
         ProtectProc = "invisible";
         ProtectSystem = "strict";
         ProtectHome = "tmpfs";
@@ -43,6 +49,8 @@ in
         PrivateDevices = true;
         PrivateIPC = true;
 
+        ProcSubset = "pid";
+
         ProtectHostname = true;
         ProtectClock = true;
         ProtectKernelTunables = true;
@@ -50,7 +58,10 @@ in
         ProtectKernelLogs = true;
         ProtectControlGroups = true;
 
+        Restart  = "on-failure";
+
         RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
         RestrictRealtime = true;
         RestrictSUIDSGID = true;
 
diff --git a/nixos/modules/services/monitoring/prometheus/alertmanager.nix b/nixos/modules/services/monitoring/prometheus/alertmanager.nix
index d1d8f2caaf63d..f40ac3c9138ff 100644
--- a/nixos/modules/services/monitoring/prometheus/alertmanager.nix
+++ b/nixos/modules/services/monitoring/prometheus/alertmanager.nix
@@ -181,15 +181,57 @@ in {
                                                     -i "${alertmanagerYml}"
         '';
         serviceConfig = {
-          Restart  = "always";
-          StateDirectory = "alertmanager";
-          DynamicUser = true; # implies PrivateTmp
-          EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
-          WorkingDirectory = "/tmp";
           ExecStart = "${cfg.package}/bin/alertmanager" +
             optionalString (length cmdlineArgs != 0) (" \\\n  " +
               concatStringsSep " \\\n  " cmdlineArgs);
           ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+
+          EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
+
+          CapabilityBoundingSet = [ "" ];
+          DeviceAllow = [ "" ];
+          DynamicUser = true;
+          NoNewPrivileges = true;
+
+          MemoryDenyWriteExecute = true;
+
+          LockPersonality = true;
+
+          ProtectProc = "invisible";
+          ProtectSystem = "strict";
+          ProtectHome = "tmpfs";
+
+          PrivateTmp = true;
+          PrivateDevices = true;
+          PrivateIPC = true;
+
+          ProcSubset = "pid";
+
+          ProtectHostname = true;
+          ProtectClock = true;
+          ProtectKernelTunables = true;
+          ProtectKernelModules = true;
+          ProtectKernelLogs = true;
+          ProtectControlGroups = true;
+
+          Restart  = "always";
+
+          RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_NETLINK" ];
+          RestrictNamespaces = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+
+          StateDirectory = "alertmanager";
+          SystemCallFilter = [
+            "@system-service"
+            "~@cpu-emulation"
+            "~@privileged"
+            "~@reboot"
+            "~@setuid"
+            "~@swap"
+          ];
+
+          WorkingDirectory = "/tmp";
         };
       };
     })
diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix
index dc357f6cc5fb3..723116d2547be 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters.nix
@@ -27,8 +27,10 @@ let
     "bird"
     "bitcoin"
     "blackbox"
+    "borgmatic"
     "buildkite-agent"
     "collectd"
+    "deluge"
     "dmarc"
     "dnsmasq"
     "dnssec"
@@ -408,6 +410,14 @@ in
         Please ensure you have either `services.prometheus.exporters.idrac.configuration'
           or `services.prometheus.exporters.idrac.configurationPath' set!
       '';
+    } {
+      assertion = cfg.deluge.enable -> (
+        (cfg.deluge.delugePassword == null) != (cfg.deluge.delugePasswordFile == null)
+      );
+      message = ''
+        Please ensure you have either `services.prometheus.exporters.deluge.delugePassword'
+          or `services.prometheus.exporters.deluge.delugePasswordFile' set!
+      '';
     } ] ++ (flip map (attrNames exporterOpts) (exporter: {
       assertion = cfg.${exporter}.firewallFilter != null -> cfg.${exporter}.openFirewall;
       message = ''
@@ -437,6 +447,13 @@ in
     hardware.rtl-sdr.enable = mkDefault true;
   })] ++ [(mkIf config.services.postfix.enable {
     services.prometheus.exporters.postfix.group = mkDefault config.services.postfix.setgidGroup;
+  })] ++ [(mkIf config.services.prometheus.exporters.deluge.enable {
+    system.activationScripts = {
+      deluge-exported.text = ''
+      mkdir -p /etc/deluge-exporter
+      echo "DELUGE_PASSWORD=$(cat ${config.services.prometheus.exporters.deluge.delugePasswordFile})" > /etc/deluge-exporter/password
+      '';
+    };
   })] ++ (mapAttrsToList (name: conf:
     mkExporterConf {
       inherit name;
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/borgmatic.nix b/nixos/modules/services/monitoring/prometheus/exporters/borgmatic.nix
new file mode 100644
index 0000000000000..573230c3afcab
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/borgmatic.nix
@@ -0,0 +1,34 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  cfg = config.services.prometheus.exporters.borgmatic;
+in
+{
+  port = 9996;
+  extraOpts.configFile = lib.mkOption {
+    type = lib.types.path;
+    default = "/etc/borgmatic/config.yaml";
+    description = ''
+      The path to the borgmatic config file
+    '';
+  };
+
+  serviceOpts = {
+    serviceConfig = {
+      DynamicUser = false;
+      ProtectSystem = false;
+      ProtectHome = lib.mkForce false;
+      ExecStart = ''
+        ${pkgs.prometheus-borgmatic-exporter}/bin/borgmatic-exporter run \
+          --port ${toString cfg.port} \
+          --config ${toString cfg.configFile} \
+          ${lib.concatMapStringsSep " " (f: lib.escapeShellArg f) cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/deluge.nix b/nixos/modules/services/monitoring/prometheus/exporters/deluge.nix
new file mode 100644
index 0000000000000..5943b46eeb5fc
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/deluge.nix
@@ -0,0 +1,85 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.prometheus.exporters.deluge;
+  inherit (lib) mkOption types concatStringsSep;
+in
+{
+  port = 9354;
+
+  extraOpts = {
+    delugeHost = mkOption {
+      type = types.str;
+      default = "localhost";
+      description = ''
+        Hostname where deluge server is running.
+      '';
+    };
+
+    delugePort = mkOption {
+      type = types.port;
+      default = 58846;
+      description = ''
+        Port where deluge server is listening.
+      '';
+    };
+
+    delugeUser = mkOption {
+      type = types.str;
+      default = "localclient";
+      description = ''
+        User to connect to deluge server.
+      '';
+    };
+
+    delugePassword = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        Password to connect to deluge server.
+
+        This stores the password unencrypted in the nix store and is thus considered unsafe. Prefer
+        using the delugePasswordFile option.
+      '';
+    };
+
+    delugePasswordFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = ''
+        File containing the password to connect to deluge server.
+      '';
+    };
+
+    exportPerTorrentMetrics = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Enable per-torrent metrics.
+
+        This may significantly increase the number of time series depending on the number of
+        torrents in your Deluge instance.
+      '';
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      ExecStart = ''
+        ${pkgs.prometheus-deluge-exporter}/bin/deluge-exporter
+      '';
+      Environment = [
+        "LISTEN_PORT=${toString cfg.port}"
+        "LISTEN_ADDRESS=${toString cfg.listenAddress}"
+
+        "DELUGE_HOST=${cfg.delugeHost}"
+        "DELUGE_USER=${cfg.delugeUser}"
+        "DELUGE_PORT=${toString cfg.delugePort}"
+      ] ++ lib.optionals (cfg.delugePassword != null) [
+        "DELUGE_PASSWORD=${cfg.delugePassword}"
+      ] ++ lib.optionals cfg.exportPerTorrentMetrics [
+        "PER_TORRENT_METRICS=1"
+      ];
+      EnvironmentFile = lib.optionalString (cfg.delugePasswordFile != null) "/etc/deluge-exporter/password";
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/nginxlog.nix b/nixos/modules/services/monitoring/prometheus/exporters/nginxlog.nix
index 2b4fd12895a39..63d25f3d88fb3 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/nginxlog.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/nginxlog.nix
@@ -7,7 +7,27 @@ in {
   port = 9117;
   extraOpts = {
     settings = mkOption {
-      type = types.attrs;
+      type = types.submodule {
+        options = {
+          consul = mkOption {
+            default = null;
+            type = types.nullOr (types.attrsOf types.anything);
+            description = ''
+              Consul integration options. For more information see the [example config](https://github.com/martin-helmich/prometheus-nginxlog-exporter#configuration-file).
+
+              This is disabled by default.
+            '';
+          };
+          namespaces = mkOption {
+            default = [];
+            type = types.listOf (types.attrsOf types.anything);
+
+            description = ''
+              Namespaces to collect the metrics for. For more information see the [example config](https://github.com/martin-helmich/prometheus-nginxlog-exporter#configuration-file).
+            '';
+          };
+        };
+      };
       default = {};
       description = ''
         All settings of nginxlog expressed as an Nix attrset.
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/pve.nix b/nixos/modules/services/monitoring/prometheus/exporters/pve.nix
index 8928577b69532..fa5852a4999dd 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/pve.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/pve.nix
@@ -114,6 +114,13 @@ in
           Collect PVE onboot status
         '';
       };
+      replication = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Collect PVE replication info
+        '';
+      };
     };
   };
   serviceOpts = {
@@ -128,6 +135,7 @@ in
           --${optionalString (!cfg.collectors.cluster) "no-"}collector.cluster \
           --${optionalString (!cfg.collectors.resources) "no-"}collector.resources \
           --${optionalString (!cfg.collectors.config) "no-"}collector.config \
+          --${optionalString (!cfg.collectors.replication) "no-"}collector.replication \
           ${optionalString (cfg.server.keyFile != null) "--server.keyfile ${cfg.server.keyFile}"} \
           ${optionalString (cfg.server.certFile != null) "--server.certfile ${cfg.server.certFile}"} \
           --config.file %d/configFile \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/smartctl.nix b/nixos/modules/services/monitoring/prometheus/exporters/smartctl.nix
index 8aadd87abbedb..e3dcc6126ff12 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/smartctl.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/smartctl.nix
@@ -2,20 +2,23 @@
 
 let
   cfg = config.services.prometheus.exporters.smartctl;
+
   inherit (lib) mkOption types literalExpression;
+
   args = lib.escapeShellArgs ([
     "--web.listen-address=${cfg.listenAddress}:${toString cfg.port}"
-    "--smartctl.path=${pkgs.smartmontools}/bin/smartctl"
     "--smartctl.interval=${cfg.maxInterval}"
   ] ++ map (device: "--smartctl.device=${device}") cfg.devices
   ++ cfg.extraFlags);
-in {
+
+in
+{
   port = 9633;
 
   extraOpts = {
     devices = mkOption {
       type = types.listOf types.str;
-      default = [];
+      default = [ ];
       example = literalExpression ''
         [ "/dev/sda", "/dev/nvme0n1" ];
       '';
@@ -24,6 +27,7 @@ in {
         all disks if none given.
       '';
     };
+
     maxInterval = mkOption {
       type = types.str;
       default = "60s";
@@ -50,9 +54,7 @@ in {
         "block-sd rw"
         "char-nvme rw"
       ];
-      ExecStart = ''
-        ${pkgs.prometheus-smartctl-exporter}/bin/smartctl_exporter ${args}
-      '';
+      ExecStart = "${pkgs.prometheus-smartctl-exporter}/bin/smartctl_exporter ${args}";
       PrivateDevices = lib.mkForce false;
       ProtectProc = "invisible";
       ProcSubset = "pid";
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix b/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
index dc10a9a2f92ea..c647aba95499a 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
@@ -81,6 +81,33 @@ in
         Only log messages with the given severity or above.
       '';
     };
+
+    environmentFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      example = "/root/prometheus-snmp-exporter.env";
+      description = ''
+        EnvironmentFile as defined in {manpage}`systemd.exec(5)`.
+
+        Secrets may be passed to the service without adding them to the
+        world-readable Nix store, by specifying placeholder variables as
+        the option value in Nix and setting these variables accordingly in the
+        environment file.
+
+        Environment variables from this file will be interpolated into the
+        config file using envsubst with this syntax:
+        `$ENVIRONMENT ''${VARIABLE}`
+
+        For variables to use see [Prometheus Configuration](https://github.com/prometheus/snmp_exporter#prometheus-configuration).
+
+        If the file path is set to this option, the parameter
+        `--config.expand-environment-variables` is implicitly added to
+        `ExecStart`.
+
+        Note that this file needs to be available on the host on which
+        this exporter is running.
+      '';
+    };
   };
   serviceOpts = let
     uncheckedConfigFile = if cfg.configurationPath != null
@@ -92,9 +119,12 @@ in
       uncheckedConfigFile;
     in {
     serviceConfig = {
+      EnvironmentFile = lib.mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
       ExecStart = ''
         ${pkgs.prometheus-snmp-exporter}/bin/snmp_exporter \
           --config.file=${escapeShellArg configFile} \
+          ${lib.optionalString (cfg.environmentFile != null)
+            "--config.expand-environment-variables"} \
           --log.format=${escapeShellArg cfg.logFormat} \
           --log.level=${cfg.logLevel} \
           --web.listen-address=${cfg.listenAddress}:${toString cfg.port} \
diff --git a/nixos/modules/services/monitoring/prometheus/pushgateway.nix b/nixos/modules/services/monitoring/prometheus/pushgateway.nix
index 80e2339f59256..d4f9c4a29f386 100644
--- a/nixos/modules/services/monitoring/prometheus/pushgateway.nix
+++ b/nixos/modules/services/monitoring/prometheus/pushgateway.nix
@@ -147,12 +147,52 @@ in {
       wantedBy = [ "multi-user.target" ];
       after    = [ "network.target" ];
       serviceConfig = {
-        Restart  = "always";
-        DynamicUser = true;
         ExecStart = "${cfg.package}/bin/pushgateway" +
           optionalString (length cmdlineArgs != 0) (" \\\n  " +
             concatStringsSep " \\\n  " cmdlineArgs);
+
+        CapabilityBoundingSet = [ "" ];
+        DeviceAllow = [ "" ];
+        DynamicUser = true;
+        NoNewPrivileges = true;
+
+        MemoryDenyWriteExecute = true;
+
+        LockPersonality = true;
+
+        ProtectProc = "invisible";
+        ProtectSystem = "strict";
+        ProtectHome = "tmpfs";
+
+        PrivateTmp = true;
+        PrivateDevices = true;
+        PrivateIPC = true;
+
+        ProcSubset = "pid";
+
+        ProtectHostname = true;
+        ProtectClock = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectKernelLogs = true;
+        ProtectControlGroups = true;
+
+        Restart  = "always";
+
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+
         StateDirectory = if cfg.persistMetrics then cfg.stateDir else null;
+        SystemCallFilter = [
+          "@system-service"
+          "~@cpu-emulation"
+          "~@privileged"
+          "~@reboot"
+          "~@setuid"
+          "~@swap"
+        ];
       };
     };
   };
diff --git a/nixos/modules/services/monitoring/scrutiny.nix b/nixos/modules/services/monitoring/scrutiny.nix
index c649d333e401a..c0ead07066ec5 100644
--- a/nixos/modules/services/monitoring/scrutiny.nix
+++ b/nixos/modules/services/monitoring/scrutiny.nix
@@ -110,7 +110,10 @@ in
       };
 
       collector = {
-        enable = mkEnableOption "the Scrutiny metrics collector";
+        enable = mkEnableOption "the Scrutiny metrics collector" // {
+          default = cfg.enable;
+          defaultText = lib.literalExpression "config.services.scrutiny.enable";
+        };
 
         package = mkPackageOption pkgs "scrutiny-collector" { };
 
diff --git a/nixos/modules/services/networking/antennas.nix b/nixos/modules/services/networking/antennas.nix
index a37df953fc923..e98b81588044e 100644
--- a/nixos/modules/services/networking/antennas.nix
+++ b/nixos/modules/services/networking/antennas.nix
@@ -38,7 +38,7 @@ in
 
   config = mkIf cfg.enable {
     systemd.services.antennas = {
-      description = "Antennas HDHomeRun emulator for Tvheadend. ";
+      description = "Antennas HDHomeRun emulator for Tvheadend.";
       wantedBy    = [ "multi-user.target" ];
 
       # Config
diff --git a/nixos/modules/services/networking/bee.nix b/nixos/modules/services/networking/bee.nix
index da11ac9399abd..83ce522ba62af 100644
--- a/nixos/modules/services/networking/bee.nix
+++ b/nixos/modules/services/networking/bee.nix
@@ -8,7 +8,7 @@ let
 in {
   meta = {
     # doc = ./bee.xml;
-    maintainers = with maintainers; [ ];
+    maintainers = [ ];
   };
 
   ### interface
@@ -101,8 +101,7 @@ in {
 
       preStart = with cfg.settings; ''
         if ! test -f ${password-file}; then
-          < /dev/urandom tr -dc _A-Z-a-z-0-9 2> /dev/null | head -c32 > ${password-file}
-          chmod 0600 ${password-file}
+          < /dev/urandom tr -dc _A-Z-a-z-0-9 2> /dev/null | head -c32 | install -m 600 /dev/stdin ${password-file}
           echo "Initialized ${password-file} from /dev/urandom"
         fi
         if [ ! -f ${data-dir}/keys/libp2p.key ]; then
diff --git a/nixos/modules/services/networking/bind.nix b/nixos/modules/services/networking/bind.nix
index 03c20f3fe3d36..6c5c59a88dec0 100644
--- a/nixos/modules/services/networking/bind.nix
+++ b/nixos/modules/services/networking/bind.nix
@@ -271,7 +271,8 @@ in
       '';
 
       serviceConfig = {
-        ExecStart = "${bindPkg.out}/sbin/named -u ${bindUser} ${optionalString cfg.ipv4Only "-4"} -c ${cfg.configFile} -f";
+        Type = "forking"; # Set type to forking, see https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=900788
+        ExecStart = "${bindPkg.out}/sbin/named -u ${bindUser} ${optionalString cfg.ipv4Only "-4"} -c ${cfg.configFile}";
         ExecReload = "${bindPkg.out}/sbin/rndc -k '/etc/bind/rndc.key' reload";
         ExecStop = "${bindPkg.out}/sbin/rndc -k '/etc/bind/rndc.key' stop";
       };
diff --git a/nixos/modules/services/networking/cgit.nix b/nixos/modules/services/networking/cgit.nix
index de8128ed5a59c..cdd316dd99d26 100644
--- a/nixos/modules/services/networking/cgit.nix
+++ b/nixos/modules/services/networking/cgit.nix
@@ -32,7 +32,7 @@ let
       fastcgi_split_path_info ^(${regexLocation cfg})(/.+)$;
       fastcgi_param PATH_INFO $fastcgi_path_info;
     ''
-    }fastcgi_pass unix:${config.services.fcgiwrap."cgit-${name}".socket.address};
+    }fastcgi_pass unix:${config.services.fcgiwrap.instances."cgit-${name}".socket.address};
   '';
 
   cgitrcLine = name: value: "${name}=${
@@ -171,7 +171,7 @@ in
       groups.${cfg.group} = { };
     }));
 
-    services.fcgiwrap = flip mapAttrs' cfgs (name: cfg:
+    services.fcgiwrap.instances = flip mapAttrs' cfgs (name: cfg:
       nameValuePair "cgit-${name}" {
         process = { inherit (cfg) user group; };
         socket = { inherit (config.services.nginx) user group; };
diff --git a/nixos/modules/services/networking/cloudflare-dyndns.nix b/nixos/modules/services/networking/cloudflare-dyndns.nix
index ab5b1a08539a5..9495c8dcaf810 100644
--- a/nixos/modules/services/networking/cloudflare-dyndns.nix
+++ b/nixos/modules/services/networking/cloudflare-dyndns.nix
@@ -10,6 +10,8 @@ in
     services.cloudflare-dyndns = {
       enable = mkEnableOption "Cloudflare Dynamic DNS Client";
 
+      package = mkPackageOption pkgs "cloudflare-dyndns" { };
+
       apiTokenFile = mkOption {
         type = types.nullOr types.str;
         default = null;
@@ -28,6 +30,16 @@ in
         '';
       };
 
+      frequency = mkOption {
+        type = types.nullOr types.str;
+        default = "*:0/5";
+        description = ''
+          Run cloudflare-dyndns with the given frequency (see
+          {manpage}`systemd.time(7)` for the format).
+          If null, do not run automatically.
+        '';
+      };
+
       proxied = mkOption {
         type = types.bool;
         default = false;
@@ -67,7 +79,6 @@ in
       description = "CloudFlare Dynamic DNS Client";
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
-      startAt = "*:0/5";
 
       environment = {
         CLOUDFLARE_DOMAINS = toString cfg.domains;
@@ -86,8 +97,10 @@ in
               ++ optional cfg.deleteMissing "--delete-missing"
               ++ optional cfg.proxied "--proxied";
           in
-          "${pkgs.cloudflare-dyndns}/bin/cloudflare-dyndns ${toString args}";
+          "${getExe cfg.package} ${toString args}";
       };
+    } // optionalAttrs (cfg.frequency != null) {
+      startAt = cfg.frequency;
     };
   };
 }
diff --git a/nixos/modules/services/networking/cloudflare-warp.nix b/nixos/modules/services/networking/cloudflare-warp.nix
new file mode 100644
index 0000000000000..2ab5f287ac496
--- /dev/null
+++ b/nixos/modules/services/networking/cloudflare-warp.nix
@@ -0,0 +1,91 @@
+{ config, lib, pkgs, ... }:
+let
+  cfg = config.services.cloudflare-warp;
+in
+{
+  options.services.cloudflare-warp = {
+    enable = lib.mkEnableOption "Cloudflare Zero Trust client daemon";
+
+    package = lib.mkPackageOption pkgs "cloudflare-warp" { };
+
+    rootDir = lib.mkOption {
+      type = lib.types.str;
+      default = "/var/lib/cloudflare-warp";
+      description = ''
+        Working directory for the warp-svc daemon.
+      '';
+    };
+
+    udpPort = lib.mkOption {
+      type = lib.types.port;
+      default = 2408;
+      description = ''
+        The UDP port to open in the firewall. Warp uses port 2408 by default, but fallback ports can be used
+        if that conflicts with another service. See the [firewall documentation](https://developers.cloudflare.com/cloudflare-one/connections/connect-devices/warp/deployment/firewall#warp-udp-ports)
+        for the pre-configured available fallback ports.
+      '';
+    };
+
+    openFirewall = lib.mkEnableOption "opening UDP ports in the firewall" // {
+      default = true;
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+
+    networking.firewall = lib.mkIf cfg.openFirewall {
+      allowedUDPPorts = [ cfg.udpPort ];
+    };
+
+    systemd.tmpfiles.rules = [
+      "d ${cfg.rootDir}    - root root"
+      "z ${cfg.rootDir}    - root root"
+    ];
+
+    systemd.services.cloudflare-warp = {
+      enable = true;
+      description = "Cloudflare Zero Trust Client Daemon";
+
+      # lsof is used by the service to determine which UDP port to bind to
+      # in the case that it detects collisions.
+      path = [ pkgs.lsof ];
+      requires = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig =
+        let
+          caps = [
+            "CAP_NET_ADMIN"
+            "CAP_NET_BIND_SERVICE"
+            "CAP_SYS_PTRACE"
+          ];
+        in
+        {
+          Type = "simple";
+          ExecStart = "${cfg.package}/bin/warp-svc";
+          ReadWritePaths = [ "${cfg.rootDir}" "/etc/resolv.conf" ];
+          CapabilityBoundingSet = caps;
+          AmbientCapabilities = caps;
+          Restart = "always";
+          RestartSec = 5;
+          Environment = [ "RUST_BACKTRACE=full" ];
+          WorkingDirectory = cfg.rootDir;
+
+          # See the systemd.exec docs for the canonicalized paths, the service
+          # makes use of them for logging, and account state info tracking.
+          # https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#RuntimeDirectory=
+          StateDirectory = "cloudflare-warp";
+          RuntimeDirectory = "cloudflare-warp";
+          LogsDirectory = "cloudflare-warp";
+
+          # The service needs to write to /etc/resolv.conf to configure DNS, so that file would have to
+          # be world read/writable to run as anything other than root.
+          User = "root";
+          Group = "root";
+        };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ treyfortmuller ];
+}
diff --git a/nixos/modules/services/networking/ddns-updater.nix b/nixos/modules/services/networking/ddns-updater.nix
new file mode 100644
index 0000000000000..85daa59fe66de
--- /dev/null
+++ b/nixos/modules/services/networking/ddns-updater.nix
@@ -0,0 +1,46 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  cfg = config.services.ddns-updater;
+in
+{
+  options.services.ddns-updater = {
+    enable = lib.mkEnableOption "Container to update DNS records periodically with WebUI for many DNS providers";
+
+    package = lib.mkPackageOption pkgs "ddns-updater" { };
+
+    environment = lib.mkOption {
+      type = lib.types.attrsOf lib.types.str;
+      description = "Environment variables to be set for the ddns-updater service. DATADIR is ignored to enable using systemd DynamicUser. For full list see https://github.com/qdm12/ddns-updater";
+      default = { };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+
+    systemd.services.ddns-updater = {
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
+      after = [ "network-online.target" ];
+      environment = cfg.environment // {
+        DATADIR = "%S/ddns-updater";
+      };
+      unitConfig = {
+        Description = "DDNS-updater service";
+      };
+      serviceConfig = {
+        TimeoutSec = "5min";
+        ExecStart = lib.getExe cfg.package;
+        RestartSec = 30;
+        DynamicUser = true;
+        StateDirectory = "ddns-updater";
+        Restart = "on-failure";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/deconz.nix b/nixos/modules/services/networking/deconz.nix
index 88b0ee612d871..d8908470bb231 100644
--- a/nixos/modules/services/networking/deconz.nix
+++ b/nixos/modules/services/networking/deconz.nix
@@ -14,7 +14,7 @@ in
 {
   options.services.deconz = {
 
-    enable = lib.mkEnableOption "deCONZ, a Zigbee gateway for use with ConBee hardware (https://phoscon.de/en/conbee2)";
+    enable = lib.mkEnableOption "deCONZ, a Zigbee gateway for use with ConBee/RaspBee hardware (https://phoscon.de/)";
 
     package = lib.mkOption {
       type = lib.types.package;
@@ -122,6 +122,7 @@ in
         RuntimeDirectory = name;
         RuntimeDirectoryMode = "0700";
         StateDirectory = name;
+        SuccessExitStatus = [ 143 ];
         WorkingDirectory = stateDir;
         # For access to /dev/ttyACM0 (ConBee).
         SupplementaryGroups = [ "dialout" ];
diff --git a/nixos/modules/services/networking/eternal-terminal.nix b/nixos/modules/services/networking/eternal-terminal.nix
index f4456f4d99c8b..d26e26d0c1950 100644
--- a/nixos/modules/services/networking/eternal-terminal.nix
+++ b/nixos/modules/services/networking/eternal-terminal.nix
@@ -90,6 +90,6 @@ in
   };
 
   meta = {
-    maintainers = with lib.maintainers; [ ];
+    maintainers = [ ];
   };
 }
diff --git a/nixos/modules/services/networking/firefox-syncserver.nix b/nixos/modules/services/networking/firefox-syncserver.nix
index 674a424fb0a42..2f706c3bc3f98 100644
--- a/nixos/modules/services/networking/firefox-syncserver.nix
+++ b/nixos/modules/services/networking/firefox-syncserver.nix
@@ -316,7 +316,7 @@ in
   };
 
   meta = {
-    maintainers = with lib.maintainers; [ ];
+    maintainers = [ ];
     doc = ./firefox-syncserver.md;
   };
 }
diff --git a/nixos/modules/services/networking/hans.nix b/nixos/modules/services/networking/hans.nix
index 00d276bcdf60a..0d0e2c340ebf1 100644
--- a/nixos/modules/services/networking/hans.nix
+++ b/nixos/modules/services/networking/hans.nix
@@ -141,5 +141,5 @@ in
     };
   };
 
-  meta.maintainers = with maintainers; [ ];
+  meta.maintainers = [ ];
 }
diff --git a/nixos/modules/services/networking/trust-dns.nix b/nixos/modules/services/networking/hickory-dns.nix
index 039b7de263504..6b99686958d72 100644
--- a/nixos/modules/services/networking/trust-dns.nix
+++ b/nixos/modules/services/networking/hickory-dns.nix
@@ -1,9 +1,9 @@
 { config, lib, pkgs, ... }:
 let
-  cfg = config.services.trust-dns;
+  cfg = config.services.hickory-dns;
   toml = pkgs.formats.toml { };
 
-  configFile = toml.generate "trust-dns.toml" (
+  configFile = toml.generate "hickory-dns.toml" (
     lib.filterAttrsRecursive (_: v: v != null) cfg.settings
   );
 
@@ -26,7 +26,7 @@ let
           - "Forward" (a cached zone where all requests are forwarded to another resolver).
 
           For more details about these zone types, consult the documentation for BIND,
-          though note that trust-dns supports only a subset of BIND's zone types:
+          though note that hickory-dns supports only a subset of BIND's zone types:
           <https://bind9.readthedocs.io/en/v9_18_4/reference.html#type>
         '';
       };
@@ -45,10 +45,19 @@ let
 in
 {
   meta.maintainers = with lib.maintainers; [ colinsane ];
+
+  imports = with lib; [
+    (mkRenamedOptionModule [ "services" "trust-dns" "enable" ] [ "services" "hickory-dns" "enable" ])
+    (mkRenamedOptionModule [ "services" "trust-dns" "package" ] [ "services" "hickory-dns" "package" ])
+    (mkRenamedOptionModule [ "services" "trust-dns" "settings" ] [ "services" "hickory-dns" "settings" ])
+    (mkRenamedOptionModule [ "services" "trust-dns" "quiet" ] [ "services" "hickory-dns" "quiet" ])
+    (mkRenamedOptionModule [ "services" "trust-dns" "debug" ] [ "services" "hickory-dns" "debug" ])
+  ];
+
   options = {
-    services.trust-dns = with lib; {
-      enable = mkEnableOption "trust-dns";
-      package = mkPackageOption pkgs "trust-dns" {
+    services.hickory-dns = with lib; {
+      enable = mkEnableOption "hickory-dns";
+      package = mkPackageOption pkgs "hickory-dns" {
         extraDescription = ''
           ::: {.note}
           The package must provide `meta.mainProgram` which names the server binary; any other utilities (client, resolver) are not needed.
@@ -75,9 +84,9 @@ in
       };
       settings = mkOption {
         description = ''
-          Settings for trust-dns. The options enumerated here are not exhaustive.
+          Settings for hickory-dns. The options enumerated here are not exhaustive.
           Refer to upstream documentation for all available options:
-          - [Example settings](https://github.com/bluejekyll/trust-dns/blob/main/tests/test-data/test_configs/example.toml)
+          - [Example settings](https://github.com/hickory-dns/hickory-dns/blob/main/tests/test-data/test_configs/example.toml)
         '';
         type = types.submodule {
           freeformType = toml.type;
@@ -106,9 +115,9 @@ in
             };
             directory = mkOption {
               type = types.str;
-              default = "/var/lib/trust-dns";
+              default = "/var/lib/hickory-dns";
               description = ''
-                The directory in which trust-dns should look for .zone files,
+                The directory in which hickory-dns should look for .zone files,
                 whenever zones aren't specified by absolute path.
               '';
             };
@@ -124,23 +133,23 @@ in
   };
 
   config = lib.mkIf cfg.enable {
-    systemd.services.trust-dns = {
-      description = "trust-dns Domain Name Server";
-      unitConfig.Documentation = "https://trust-dns.org/";
+    systemd.services.hickory-dns = {
+      description = "hickory-dns Domain Name Server";
+      unitConfig.Documentation = "https://hickory-dns.org/";
       serviceConfig = {
         ExecStart =
         let
           flags =  (lib.optional cfg.debug "--debug") ++ (lib.optional cfg.quiet "--quiet");
           flagsStr = builtins.concatStringsSep " " flags;
         in ''
-          ${cfg.package}/bin/${cfg.package.meta.mainProgram} --config ${configFile} ${flagsStr}
+          ${lib.getExe cfg.package} --config ${configFile} ${flagsStr}
         '';
         Type = "simple";
         Restart = "on-failure";
         RestartSec = "10s";
         DynamicUser = true;
 
-        StateDirectory = "trust-dns";
+        StateDirectory = "hickory-dns";
         ReadWritePaths = [ cfg.settings.directory ];
 
         AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
diff --git a/nixos/modules/services/networking/i2pd.nix b/nixos/modules/services/networking/i2pd.nix
index aa059b1b7c90a..2bb9df15de2c3 100644
--- a/nixos/modules/services/networking/i2pd.nix
+++ b/nixos/modules/services/networking/i2pd.nix
@@ -183,37 +183,45 @@ let
   in
     pkgs.writeText "i2pd.conf" (concatStringsSep "\n" opts);
 
-  tunnelConf = let opts = [
-    notice
-    (flip map
-      (collect (tun: tun ? port && tun ? destination) cfg.outTunnels)
-      (tun: let outTunOpts = [
-        (sec tun.name)
-        "type = client"
-        (intOpt "port" tun.port)
-        (strOpt "destination" tun.destination)
+  tunnelConf = let
+    mkOutTunnel = tun:
+      let
+        outTunOpts = [
+          (sec tun.name)
+          "type = client"
+          (intOpt "port" tun.port)
+          (strOpt "destination" tun.destination)
         ] ++ (optionals (tun ? destinationPort) (optionalNullInt "destinationport" tun.destinationPort))
-        ++ (optionals (tun ? keys) (optionalNullString "keys" tun.keys))
-        ++ (optionals (tun ? address) (optionalNullString "address" tun.address))
-        ++ (optionals (tun ? inbound.length) (optionalNullInt "inbound.length" tun.inbound.length))
-        ++ (optionals (tun ? inbound.quantity) (optionalNullInt "inbound.quantity" tun.inbound.quantity))
-        ++ (optionals (tun ? outbound.length) (optionalNullInt "outbound.length" tun.outbound.length))
-        ++ (optionals (tun ? outbound.quantity) (optionalNullInt "outbound.quantity" tun.outbound.quantity))
-        ++ (optionals (tun ? crypto.tagsToSend) (optionalNullInt "crypto.tagstosend" tun.crypto.tagsToSend));
-        in concatStringsSep "\n" outTunOpts))
-    (flip map
-      (collect (tun: tun ? port && tun ? address) cfg.inTunnels)
-      (tun: let inTunOpts = [
-        (sec tun.name)
-        "type = server"
-        (intOpt "port" tun.port)
-        (strOpt "host" tun.address)
-      ] ++ (optionals (tun ? destination) (optionalNullString "destination" tun.destination))
-        ++ (optionals (tun ? keys) (optionalNullString "keys" tun.keys))
-        ++ (optionals (tun ? inPort) (optionalNullInt "inport" tun.inPort))
-        ++ (optionals (tun ? accessList) (optionalEmptyList "accesslist" tun.accessList));
-        in concatStringsSep "\n" inTunOpts))];
-    in pkgs.writeText "i2pd-tunnels.conf" opts;
+          ++ (optionals (tun ? keys) (optionalNullString "keys" tun.keys))
+          ++ (optionals (tun ? address) (optionalNullString "address" tun.address))
+          ++ (optionals (tun ? inbound.length) (optionalNullInt "inbound.length" tun.inbound.length))
+          ++ (optionals (tun ? inbound.quantity) (optionalNullInt "inbound.quantity" tun.inbound.quantity))
+          ++ (optionals (tun ? outbound.length) (optionalNullInt "outbound.length" tun.outbound.length))
+          ++ (optionals (tun ? outbound.quantity) (optionalNullInt "outbound.quantity" tun.outbound.quantity))
+          ++ (optionals (tun ? crypto.tagsToSend) (optionalNullInt "crypto.tagstosend" tun.crypto.tagsToSend));
+      in
+        concatStringsSep "\n" outTunOpts;
+
+    mkInTunnel = tun:
+      let
+        inTunOpts = [
+          (sec tun.name)
+          "type = server"
+          (intOpt "port" tun.port)
+          (strOpt "host" tun.address)
+        ] ++ (optionals (tun ? destination) (optionalNullString "destination" tun.destination))
+          ++ (optionals (tun ? keys) (optionalNullString "keys" tun.keys))
+          ++ (optionals (tun ? inPort) (optionalNullInt "inport" tun.inPort))
+          ++ (optionals (tun ? accessList) (optionalEmptyList "accesslist" tun.accessList));
+      in
+        concatStringsSep "\n" inTunOpts;
+
+    allOutTunnels = collect (tun: tun ? port && tun ? destination) cfg.outTunnels;
+    allInTunnels = collect (tun: tun ? port && tun ? address) cfg.inTunnels;
+
+    opts = [ notice ] ++ (map mkOutTunnel allOutTunnels) ++ (map mkInTunnel allInTunnels);
+  in
+    pkgs.writeText "i2pd-tunnels.conf" (concatStringsSep "\n" opts);
 
   i2pdFlags = concatStringsSep " " (
     optional (cfg.address != null) ("--host=" + cfg.address) ++ [
diff --git a/nixos/modules/services/networking/magic-wormhole-mailbox-server.nix b/nixos/modules/services/networking/magic-wormhole-mailbox-server.nix
index 03210bca371cf..5b700269037c2 100644
--- a/nixos/modules/services/networking/magic-wormhole-mailbox-server.nix
+++ b/nixos/modules/services/networking/magic-wormhole-mailbox-server.nix
@@ -1,18 +1,27 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
 
 let
   cfg = config.services.magic-wormhole-mailbox-server;
+  # keep semicolon in dataDir for backward compatibility
   dataDir = "/var/lib/magic-wormhole-mailbox-server;";
-  python = pkgs.python3.withPackages (py: [ py.magic-wormhole-mailbox-server py.twisted ]);
+  python = pkgs.python311.withPackages (
+    py: with py; [
+      magic-wormhole-mailbox-server
+      twisted
+    ]
+  );
 in
 {
   options.services.magic-wormhole-mailbox-server = {
-    enable = mkEnableOption "Magic Wormhole Mailbox Server";
+    enable = lib.mkEnableOption "Magic Wormhole Mailbox Server";
   };
 
-  config = mkIf cfg.enable {
+  config = lib.mkIf cfg.enable {
     systemd.services.magic-wormhole-mailbox-server = {
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
@@ -23,6 +32,7 @@ in
         StateDirectory = baseNameOf dataDir;
       };
     };
-
   };
+
+  meta.maintainers = [ lib.maintainers.mjoerg ];
 }
diff --git a/nixos/modules/services/networking/mosquitto.nix b/nixos/modules/services/networking/mosquitto.nix
index 7baaf93a1bcfa..468f58b56bb1c 100644
--- a/nixos/modules/services/networking/mosquitto.nix
+++ b/nixos/modules/services/networking/mosquitto.nix
@@ -483,7 +483,7 @@ let
 
     listeners = mkOption {
       type = listOf listenerOptions;
-      default = {};
+      default = [];
       description = ''
         Listeners to configure on this broker.
       '';
@@ -721,7 +721,7 @@ in
   };
 
   meta = {
-    maintainers = with lib.maintainers; [ ];
+    maintainers = [ ];
     doc = ./mosquitto.md;
   };
 }
diff --git a/nixos/modules/services/networking/mxisd.nix b/nixos/modules/services/networking/mxisd.nix
deleted file mode 100644
index e53fb71788cdc..0000000000000
--- a/nixos/modules/services/networking/mxisd.nix
+++ /dev/null
@@ -1,137 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  isMa1sd =
-    package:
-    lib.hasPrefix "ma1sd" package.name;
-
-  isMxisd =
-    package:
-    lib.hasPrefix "mxisd" package.name;
-
-  cfg = config.services.mxisd;
-
-  server = optionalAttrs (cfg.server.name != null) { inherit (cfg.server) name; }
-        // optionalAttrs (cfg.server.port != null) { inherit (cfg.server) port; };
-
-  baseConfig = {
-    matrix.domain = cfg.matrix.domain;
-    key.path = "${cfg.dataDir}/signing.key";
-    storage = {
-      provider.sqlite.database = if isMa1sd cfg.package
-                                 then "${cfg.dataDir}/ma1sd.db"
-                                 else "${cfg.dataDir}/mxisd.db";
-    };
-  } // optionalAttrs (server != {}) { inherit server; };
-
-  # merges baseConfig and extraConfig into a single file
-  fullConfig = recursiveUpdate baseConfig cfg.extraConfig;
-
-  configFile = if isMa1sd cfg.package
-               then pkgs.writeText "ma1sd-config.yaml" (builtins.toJSON fullConfig)
-               else pkgs.writeText "mxisd-config.yaml" (builtins.toJSON fullConfig);
-
-in {
-  options = {
-    services.mxisd = {
-      enable = mkEnableOption "matrix federated identity server";
-
-      package = mkPackageOption pkgs "ma1sd" { };
-
-      environmentFile = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = ''
-          Path to an environment-file which may contain secrets to be
-          substituted via `envsubst`.
-        '';
-      };
-
-      dataDir = mkOption {
-        type = types.str;
-        default = "/var/lib/mxisd";
-        description = "Where data mxisd/ma1sd uses resides";
-      };
-
-      extraConfig = mkOption {
-        type = types.attrs;
-        default = {};
-        description = "Extra options merged into the mxisd/ma1sd configuration";
-      };
-
-      matrix = {
-
-        domain = mkOption {
-          type = types.str;
-          description = ''
-            the domain of the matrix homeserver
-          '';
-        };
-
-      };
-
-      server = {
-
-        name = mkOption {
-          type = types.nullOr types.str;
-          default = null;
-          description = ''
-            Public hostname of mxisd/ma1sd, if different from the Matrix domain.
-          '';
-        };
-
-        port = mkOption {
-          type = types.nullOr types.int;
-          default = null;
-          description = ''
-            HTTP port to listen on (unencrypted)
-          '';
-        };
-
-      };
-
-    };
-  };
-
-  config = mkIf cfg.enable {
-    users.users.mxisd =
-      {
-        group = "mxisd";
-        home = cfg.dataDir;
-        createHome = true;
-        shell = "${pkgs.bash}/bin/bash";
-        uid = config.ids.uids.mxisd;
-      };
-
-    users.groups.mxisd =
-      {
-        gid = config.ids.gids.mxisd;
-      };
-
-    systemd.services.mxisd = {
-      description = "a federated identity server for the matrix ecosystem";
-      after = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
-
-      serviceConfig = let
-        executable = if isMa1sd cfg.package then "ma1sd" else "mxisd";
-      in {
-        Type = "simple";
-        User = "mxisd";
-        Group = "mxisd";
-        EnvironmentFile = mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
-        ExecStart = "${cfg.package}/bin/${executable} -c ${cfg.dataDir}/mxisd-config.yaml";
-        ExecStartPre = "${pkgs.writeShellScript "mxisd-substitute-secrets" ''
-          umask 0077
-          ${pkgs.envsubst}/bin/envsubst -o ${cfg.dataDir}/mxisd-config.yaml \
-            -i ${configFile}
-        ''}";
-        WorkingDirectory = cfg.dataDir;
-        Restart = "on-failure";
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/networking/nar-serve.nix b/nixos/modules/services/networking/nar-serve.nix
index b2082032ad90f..5a5cefe86d29f 100644
--- a/nixos/modules/services/networking/nar-serve.nix
+++ b/nixos/modules/services/networking/nar-serve.nix
@@ -12,6 +12,8 @@ in
     services.nar-serve = {
       enable = mkEnableOption "serving NAR file contents via HTTP";
 
+      package = mkPackageOption pkgs "nar-serve" { };
+
       port = mkOption {
         type = types.port;
         default = 8383;
@@ -32,6 +34,17 @@ in
           - gs:// for binary caches stored in Google Cloud Storage
         '';
       };
+
+      domain = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          When set, enables the feature of serving <nar-hash>.<domain>
+          on top of <domain>/nix/store/<nar-hash>-<pname>.
+
+          Useful to preview static websites where paths are absolute.
+        '';
+      };
     };
   };
 
@@ -47,7 +60,7 @@ in
       serviceConfig = {
         Restart = "always";
         RestartSec = "5s";
-        ExecStart = "${pkgs.nar-serve}/bin/nar-serve";
+        ExecStart = lib.getExe cfg.package;
         DynamicUser = true;
       };
     };
diff --git a/nixos/modules/services/networking/nats.nix b/nixos/modules/services/networking/nats.nix
index f159ef068b561..4851daae4dbab 100644
--- a/nixos/modules/services/networking/nats.nix
+++ b/nixos/modules/services/networking/nats.nix
@@ -10,6 +10,13 @@ let
 
   configFile = format.generate "nats.conf" cfg.settings;
 
+  validateConfig = file:
+  pkgs.runCommand "validate-nats-conf" {
+    nativeBuildInputs = [ pkgs.nats-server ];
+  } ''
+    nats-server --config "${configFile}" -t
+    ln -s "${configFile}" "$out"
+  '';
 in {
 
   ### Interface
@@ -104,7 +111,7 @@ in {
         })
         {
           Type = "simple";
-          ExecStart = "${pkgs.nats-server}/bin/nats-server -c ${configFile}";
+          ExecStart = "${pkgs.nats-server}/bin/nats-server -c ${validateConfig configFile}";
           ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
           ExecStop = "${pkgs.coreutils}/bin/kill -SIGINT $MAINPID";
           Restart = "on-failure";
diff --git a/nixos/modules/services/networking/nebula.nix b/nixos/modules/services/networking/nebula.nix
index 56eed04c3e8d9..d69af8d16b52c 100644
--- a/nixos/modules/services/networking/nebula.nix
+++ b/nixos/modules/services/networking/nebula.nix
@@ -51,8 +51,8 @@ in
             };
 
             key = mkOption {
-              type = types.path;
-              description = "Path to the host key.";
+              type = types.oneOf [types.nonEmptyStr types.path];
+              description = "Path or reference to the host key.";
               example = "/etc/nebula/host.key";
             };
 
@@ -241,7 +241,7 @@ in
               ProtectKernelModules = true;
               ProtectKernelTunables = true;
               ProtectProc = "invisible";
-              ProtectSystem = "strict";
+              ProtectSystem = true;
               RestrictNamespaces = true;
               RestrictSUIDSGID = true;
               User = networkId;
@@ -269,4 +269,6 @@ in
       ${nameToId netName} = {};
     }) enabledNetworks);
   };
+
+  meta.maintainers = [ numinit ];
 }
diff --git a/nixos/modules/services/networking/opengfw.nix b/nixos/modules/services/networking/opengfw.nix
new file mode 100644
index 0000000000000..1866e75487dd3
--- /dev/null
+++ b/nixos/modules/services/networking/opengfw.nix
@@ -0,0 +1,414 @@
+{
+  lib,
+  pkgs,
+  config,
+  ...
+}:
+let
+  inherit (lib)
+    mkOption
+    types
+    mkIf
+    optionalString
+    ;
+  cfg = config.services.opengfw;
+in
+{
+  options.services.opengfw = {
+    enable = lib.mkEnableOption ''
+      OpenGFW, A flexible, easy-to-use, open source implementation of GFW on Linux
+    '';
+
+    package = lib.mkPackageOption pkgs "opengfw" { default = "opengfw"; };
+
+    user = mkOption {
+      default = "opengfw";
+      type = types.singleLineStr;
+      description = "Username of the OpenGFW user.";
+    };
+
+    dir = mkOption {
+      default = "/var/lib/opengfw";
+      type = types.singleLineStr;
+      description = ''
+        Working directory of the OpenGFW service and home of `opengfw.user`.
+      '';
+    };
+
+    logFile = mkOption {
+      default = null;
+      type = types.nullOr types.path;
+      example = "/var/lib/opengfw/opengfw.log";
+      description = ''
+        File to write the output to instead of systemd.
+      '';
+    };
+
+    logFormat = mkOption {
+      description = ''
+        Format of the logs. [logFormatMap](https://github.com/apernet/OpenGFW/blob/d7737e92117a11c9a6100d53019fac3b9d724fe3/cmd/root.go#L62)
+      '';
+      default = "json";
+      example = "console";
+      type = types.enum [
+        "json"
+        "console"
+      ];
+    };
+
+    pcapReplay = mkOption {
+      default = null;
+      example = "./opengfw.pcap";
+      type = types.nullOr types.path;
+      description = ''
+        Path to PCAP replay file.
+        In pcap mode, none of the actions in the rules have any effect.
+        This mode is mainly for debugging.
+      '';
+    };
+
+    logLevel = mkOption {
+      description = ''
+        Level of the logs. [logLevelMap](https://github.com/apernet/OpenGFW/blob/d7737e92117a11c9a6100d53019fac3b9d724fe3/cmd/root.go#L55)
+      '';
+      default = "info";
+      example = "warn";
+      type = types.enum [
+        "debug"
+        "info"
+        "warn"
+        "error"
+      ];
+    };
+
+    rulesFile = mkOption {
+      default = null;
+      type = types.nullOr types.path;
+      description = ''
+        Path to file containing OpenGFW rules.
+      '';
+    };
+
+    settingsFile = mkOption {
+      default = null;
+      type = types.nullOr types.path;
+      description = ''
+        Path to file containing OpenGFW settings.
+      '';
+    };
+
+    settings = mkOption {
+      default = null;
+      description = ''
+        Settings passed to OpenGFW. [Example config](https://gfw.dev/docs/build-run/#config-example)
+      '';
+      type = types.nullOr (
+        types.submodule {
+          options = {
+            replay = mkOption {
+              description = ''
+                PCAP replay settings.
+              '';
+              default = { };
+              type = types.submodule {
+                options = {
+                  realtime = mkOption {
+                    description = ''
+                      Whether the packets in the PCAP file should be replayed in "real time" (instead of as fast as possible).
+                    '';
+                    default = false;
+                    example = true;
+                    type = types.bool;
+                  };
+                };
+              };
+            };
+
+            io = mkOption {
+              description = ''
+                IO settings.
+              '';
+              default = { };
+              type = types.submodule {
+                options = {
+                  queueSize = mkOption {
+                    description = "IO queue size.";
+                    type = types.int;
+                    default = 1024;
+                    example = 2048;
+                  };
+                  local = mkOption {
+                    description = ''
+                      Set to false if you want to run OpenGFW on FORWARD chain. (e.g. on a router)
+                    '';
+                    type = types.bool;
+                    default = true;
+                    example = false;
+                  };
+                  rst = mkOption {
+                    description = ''
+                      Set to true if you want to send RST for blocked TCP connections, needs `local = false`.
+                    '';
+                    type = types.bool;
+                    default = !cfg.settings.io.local;
+                    defaultText = "`!config.services.opengfw.settings.io.local`";
+                    example = false;
+                  };
+                  rcvBuf = mkOption {
+                    description = "Netlink receive buffer size.";
+                    type = types.int;
+                    default = 4194304;
+                    example = 2097152;
+                  };
+                  sndBuf = mkOption {
+                    description = "Netlink send buffer size.";
+                    type = types.int;
+                    default = 4194304;
+                    example = 2097152;
+                  };
+                };
+              };
+            };
+            ruleset = mkOption {
+              description = ''
+                The path to load specific local geoip/geosite db files.
+                If not set, they will be automatically downloaded from (Loyalsoldier/v2ray-rules-dat)[https://github.com/Loyalsoldier/v2ray-rules-dat].
+              '';
+              default = { };
+              type = types.submodule {
+                options = {
+                  geoip = mkOption {
+                    description = "Path to `geoip.dat`.";
+                    default = null;
+                    type = types.nullOr types.path;
+                  };
+                  geosite = mkOption {
+                    description = "Path to `geosite.dat`.";
+                    default = null;
+                    type = types.nullOr types.path;
+                  };
+                };
+              };
+            };
+            workers = mkOption {
+              default = { };
+              description = "Worker settings.";
+              type = types.submodule {
+                options = {
+                  count = mkOption {
+                    type = types.int;
+                    description = ''
+                      Number of workers.
+                      Recommended to be no more than the number of CPU cores
+                    '';
+                    default = 4;
+                    example = 8;
+                  };
+                  queueSize = mkOption {
+                    type = types.int;
+                    description = "Worker queue size.";
+                    default = 16;
+                    example = 32;
+                  };
+                  tcpMaxBufferedPagesTotal = mkOption {
+                    type = types.int;
+                    description = ''
+                      TCP max total buffered pages.
+                    '';
+                    default = 4096;
+                    example = 8192;
+                  };
+                  tcpMaxBufferedPagesPerConn = mkOption {
+                    type = types.int;
+                    description = ''
+                      TCP max total bufferd pages per connection.
+                    '';
+                    default = 64;
+                    example = 128;
+                  };
+                  tcpTimeout = mkOption {
+                    type = types.str;
+                    description = ''
+                      How long a connection is considered dead when no data is being transferred.
+                      Dead connections are purged from TCP reassembly pools once per minute.
+                    '';
+                    default = "10m";
+                    example = "5m";
+                  };
+                  udpMaxStreams = mkOption {
+                    type = types.int;
+                    description = "UDP max streams.";
+                    default = 4096;
+                    example = 8192;
+                  };
+                };
+              };
+            };
+          };
+        }
+      );
+    };
+
+    rules = mkOption {
+      default = [ ];
+      description = ''
+        Rules passed to OpenGFW. [Example rules](https://gfw.dev/docs/rules)
+      '';
+      type = types.listOf (
+        types.submodule {
+          options = {
+            name = mkOption {
+              description = "Name of the rule.";
+              example = "block google dns";
+              type = types.singleLineStr;
+            };
+
+            action = mkOption {
+              description = ''
+                Action of the rule. [Supported actions](https://gfw.dev/docs/rules#supported-actions)
+              '';
+              default = "allow";
+              example = "block";
+              type = types.enum [
+                "allow"
+                "block"
+                "drop"
+                "modify"
+              ];
+            };
+
+            log = mkOption {
+              description = "Whether to enable logging for the rule.";
+              default = true;
+              example = false;
+              type = types.bool;
+            };
+
+            expr = mkOption {
+              description = ''
+                [Expr Language](https://expr-lang.org/docs/language-definition) expression using [analyzers](https://gfw.dev/docs/analyzers) and [functions](https://gfw.dev/docs/functions).
+              '';
+              type = types.str;
+              example = ''dns != nil && dns.qr && any(dns.questions, {.name endsWith "google.com"})'';
+            };
+
+            modifier = mkOption {
+              default = null;
+              description = ''
+                Modification of specified packets when using the `modify` action. [Available modifiers](https://github.com/apernet/OpenGFW/tree/master/modifier)
+              '';
+              type = types.nullOr (
+                types.submodule {
+                  options = {
+                    name = mkOption {
+                      description = "Name of the modifier.";
+                      type = types.singleLineStr;
+                      example = "dns";
+                    };
+
+                    args = mkOption {
+                      description = "Arguments passed to the modifier.";
+                      type = types.attrs;
+                      example = {
+                        a = "0.0.0.0";
+                        aaaa = "::";
+                      };
+                    };
+                  };
+                }
+              );
+            };
+          };
+        }
+      );
+
+      example = [
+        {
+          name = "block v2ex http";
+          action = "block";
+          expr = ''string(http?.req?.headers?.host) endsWith "v2ex.com"'';
+        }
+        {
+          name = "block google socks";
+          action = "block";
+          expr = ''string(socks?.req?.addr) endsWith "google.com" && socks?.req?.port == 80'';
+        }
+        {
+          name = "v2ex dns poisoning";
+          action = "modify";
+          modifier = {
+            name = "dns";
+            args = {
+              a = "0.0.0.0";
+              aaaa = "::";
+            };
+          };
+          expr = ''dns != nil && dns.qr && any(dns.questions, {.name endsWith "v2ex.com"})'';
+        }
+      ];
+    };
+  };
+
+  config =
+    let
+      format = pkgs.formats.yaml { };
+
+      settings =
+        if cfg.settings != null then
+          format.generate "opengfw-config.yaml" cfg.settings
+        else
+          cfg.settingsFile;
+      rules = if cfg.rules != [ ] then format.generate "opengfw-rules.yaml" cfg.rules else cfg.rulesFile;
+    in
+    mkIf cfg.enable {
+      security.wrappers.OpenGFW = {
+        owner = cfg.user;
+        group = cfg.user;
+        capabilities = "cap_net_admin+ep";
+        source = "${cfg.package}/bin/OpenGFW";
+      };
+
+      systemd.services.opengfw = {
+        description = "OpenGFW";
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+        path = with pkgs; [ iptables ];
+
+        preStart = ''
+          ${optionalString (rules != null) "ln -sf ${rules} rules.yaml"}
+          ${optionalString (settings != null) "ln -sf ${settings} config.yaml"}
+        '';
+
+        script = ''
+          ${config.security.wrapperDir}/OpenGFW \
+            -f ${cfg.logFormat} \
+            -l ${cfg.logLevel} \
+            ${optionalString (cfg.pcapReplay != null) "-p ${cfg.pcapReplay}"} \
+            -c config.yaml \
+            rules.yaml
+        '';
+
+        serviceConfig = rec {
+          WorkingDirectory = cfg.dir;
+          ExecReload = "kill -HUP $MAINPID";
+          Restart = "always";
+          User = cfg.user;
+          StandardOutput = mkIf (cfg.logFile != null) "append:${cfg.logFile}";
+          StandardError = StandardOutput;
+        };
+      };
+
+      users = {
+        groups.${cfg.user} = { };
+        users.${cfg.user} = {
+          description = "opengfw user";
+          isSystemUser = true;
+          group = cfg.user;
+          home = cfg.dir;
+          createHome = true;
+          homeMode = "750";
+        };
+      };
+    };
+  meta.maintainers = with lib.maintainers; [ eum3l ];
+}
diff --git a/nixos/modules/services/networking/openvpn.nix b/nixos/modules/services/networking/openvpn.nix
index 4a00cdc649754..fba6d5e48f927 100644
--- a/nixos/modules/services/networking/openvpn.nix
+++ b/nixos/modules/services/networking/openvpn.nix
@@ -223,7 +223,7 @@ in
 
   config = mkIf (cfg.servers != { }) {
 
-    systemd.services = (listToAttrs (mapAttrsFlatten (name: value: nameValuePair "openvpn-${name}" (makeOpenVPNJob value name)) cfg.servers))
+    systemd.services = (listToAttrs (mapAttrsToList (name: value: nameValuePair "openvpn-${name}" (makeOpenVPNJob value name)) cfg.servers))
       // restartService;
 
     environment.systemPackages = [ openvpn ];
diff --git a/nixos/modules/services/networking/pppd.nix b/nixos/modules/services/networking/pppd.nix
index 8310b119b5f67..3642a4b15a661 100644
--- a/nixos/modules/services/networking/pppd.nix
+++ b/nixos/modules/services/networking/pppd.nix
@@ -7,7 +7,7 @@ let
 in
 {
   meta = {
-    maintainers = with maintainers; [ ];
+    maintainers = [ ];
   };
 
   options = {
diff --git a/nixos/modules/services/networking/prosody.nix b/nixos/modules/services/networking/prosody.nix
index 762c5d8d61468..9e54dfc17dfe7 100644
--- a/nixos/modules/services/networking/prosody.nix
+++ b/nixos/modules/services/networking/prosody.nix
@@ -266,6 +266,13 @@ let
     else if builtins.isList x then "{ ${lib.concatMapStringsSep ", " toLua x} }"
     else throw "Invalid Lua value";
 
+  settingsToLua = prefix: settings: generators.toKeyValue {
+    listsAsDuplicateKeys = false;
+    mkKeyValue = k: generators.mkKeyValueDefault {
+      mkValueString = toLua;
+    } " = " (prefix + k);
+  } (filterAttrs (k: v: v != null) settings);
+
   createSSLOptsStr = o: ''
     ssl = {
       cafile = "/etc/ssl/certs/ca-bundle.crt";
@@ -418,15 +425,26 @@ let
       httpUploadPath = mkOption {
         type = types.str;
         description = ''
-          Directory where the uploaded files will be stored. By
-          default, uploaded files are put in a sub-directory of the
-          default Prosody storage path (usually /var/lib/prosody).
+          Directory where the uploaded files will be stored when the http_upload module is used.
+          By default, uploaded files are put in a sub-directory of the default Prosody storage path (usually /var/lib/prosody).
         '';
         default = "/var/lib/prosody";
       };
     };
   };
 
+  httpFileShareOpts = { ... }: {
+    freeformType = with types;
+      let atom = oneOf [ int bool str (listOf atom) ]; in
+      attrsOf (nullOr atom) // {
+        description = "int, bool, string or list of them";
+      };
+    options.domain = mkOption {
+      type = with types; nullOr str;
+      description = "Domain name for a http_file_share service.";
+    };
+  };
+
   vHostOpts = { ... }: {
 
     options = {
@@ -650,7 +668,7 @@ in
 
       uploadHttp = mkOption {
         description = ''
-          Configures the Prosody builtin HTTP server to handle user uploads.
+          Configures the old Prosody builtin HTTP server to handle user uploads.
         '';
         type = types.nullOr (types.submodule uploadHttpOpts);
         default = null;
@@ -659,6 +677,17 @@ in
         };
       };
 
+      httpFileShare = mkOption {
+        description = ''
+          Configures the http_file_share module to handle user uploads.
+        '';
+        type = types.nullOr (types.submodule httpFileShareOpts);
+        default = null;
+        example = {
+          domain = "uploads.my-xmpp-example-host.org";
+        };
+      };
+
       muc = mkOption {
         type = types.listOf (types.submodule mucOpts);
         default = [ ];
@@ -751,11 +780,10 @@ in
             You need to setup at least a MUC domain to comply with
             XEP-0423.
           '' + genericErrMsg;}
-        { assertion = cfg.uploadHttp != null || !cfg.xmppComplianceSuite;
+        { assertion = cfg.uploadHttp != null || cfg.httpFileShare != null || !cfg.xmppComplianceSuite;
           message = ''
-            You need to setup the uploadHttp module through
-            config.services.prosody.uploadHttp to comply with
-            XEP-0423.
+            You need to setup the http_upload or http_file_share modules through config.services.prosody.uploadHttp
+            or config.services.prosody.httpFileShare to comply with XEP-0423.
           '' + genericErrMsg;}
       ];
     in errors;
@@ -764,8 +792,11 @@ in
 
     environment.etc."prosody/prosody.cfg.lua".text =
       let
-        httpDiscoItems = optionals (cfg.uploadHttp != null)
-            [{ url = cfg.uploadHttp.domain; description = "HTTP upload endpoint";}];
+        httpDiscoItems = optional (cfg.uploadHttp != null) {
+          url = cfg.uploadHttp.domain; description = "HTTP upload endpoint";
+        } ++ optional (cfg.httpFileShare != null) {
+          url = cfg.httpFileShare.domain; description = "HTTP file share endpoint";
+        };
         mucDiscoItems = builtins.foldl'
             (acc: muc: [{ url = muc.domain; description = "${muc.domain} MUC endpoint";}] ++ acc)
             []
@@ -844,7 +875,6 @@ in
         '') cfg.muc}
 
       ${ lib.optionalString (cfg.uploadHttp != null) ''
-        -- TODO: think about migrating this to mod-http_file_share instead.
         Component ${toLua cfg.uploadHttp.domain} "http_upload"
             http_upload_file_size_limit = ${cfg.uploadHttp.uploadFileSizeLimit}
             http_upload_expire_after = ${cfg.uploadHttp.uploadExpireAfter}
@@ -852,6 +882,11 @@ in
             http_upload_path = ${toLua cfg.uploadHttp.httpUploadPath}
       ''}
 
+      ${lib.optionalString (cfg.httpFileShare != null) ''
+        Component ${toLua cfg.httpFileShare.domain} "http_file_share"
+        ${settingsToLua "  http_file_share_" (cfg.httpFileShare // { domain = null; })}
+      ''}
+
       ${ lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: ''
         VirtualHost "${v.domain}"
           enabled = ${boolToString v.enabled};
diff --git a/nixos/modules/services/networking/rathole.nix b/nixos/modules/services/networking/rathole.nix
new file mode 100644
index 0000000000000..b6cd3ff89d9cf
--- /dev/null
+++ b/nixos/modules/services/networking/rathole.nix
@@ -0,0 +1,165 @@
+{
+  pkgs,
+  lib,
+  config,
+  ...
+}:
+
+let
+  cfg = config.services.rathole;
+  settingsFormat = pkgs.formats.toml { };
+  py-toml-merge =
+    pkgs.writers.writePython3Bin "py-toml-merge"
+      {
+        libraries = with pkgs.python3Packages; [
+          tomli-w
+          mergedeep
+        ];
+      }
+      ''
+        import argparse
+        from pathlib import Path
+        from typing import Any
+
+        import tomli_w
+        import tomllib
+        from mergedeep import merge
+
+        parser = argparse.ArgumentParser(description="Merge multiple TOML files")
+        parser.add_argument(
+            "files",
+            type=Path,
+            nargs="+",
+            help="List of TOML files to merge",
+        )
+
+        args = parser.parse_args()
+        merged: dict[str, Any] = {}
+
+        for file in args.files:
+            with open(file, "rb") as fh:
+                loaded_toml = tomllib.load(fh)
+                merged = merge(merged, loaded_toml)
+
+        print(tomli_w.dumps(merged))
+      '';
+in
+
+{
+  options = {
+    services.rathole = {
+      enable = lib.mkEnableOption "Rathole";
+
+      package = lib.mkPackageOption pkgs "rathole" { };
+
+      role = lib.mkOption {
+        type = lib.types.enum [
+          "server"
+          "client"
+        ];
+        description = ''
+          Select whether rathole needs to be run as a `client` or a `server`.
+          Server is a machine with a public IP and client is a device behind NAT,
+          but running some services that need to be exposed to the Internet.
+        '';
+      };
+
+      credentialsFile = lib.mkOption {
+        type = lib.types.path;
+        default = "/dev/null";
+        description = ''
+          Path to a TOML file to be merged with the settings.
+          Useful to set secret config parameters like tokens, which
+          should not appear in the Nix Store.
+        '';
+        example = "/var/lib/secrets/rathole/config.toml";
+      };
+
+      settings = lib.mkOption {
+        type = settingsFormat.type;
+        default = { };
+        description = ''
+          Rathole configuration, for options reference
+          see the [example](https://github.com/rapiz1/rathole?tab=readme-ov-file#configuration) on GitHub.
+          Both server and client configurations can be specified at the same time, regardless of the selected role.
+        '';
+        example = {
+          server = {
+            bind_addr = "0.0.0.0:2333";
+            services.my_nas_ssh = {
+              token = "use_a_secret_that_only_you_know";
+              bind_addr = "0.0.0.0:5202";
+            };
+          };
+        };
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.rathole = {
+      requires = [ "network.target" ];
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      description = "Rathole ${cfg.role} Service";
+
+      serviceConfig =
+        let
+          name = "rathole";
+          configFile = settingsFormat.generate "${name}.toml" cfg.settings;
+          runtimeDir = "/run/${name}";
+          ratholePrestart =
+            "+"
+            + (pkgs.writeShellScript "rathole-prestart" ''
+              DYNUSER_UID=$(stat -c %u ${runtimeDir})
+              DYNUSER_GID=$(stat -c %g ${runtimeDir})
+              ${lib.getExe py-toml-merge} ${configFile} '${cfg.credentialsFile}' |
+                install -m 600 -o $DYNUSER_UID -g $DYNUSER_GID /dev/stdin ${runtimeDir}/${mergedConfigName}
+            '');
+          mergedConfigName = "merged.toml";
+        in
+        {
+          Type = "simple";
+          Restart = "on-failure";
+          RestartSec = 5;
+          ExecStartPre = ratholePrestart;
+          ExecStart = "${lib.getExe cfg.package} --${cfg.role} ${runtimeDir}/${mergedConfigName}";
+          DynamicUser = true;
+          LimitNOFILE = "1048576";
+          RuntimeDirectory = name;
+          RuntimeDirectoryMode = "0700";
+          # Hardening
+          AmbientCapabilities = "CAP_NET_BIND_SERVICE";
+          CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
+          LockPersonality = true;
+          MemoryDenyWriteExecute = true;
+          PrivateDevices = true;
+          PrivateMounts = true;
+          PrivateTmp = true;
+          # PrivateUsers=true breaks AmbientCapabilities=CAP_NET_BIND_SERVICE
+          ProcSubset = "pid";
+          ProtectClock = true;
+          ProtectControlGroups = true;
+          ProtectHome = true;
+          ProtectHostname = true;
+          ProtectKernelLogs = true;
+          ProtectKernelModules = true;
+          ProtectKernelTunables = true;
+          ProtectProc = "invisible";
+          ProtectSystem = "strict";
+          RemoveIPC = true;
+          RestrictAddressFamilies = [
+            "AF_INET"
+            "AF_INET6"
+          ];
+          RestrictNamespaces = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+          SystemCallArchitectures = "native";
+          UMask = "0066";
+        };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ xokdvium ];
+}
diff --git a/nixos/modules/services/networking/realm.nix b/nixos/modules/services/networking/realm.nix
new file mode 100644
index 0000000000000..5b0c34ac825ff
--- /dev/null
+++ b/nixos/modules/services/networking/realm.nix
@@ -0,0 +1,50 @@
+{ config
+, lib
+, pkgs
+, ...
+}:
+let
+  cfg = config.services.realm;
+  configFormat = pkgs.formats.json { };
+  configFile = configFormat.generate "config.json" cfg.config;
+  inherit (lib)
+    mkEnableOption mkPackageOption mkOption mkIf types getExe;
+in
+{
+
+  meta.maintainers = with lib.maintainers; [ ocfox ];
+
+  options = {
+    services.realm = {
+      enable = mkEnableOption "A simple, high performance relay server written in rust";
+      package = mkPackageOption pkgs "realm" { };
+      config = mkOption {
+        type = types.submodule {
+          freeformType = configFormat.type;
+        };
+        default = { };
+        description = ''
+          The realm configuration, see <https://github.com/zhboner/realm#overview> for documentation.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.realm = {
+      serviceConfig = {
+        DynamicUser = true;
+        MemoryDenyWriteExecute = true;
+        PrivateDevices = true;
+        ProtectClock = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectProc = "invisible";
+        ProtectKernelTunables = true;
+        ExecStart = "${getExe cfg.package} --config ${configFile}";
+        AmbientCapabilities = [ "CAP_NET_ADMIN" "CAP_NET_BIND_SERVICE" ];
+      };
+      wantedBy = [ "multi-user.target" ];
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/resilio.nix b/nixos/modules/services/networking/resilio.nix
index c788069fbaa55..02773d78b1328 100644
--- a/nixos/modules/services/networking/resilio.nix
+++ b/nixos/modules/services/networking/resilio.nix
@@ -291,5 +291,5 @@ in
     };
   };
 
-  meta.maintainers = with maintainers; [ ];
+  meta.maintainers = [ ];
 }
diff --git a/nixos/modules/services/networking/scion/scion-control.nix b/nixos/modules/services/networking/scion/scion-control.nix
index 95d78a87ac859..8b6733ac0c244 100644
--- a/nixos/modules/services/networking/scion/scion-control.nix
+++ b/nixos/modules/services/networking/scion/scion-control.nix
@@ -3,8 +3,10 @@
 with lib;
 
 let
+  globalCfg = config.services.scion;
   cfg = config.services.scion.scion-control;
   toml = pkgs.formats.toml { };
+  connectionDir = if globalCfg.stateless then "/run" else "/var/lib";
   defaultConfig = {
     general = {
       id = "cs";
@@ -12,13 +14,13 @@ let
       reconnect_to_dispatcher = true;
     };
     beacon_db = {
-      connection = "/run/scion-control/control.beacon.db";
+      connection = "${connectionDir}/scion-control/control.beacon.db";
     };
     path_db = {
-      connection = "/run/scion-control/control.path.db";
+      connection = "${connectionDir}/scion-control/control.path.db";
     };
     trust_db = {
-      connection = "/run/scion-control/control.trust.db";
+      connection = "${connectionDir}/scion-control/control.trust.db";
     };
     log.console = {
       level = "info";
@@ -58,11 +60,11 @@ in
       serviceConfig = {
         Type = "simple";
         Group = if (config.services.scion.scion-dispatcher.enable == true) then "scion" else null;
-        ExecStart = "${pkgs.scion}/bin/scion-control --config ${configFile}";
+        ExecStart = "${globalCfg.package}/bin/scion-control --config ${configFile}";
         DynamicUser = true;
         Restart = "on-failure";
         BindPaths = [ "/dev/shm:/run/shm" ];
-        RuntimeDirectory = "scion-control";
+        ${if globalCfg.stateless then "RuntimeDirectory" else "StateDirectory"} = "scion-control";
       };
     };
   };
diff --git a/nixos/modules/services/networking/scion/scion-daemon.nix b/nixos/modules/services/networking/scion/scion-daemon.nix
index 8528bec1d52eb..09416178d35ab 100644
--- a/nixos/modules/services/networking/scion/scion-daemon.nix
+++ b/nixos/modules/services/networking/scion/scion-daemon.nix
@@ -3,8 +3,10 @@
 with lib;
 
 let
+  globalCfg = config.services.scion;
   cfg = config.services.scion.scion-daemon;
   toml = pkgs.formats.toml { };
+  connectionDir = if globalCfg.stateless then "/run" else "/var/lib";
   defaultConfig = {
     general = {
       id = "sd";
@@ -12,10 +14,10 @@ let
       reconnect_to_dispatcher = true;
     };
     path_db = {
-      connection = "/run/scion-daemon/sd.path.db";
+      connection = "${connectionDir}/scion-daemon/sd.path.db";
     };
     trust_db = {
-      connection = "/run/scion-daemon/sd.trust.db";
+      connection = "${connectionDir}/scion-daemon/sd.trust.db";
     };
     log.console = {
       level = "info";
@@ -54,10 +56,10 @@ in
       wantedBy = [ "multi-user.target" ];
       serviceConfig = {
         Type = "simple";
-        ExecStart = "${pkgs.scion}/bin/scion-daemon --config ${configFile}";
+        ExecStart = "${globalCfg.package}/bin/scion-daemon --config ${configFile}";
         Restart = "on-failure";
         DynamicUser = true;
-        RuntimeDirectory = "scion-daemon";
+        ${if globalCfg.stateless then "RuntimeDirectory" else "StateDirectory"} = "scion-daemon";
       };
     };
   };
diff --git a/nixos/modules/services/networking/scion/scion-dispatcher.nix b/nixos/modules/services/networking/scion/scion-dispatcher.nix
index 7c9f5e6a385ee..7d2e28be75f72 100644
--- a/nixos/modules/services/networking/scion/scion-dispatcher.nix
+++ b/nixos/modules/services/networking/scion/scion-dispatcher.nix
@@ -3,6 +3,7 @@
 with lib;
 
 let
+  globalCfg = config.services.scion;
   cfg = config.services.scion.scion-dispatcher;
   toml = pkgs.formats.toml { };
   defaultConfig = {
@@ -64,9 +65,9 @@ in
         DynamicUser = true;
         BindPaths = [ "/dev/shm:/run/shm" ];
         ExecStartPre = "${pkgs.coreutils}/bin/rm -rf /run/shm/dispatcher";
-        ExecStart = "${pkgs.scion}/bin/scion-dispatcher --config ${configFile}";
+        ExecStart = "${globalCfg.package}/bin/scion-dispatcher --config ${configFile}";
         Restart = "on-failure";
-        RuntimeDirectory = "scion-dispatcher";
+        ${if globalCfg.stateless then "RuntimeDirectory" else "StateDirectory"} = "scion-dispatcher";
       };
     };
   };
diff --git a/nixos/modules/services/networking/scion/scion-router.nix b/nixos/modules/services/networking/scion/scion-router.nix
index 2cac44ab767ef..7587a06ed9dbe 100644
--- a/nixos/modules/services/networking/scion/scion-router.nix
+++ b/nixos/modules/services/networking/scion/scion-router.nix
@@ -3,6 +3,7 @@
 with lib;
 
 let
+  globalCfg = config.services.scion;
   cfg = config.services.scion.scion-router;
   toml = pkgs.formats.toml { };
   defaultConfig = {
@@ -39,10 +40,10 @@ in
       wantedBy = [ "multi-user.target" ];
       serviceConfig = {
         Type = "simple";
-        ExecStart = "${pkgs.scion}/bin/scion-router --config ${configFile}";
+        ExecStart = "${globalCfg.package}/bin/scion-router --config ${configFile}";
         Restart = "on-failure";
         DynamicUser = true;
-        RuntimeDirectory = "scion-router";
+        ${if globalCfg.stateless then "RuntimeDirectory" else "StateDirectory"} = "scion-router";
       };
     };
   };
diff --git a/nixos/modules/services/networking/scion/scion.nix b/nixos/modules/services/networking/scion/scion.nix
index b8bfef8b93b58..efab97f7cd8da 100644
--- a/nixos/modules/services/networking/scion/scion.nix
+++ b/nixos/modules/services/networking/scion/scion.nix
@@ -8,6 +8,23 @@ in
 {
   options.services.scion = {
     enable = mkEnableOption "all of the scion components and services";
+    package = mkPackageOption pkgs "scion" { };
+    stateless = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Setting this value to false (stateful) can lead to improved caching and
+        performance.
+
+        This option decides whether to persist the SCION path sqlite databases
+        on disk or not. Persisting this data can lead to database corruption in
+        extreme cases such as power outage, meaning SCION fails to work on the
+        next boot. This is being investigated.
+
+        If true, /run/scion-* is used for data
+        If false, use /var/lib/scion-* is used for data
+      '';
+    };
     bypassBootstrapWarning = mkOption {
       type = types.bool;
       default = false;
@@ -18,7 +35,7 @@ in
   };
   config = mkIf cfg.enable {
     environment.systemPackages = [
-      pkgs.scion
+      cfg.package
     ];
     services.scion = {
       scion-dispatcher.enable = true;
diff --git a/nixos/modules/services/networking/smokeping.nix b/nixos/modules/services/networking/smokeping.nix
index a07cde847cf69..2e572a3d071af 100644
--- a/nixos/modules/services/networking/smokeping.nix
+++ b/nixos/modules/services/networking/smokeping.nix
@@ -337,7 +337,7 @@ in
     };
 
     # use nginx to serve the smokeping web service
-    services.fcgiwrap.smokeping = mkIf cfg.webService {
+    services.fcgiwrap.instances.smokeping = mkIf cfg.webService {
       process.user = cfg.user;
       process.group = cfg.user;
       socket = { inherit (config.services.nginx) user group; };
@@ -353,7 +353,7 @@ in
         locations."/smokeping.fcgi" = {
           extraConfig = ''
             include ${config.services.nginx.package}/conf/fastcgi_params;
-            fastcgi_pass unix:${config.services.fcgiwrap.smokeping.socket.address};
+            fastcgi_pass unix:${config.services.fcgiwrap.instances.smokeping.socket.address};
             fastcgi_param SCRIPT_FILENAME ${smokepingHome}/smokeping.fcgi;
             fastcgi_param DOCUMENT_ROOT ${smokepingHome};
           '';
diff --git a/nixos/modules/services/networking/spiped.nix b/nixos/modules/services/networking/spiped.nix
index ada36ee9be0bc..a5188d933995e 100644
--- a/nixos/modules/services/networking/spiped.nix
+++ b/nixos/modules/services/networking/spiped.nix
@@ -62,11 +62,11 @@ in
               keyfile = mkOption {
                 type    = types.path;
                 description = ''
-                  Name of a file containing the spiped key. As the
-                  daemon runs as the `spiped` user, the
-                  key file must be somewhere owned by that user. By
-                  default, we recommend putting the keys for any spipe
-                  services in `/var/lib/spiped`.
+                  Name of a file containing the spiped key.
+                  As the daemon runs as the `spiped` user,
+                  the key file must be readable by that user.
+                  To securely manage the file within your configuration
+                  consider a tool such as agenix or sops-nix.
                 '';
               };
 
@@ -185,22 +185,12 @@ in
       serviceConfig = {
         Restart   = "always";
         User      = "spiped";
-        PermissionsStartOnly = true;
       };
 
-      preStart  = ''
-        cd /var/lib/spiped
-        chmod -R 0660 *
-        chown -R spiped:spiped *
-      '';
       scriptArgs = "%i";
       script = "exec ${pkgs.spiped}/bin/spiped -F `cat /etc/spiped/$1.spec`";
     };
 
-    systemd.tmpfiles.rules = lib.mkIf (cfg.config != { }) [
-      "d /var/lib/spiped -"
-    ];
-
     # Setup spiped config files
     environment.etc = mapAttrs' (name: cfg: nameValuePair "spiped/${name}.spec"
       { text = concatStringsSep " "
diff --git a/nixos/modules/services/networking/wg-quick.nix b/nixos/modules/services/networking/wg-quick.nix
index 2062f2806d79f..369c5a9397659 100644
--- a/nixos/modules/services/networking/wg-quick.nix
+++ b/nixos/modules/services/networking/wg-quick.nix
@@ -57,6 +57,15 @@ let
         '';
       };
 
+      generatePrivateKeyFile = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Automatically generate a private key with
+          {command}`wg genkey`, at the privateKeyFile location.
+        '';
+      };
+
       privateKeyFile = mkOption {
         example = "/private/wireguard_key";
         type = with types; nullOr str;
@@ -218,9 +227,24 @@ let
 
   writeScriptFile = name: text: ((pkgs.writeShellScriptBin name text) + "/bin/${name}");
 
+  generatePrivateKeyScript = privateKeyFile: ''
+    set -e
+
+    # If the parent dir does not already exist, create it.
+    # Otherwise, does nothing, keeping existing permissions intact.
+    mkdir -p --mode 0755 "${dirOf privateKeyFile}"
+
+    if [ ! -f "${privateKeyFile}" ]; then
+      # Write private key file with atomically-correct permissions.
+      (set -e; umask 077; wg genkey > "${privateKeyFile}")
+    fi
+  '';
+
   generateUnit = name: values:
     assert assertMsg (values.configFile != null || ((values.privateKey != null) != (values.privateKeyFile != null))) "Only one of privateKey, configFile or privateKeyFile may be set";
+    assert assertMsg (values.generatePrivateKeyFile == false || values.privateKeyFile != null) "generatePrivateKeyFile requires privateKeyFile to be set";
     let
+      generateKeyScriptFile = if values.generatePrivateKeyFile then writeScriptFile "generatePrivateKey.sh" (generatePrivateKeyScript values.privateKeyFile) else null;
       preUpFile = if values.preUp != "" then writeScriptFile "preUp.sh" values.preUp else null;
       postUp =
             optional (values.privateKeyFile != null) "wg set ${name} private-key <(cat ${values.privateKeyFile})" ++
@@ -247,6 +271,7 @@ let
         optionalString (values.mtu != null) "MTU = ${toString values.mtu}\n" +
         optionalString (values.privateKey != null) "PrivateKey = ${values.privateKey}\n" +
         optionalString (values.listenPort != null) "ListenPort = ${toString values.listenPort}\n" +
+        optionalString (generateKeyScriptFile != null) "PreUp = ${generateKeyScriptFile}\n" +
         optionalString (preUpFile != null) "PreUp = ${preUpFile}\n" +
         optionalString (postUpFile != null) "PostUp = ${postUpFile}\n" +
         optionalString (preDownFile != null) "PreDown = ${preDownFile}\n" +
diff --git a/nixos/modules/services/networking/wireguard.nix b/nixos/modules/services/networking/wireguard.nix
index 81abae2c9303d..08e5494b63df9 100644
--- a/nixos/modules/services/networking/wireguard.nix
+++ b/nixos/modules/services/networking/wireguard.nix
@@ -481,29 +481,27 @@ let
           RemainAfterExit = true;
         };
 
-        script = ''
-          ${optionalString (!config.boot.isContainer) "modprobe wireguard || true"}
-
-          ${values.preSetup}
-
-          ${ipPreMove} link add dev "${name}" type wireguard
-          ${optionalString (values.interfaceNamespace != null && values.interfaceNamespace != values.socketNamespace) ''${ipPreMove} link set "${name}" netns "${ns}"''}
-          ${optionalString (values.mtu != null) ''${ipPostMove} link set "${name}" mtu ${toString values.mtu}''}
-
-          ${concatMapStringsSep "\n" (ip:
+        script = concatStringsSep "\n" (
+          optional (!config.boot.isContainer) "modprobe wireguard || true"
+          ++ [
+            values.preSetup
+            ''${ipPreMove} link add dev "${name}" type wireguard''
+          ]
+          ++ optional (values.interfaceNamespace != null && values.interfaceNamespace != values.socketNamespace) ''${ipPreMove} link set "${name}" netns "${ns}"''
+          ++ optional (values.mtu != null) ''${ipPostMove} link set "${name}" mtu ${toString values.mtu}''
+          ++ (map (ip:
             ''${ipPostMove} address add "${ip}" dev "${name}"''
-          ) values.ips}
-
-          ${concatStringsSep " " (
+          ) values.ips)
+          ++ [
+            (concatStringsSep " " (
             [ ''${wg} set "${name}" private-key "${privKey}"'' ]
             ++ optional (values.listenPort != null) ''listen-port "${toString values.listenPort}"''
             ++ optional (values.fwMark != null) ''fwmark "${values.fwMark}"''
-          )}
-
-          ${ipPostMove} link set up dev "${name}"
-
-          ${values.postSetup}
-        '';
+            ))
+            ''${ipPostMove} link set up dev "${name}"''
+            values.postSetup
+          ]
+          );
 
         postStop = ''
           ${values.preShutdown}
diff --git a/nixos/modules/services/networking/wpa_supplicant.nix b/nixos/modules/services/networking/wpa_supplicant.nix
index 435cd530c18d4..b068f22273102 100644
--- a/nixos/modules/services/networking/wpa_supplicant.nix
+++ b/nixos/modules/services/networking/wpa_supplicant.nix
@@ -3,10 +3,6 @@
 with lib;
 
 let
-  package = if cfg.allowAuxiliaryImperativeNetworks
-    then pkgs.wpa_supplicant_ro_ssids
-    else pkgs.wpa_supplicant;
-
   cfg = config.networking.wireless;
   opt = options.networking.wireless;
 
@@ -106,7 +102,7 @@ let
       wantedBy = [ "multi-user.target" ];
       stopIfChanged = false;
 
-      path = [ package ];
+      path = [ pkgs.wpa_supplicant ];
       # if `userControl.enable`, the supplicant automatically changes the permissions
       #  and owning group of the runtime dir; setting `umask` ensures the generated
       #  config file isn't readable (except to root);  see nixpkgs#267693
@@ -139,9 +135,9 @@ let
                 $0 = substr($0, 1, i-1) repl substr($0, i+length(find))
             }
             print
-          }' "${configFile}" > "${finalConfig}"
+          }' "${configFile}" > ${finalConfig}
         else
-          touch "${finalConfig}"
+          touch ${finalConfig}
         fi
 
         iface_args="-s ${optionalString cfg.dbusControlled "-u"} -D${cfg.driver} ${configStr}"
@@ -521,8 +517,8 @@ in {
 
     hardware.wirelessRegulatoryDatabase = true;
 
-    environment.systemPackages = [ package ];
-    services.dbus.packages = optional cfg.dbusControlled package;
+    environment.systemPackages = [ pkgs.wpa_supplicant ];
+    services.dbus.packages = optional cfg.dbusControlled pkgs.wpa_supplicant;
 
     systemd.services =
       if cfg.interfaces == []
diff --git a/nixos/modules/services/networking/wstunnel.nix b/nixos/modules/services/networking/wstunnel.nix
index bd7536351955a..439e1f8ea9b5d 100644
--- a/nixos/modules/services/networking/wstunnel.nix
+++ b/nixos/modules/services/networking/wstunnel.nix
@@ -23,14 +23,14 @@ let
   };
 
   commonOptions = {
-    enable = lib.mkEnableOption "this `wstunnel` instance." // {
+    enable = lib.mkEnableOption "this `wstunnel` instance" // {
       default = true;
     };
 
     package = lib.mkPackageOption pkgs "wstunnel" { };
 
     autoStart =
-      lib.mkEnableOption "starting this wstunnel instance automatically." // {
+      lib.mkEnableOption "starting this wstunnel instance automatically" // {
         default = true;
       };
 
diff --git a/nixos/modules/services/networking/wvdial.nix b/nixos/modules/services/networking/wvdial.nix
new file mode 100644
index 0000000000000..8e06d64940d01
--- /dev/null
+++ b/nixos/modules/services/networking/wvdial.nix
@@ -0,0 +1,47 @@
+# Global configuration for wvdial.
+
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  cfg = config.environment.wvdial;
+in
+{
+  options = {
+    environment.wvdial = {
+      dialerDefaults = lib.mkOption {
+        default = "";
+        type = lib.types.str;
+        example = ''Init1 = AT+CGDCONT=1,"IP","internet.t-mobile"'';
+        description = ''
+          Contents of the "Dialer Defaults" section of
+          <filename>/etc/wvdial.conf</filename>.
+        '';
+      };
+      pppDefaults = lib.mkOption {
+        default = ''
+          noipdefault
+          usepeerdns
+          defaultroute
+          persist
+          noauth
+        '';
+        type = lib.types.str;
+        description = "Default ppp settings for wvdial.";
+      };
+    };
+  };
+
+  config = lib.mkIf (cfg.dialerDefaults != "") {
+    environment.etc."wvdial.conf".source = pkgs.writeText "wvdial.conf" ''
+      [Dialer Defaults]
+      PPPD PATH = ${pkgs.ppp}/sbin/pppd
+      ${config.environment.wvdial.dialerDefaults}
+    '';
+    environment.etc."ppp/peers/wvdial".source = pkgs.writeText "wvdial" cfg.pppDefaults;
+  };
+}
diff --git a/nixos/modules/services/networking/zeronsd.nix b/nixos/modules/services/networking/zeronsd.nix
new file mode 100644
index 0000000000000..23f1a5fa7e4f8
--- /dev/null
+++ b/nixos/modules/services/networking/zeronsd.nix
@@ -0,0 +1,117 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  cfg = config.services.zeronsd;
+  settingsFormat = pkgs.formats.json { };
+in
+{
+  options.services.zeronsd.servedNetworks = lib.mkOption {
+    default = { };
+    example = {
+      "a8a2c3c10c1a68de".settings.token = "/var/lib/zeronsd/apitoken";
+    };
+    description = "ZeroTier Networks to start zeronsd instances for.";
+    type = lib.types.attrsOf (
+      lib.types.submodule {
+        options = {
+          package = lib.mkPackageOption pkgs "zeronsd" { };
+
+          settings = lib.mkOption {
+            description = "Settings for zeronsd";
+            default = { };
+            type = lib.types.submodule {
+              freeformType = settingsFormat.type;
+
+              options = {
+                domain = lib.mkOption {
+                  default = "home.arpa";
+                  type = lib.types.singleLineStr;
+                  description = "Domain under which ZeroTier records will be available.";
+                };
+
+                token = lib.mkOption {
+                  type = lib.types.path;
+                  description = "Path to a file containing the API Token for ZeroTier Central.";
+                };
+
+                log_level = lib.mkOption {
+                  default = "warn";
+                  type = lib.types.enum [
+                    "off"
+                    "error"
+                    "warn"
+                    "info"
+                    "debug"
+                    "trace"
+                  ];
+                  description = "Log Level.";
+                };
+
+                wildcard = lib.mkOption {
+                  default = false;
+                  type = lib.types.bool;
+                  description = "Whether to serve a wildcard record for ZeroTier Nodes.";
+                };
+              };
+            };
+          };
+        };
+      }
+    );
+  };
+
+  config = lib.mkIf (cfg.servedNetworks != { }) {
+    assertions = [
+      {
+        assertion = config.services.zerotierone.enable;
+        message = "zeronsd needs a configured zerotier-one";
+      }
+    ];
+
+    systemd.services = lib.mapAttrs' (netname: netcfg: {
+      name = "zeronsd-${netname}";
+      value = {
+        description = "ZeroTier DNS server for Network ${netname}";
+
+        wantedBy = [ "multi-user.target" ];
+        after = [
+          "network.target"
+          "zerotierone.service"
+        ];
+        wants = [ "network-online.target" ];
+
+        serviceConfig =
+          let
+            configFile = pkgs.writeText "zeronsd.json" (builtins.toJSON netcfg.settings);
+          in
+          {
+            ExecStart = "${netcfg.package}/bin/zeronsd start --config ${configFile} --config-type json ${netname}";
+            Restart = "on-failure";
+            RestartSec = 2;
+            TimeoutStopSec = 5;
+            User = "zeronsd";
+            Group = "zeronsd";
+            AmbientCapabilities = "CAP_NET_BIND_SERVICE";
+          };
+      };
+    }) cfg.servedNetworks;
+
+    systemd.tmpfiles.rules = [
+      "a+ /var/lib/zerotier-one - - - - u:zeronsd:x"
+      "a+ /var/lib/zerotier-one/authtoken.secret - - - - mask::r,u:zeronsd:r"
+    ];
+
+    users.users.zeronsd = {
+      group = "zeronsd";
+      description = "Service user for running zeronsd";
+      isSystemUser = true;
+    };
+
+    users.groups.zeronsd = { };
+  };
+}
diff --git a/nixos/modules/services/search/tika.nix b/nixos/modules/services/search/tika.nix
new file mode 100644
index 0000000000000..94096b6db29fe
--- /dev/null
+++ b/nixos/modules/services/search/tika.nix
@@ -0,0 +1,98 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  cfg = config.services.tika;
+  inherit (lib)
+    literalExpression
+    mkIf
+    mkEnableOption
+    mkOption
+    mkPackageOption
+    getExe
+    types
+    ;
+in
+{
+  meta.maintainers = [ lib.maintainers.drupol ];
+
+  options = {
+    services.tika = {
+      enable = mkEnableOption "Apache Tika server";
+      package = mkPackageOption pkgs "tika" { };
+
+      listenAddress = mkOption {
+        type = types.str;
+        default = "127.0.0.1";
+        example = "0.0.0.0";
+        description = ''
+          The Apache Tika bind address.
+        '';
+      };
+
+      port = mkOption {
+        type = types.port;
+        default = 9998;
+        description = ''
+          The Apache Tike port to listen on
+        '';
+      };
+
+      configFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          The Apache Tika configuration (XML) file to use.
+        '';
+        example = literalExpression "./tika/tika-config.xml";
+      };
+
+      enableOcr = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable OCR support by adding the `tesseract` package as a dependency.
+        '';
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to open the firewall for Apache Tika.
+          This adds `services.tika.port` to `networking.firewall.allowedTCPPorts`.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.tika = {
+      description = "Apache Tika Server";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig =
+        let
+          package = cfg.package.override { inherit (cfg) enableOcr; };
+        in
+        {
+          Type = "simple";
+
+          ExecStart = "${getExe package} --host ${cfg.listenAddress} --port ${toString cfg.port} ${
+            lib.optionalString (cfg.configFile != null) "--config ${cfg.configFile}"
+          }";
+          DynamicUser = true;
+          StateDirectory = "tika";
+          CacheDirectory = "tika";
+        };
+    };
+
+    networking.firewall = mkIf cfg.openFirewall { allowedTCPPorts = [ cfg.port ]; };
+  };
+}
diff --git a/nixos/modules/services/security/authelia.nix b/nixos/modules/services/security/authelia.nix
index cf1c57e34c4ef..f73c1ce41e5b9 100644
--- a/nixos/modules/services/security/authelia.nix
+++ b/nixos/modules/services/security/authelia.nix
@@ -8,7 +8,6 @@ let
   cfg = config.services.authelia;
 
   format = pkgs.formats.yaml { };
-  configFile = format.generate "config.yml" cfg.settings;
 
   autheliaOpts = with lib; { name, ... }: {
     options = {
@@ -156,18 +155,12 @@ let
             };
 
             server = {
-              host = mkOption {
+              address = mkOption {
                 type = types.str;
-                default = "localhost";
-                example = "0.0.0.0";
+                default = "tcp://:9091/";
+                example = "unix:///var/run/authelia.sock?path=authelia&umask=0117";
                 description = "The address to listen on.";
               };
-
-              port = mkOption {
-                type = types.port;
-                default = 9091;
-                description = "The port to listen on.";
-              };
             };
 
             log = {
@@ -233,6 +226,23 @@ let
       };
     };
   };
+
+  writeOidcJwksConfigFile = oidcIssuerPrivateKeyFile: pkgs.writeText "oidc-jwks.yaml" ''
+    identity_providers:
+      oidc:
+        jwks:
+          - key: {{ secret "${oidcIssuerPrivateKeyFile}" | mindent 10 "|" | msquote }}
+  '';
+
+  # Remove an attribute in a nested set
+  # https://discourse.nixos.org/t/modify-an-attrset-in-nix/29919/5
+  removeAttrByPath = set: pathList:
+    lib.updateManyAttrsByPath [{
+      path = lib.init pathList;
+      update = old:
+        lib.filterAttrs (n: v: n != (lib.last pathList)) old;
+    }]
+      set;
 in
 {
   options.services.authelia.instances = with lib; mkOption {
@@ -281,9 +291,19 @@ in
     let
       mkInstanceServiceConfig = instance:
         let
+          cleanedSettings =
+            if (instance.settings.server?host || instance.settings.server?port || instance.settings.server?path) then
+            # Old settings are used: display a warning and remove the default value of server.address
+            # as authelia does not allow both old and new settings to be set
+              lib.warn "Please replace services.authelia.instances.${instance.name}.settings.{host,port,path} with services.authelia.instances.${instance.name}.settings.address, before release 5.0.0"
+                (removeAttrByPath instance.settings [ "server" "address" ])
+            else
+              instance.settings;
+
           execCommand = "${instance.package}/bin/authelia";
-          configFile = format.generate "config.yml" instance.settings;
-          configArg = "--config ${builtins.concatStringsSep "," (lib.concatLists [[configFile] instance.settingsFiles])}";
+          configFile = format.generate "config.yml" cleanedSettings;
+          oidcJwksConfigFile = lib.optional (instance.secrets.oidcIssuerPrivateKeyFile != null) (writeOidcJwksConfigFile instance.secrets.oidcIssuerPrivateKeyFile);
+          configArg = "--config ${builtins.concatStringsSep "," (lib.concatLists [[configFile] instance.settingsFiles oidcJwksConfigFile])}";
         in
         {
           description = "Authelia authentication and authorization server";
@@ -291,10 +311,10 @@ in
           after = [ "network.target" ];
           environment =
             (lib.filterAttrs (_: v: v != null) {
-              AUTHELIA_JWT_SECRET_FILE = instance.secrets.jwtSecretFile;
+              X_AUTHELIA_CONFIG_FILTERS = lib.mkIf (oidcJwksConfigFile != [ ]) "template";
+              AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET_FILE = instance.secrets.jwtSecretFile;
               AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE = instance.secrets.storageEncryptionKeyFile;
               AUTHELIA_SESSION_SECRET_FILE = instance.secrets.sessionSecretFile;
-              AUTHELIA_IDENTITY_PROVIDERS_OIDC_ISSUER_PRIVATE_KEY_FILE = instance.secrets.oidcIssuerPrivateKeyFile;
               AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE = instance.secrets.oidcHmacSecretFile;
             })
             // instance.environmentVariables;
diff --git a/nixos/modules/services/security/clamav.nix b/nixos/modules/services/security/clamav.nix
index b3598606d8be7..a23c19f685d2d 100644
--- a/nixos/modules/services/security/clamav.nix
+++ b/nixos/modules/services/security/clamav.nix
@@ -5,7 +5,6 @@ let
   stateDir = "/var/lib/clamav";
   clamavGroup = clamavUser;
   cfg = config.services.clamav;
-  pkg = pkgs.clamav;
 
   toKeyValue = generators.toKeyValue {
     mkKeyValue = generators.mkKeyValueDefault { } " ";
@@ -27,6 +26,7 @@ in
 
   options = {
     services.clamav = {
+      package = mkPackageOption pkgs "clamav" { };
       daemon = {
         enable = mkEnableOption "ClamAV clamd daemon";
 
@@ -125,7 +125,7 @@ in
   };
 
   config = mkIf (cfg.updater.enable || cfg.daemon.enable) {
-    environment.systemPackages = [ pkg ];
+    environment.systemPackages = [ cfg.package ];
 
     users.users.${clamavUser} = {
       uid = config.ids.uids.clamav;
@@ -172,7 +172,7 @@ in
       restartTriggers = [ clamdConfigFile ];
 
       serviceConfig = {
-        ExecStart = "${pkg}/bin/clamd";
+        ExecStart = "${cfg.package}/bin/clamd";
         ExecReload = "${pkgs.coreutils}/bin/kill -USR2 $MAINPID";
         User = clamavUser;
         Group = clamavGroup;
@@ -201,7 +201,7 @@ in
 
       serviceConfig = {
         Type = "oneshot";
-        ExecStart = "${pkg}/bin/freshclam";
+        ExecStart = "${cfg.package}/bin/freshclam";
         SuccessExitStatus = "1"; # if databases are up to date
         StateDirectory = "clamav";
         User = clamavUser;
@@ -274,7 +274,7 @@ in
 
       serviceConfig = {
         Type = "oneshot";
-        ExecStart = "${pkg}/bin/clamdscan --multiscan --fdpass --infected --allmatch ${lib.concatStringsSep " " cfg.scanner.scanDirectories}";
+        ExecStart = "${cfg.package}/bin/clamdscan --multiscan --fdpass --infected --allmatch ${lib.concatStringsSep " " cfg.scanner.scanDirectories}";
       };
     };
   };
diff --git a/nixos/modules/services/security/hologram-agent.nix b/nixos/modules/services/security/hologram-agent.nix
index e29267e50003b..a2cf9611d677b 100644
--- a/nixos/modules/services/security/hologram-agent.nix
+++ b/nixos/modules/services/security/hologram-agent.nix
@@ -54,5 +54,5 @@ in {
 
   };
 
-  meta.maintainers = with lib.maintainers; [ ];
+  meta.maintainers = [ ];
 }
diff --git a/nixos/modules/services/security/shibboleth-sp.nix b/nixos/modules/services/security/shibboleth-sp.nix
index c6d260b902670..0a85173501d5f 100644
--- a/nixos/modules/services/security/shibboleth-sp.nix
+++ b/nixos/modules/services/security/shibboleth-sp.nix
@@ -70,5 +70,5 @@ in {
     };
   };
 
-  meta.maintainers = with lib.maintainers; [ ];
+  meta.maintainers = [ ];
 }
diff --git a/nixos/modules/services/security/sks.nix b/nixos/modules/services/security/sks.nix
index 520da45c94e2f..20d2dadbb7e22 100644
--- a/nixos/modules/services/security/sks.nix
+++ b/nixos/modules/services/security/sks.nix
@@ -10,7 +10,7 @@ let
   '';
 
 in {
-  meta.maintainers = with maintainers; [ primeos calbrecht jcumming ];
+  meta.maintainers = with maintainers; [ calbrecht jcumming ];
 
   options = {
 
diff --git a/nixos/modules/services/security/step-ca.nix b/nixos/modules/services/security/step-ca.nix
index 43bc402e7818b..70651e995dda1 100644
--- a/nixos/modules/services/security/step-ca.nix
+++ b/nixos/modules/services/security/step-ca.nix
@@ -4,7 +4,7 @@ let
   settingsFormat = (pkgs.formats.json { });
 in
 {
-  meta.maintainers = with lib.maintainers; [ ];
+  meta.maintainers = [ ];
 
   options = {
     services.step-ca = {
diff --git a/nixos/modules/services/security/vaultwarden/default.nix b/nixos/modules/services/security/vaultwarden/default.nix
index 41f7de5d80fab..11d49b580b310 100644
--- a/nixos/modules/services/security/vaultwarden/default.nix
+++ b/nixos/modules/services/security/vaultwarden/default.nix
@@ -7,6 +7,8 @@ let
 
   StateDirectory = if lib.versionOlder config.system.stateVersion "24.11" then "bitwarden_rs" else "vaultwarden";
 
+  dataDir = "/var/lib/${StateDirectory}";
+
   # Convert name from camel case (e.g. disable2FARemember) to upper case snake case (e.g. DISABLE_2FA_REMEMBER).
   nameToEnvVar = name:
     let
@@ -25,7 +27,7 @@ let
       configEnv = lib.concatMapAttrs (name: value: lib.optionalAttrs (value != null) {
         ${nameToEnvVar name} = if lib.isBool value then lib.boolToString value else toString value;
       }) cfg.config;
-    in { DATA_FOLDER = "/var/lib/${StateDirectory}"; } // lib.optionalAttrs (!(configEnv ? WEB_VAULT_ENABLED) || configEnv.WEB_VAULT_ENABLED == "true") {
+    in { DATA_FOLDER = dataDir; } // lib.optionalAttrs (!(configEnv ? WEB_VAULT_ENABLED) || configEnv.WEB_VAULT_ENABLED == "true") {
       WEB_VAULT_FOLDER = "${cfg.webVaultPackage}/share/vaultwarden/vault";
     } // configEnv;
 
@@ -160,10 +162,16 @@ in {
   };
 
   config = lib.mkIf cfg.enable {
-    assertions = [ {
-      assertion = cfg.backupDir != null -> cfg.dbBackend == "sqlite";
-      message = "Backups for database backends other than sqlite will need customization";
-    } ];
+    assertions = [
+      {
+        assertion = cfg.backupDir != null -> cfg.dbBackend == "sqlite";
+        message = "Backups for database backends other than sqlite will need customization";
+      }
+      {
+        assertion = cfg.backupDir != null -> !(lib.hasPrefix dataDir cfg.backupDir);
+        message = "Backup directory can not be in ${dataDir}";
+      }
+    ];
 
     users.users.vaultwarden = {
       inherit group;
@@ -224,7 +232,7 @@ in {
     systemd.services.backup-vaultwarden = lib.mkIf (cfg.backupDir != null) {
       description = "Backup vaultwarden";
       environment = {
-        DATA_FOLDER = "/var/lib/${StateDirectory}";
+        DATA_FOLDER = dataDir;
         BACKUP_FOLDER = cfg.backupDir;
       };
       path = with pkgs; [ sqlite ];
diff --git a/nixos/modules/services/ttys/kmscon.nix b/nixos/modules/services/ttys/kmscon.nix
index 6b05886756fe2..d274b508300f2 100644
--- a/nixos/modules/services/ttys/kmscon.nix
+++ b/nixos/modules/services/ttys/kmscon.nix
@@ -105,7 +105,7 @@ in {
           ));
         render = optionals cfg.hwRender [ "drm" "hwaccel" ];
         fonts = optional (cfg.fonts != null) "font-name=${lib.concatMapStringsSep ", " (f: f.name) cfg.fonts}";
-      in lib.concatStringsSep "\n" (xkb ++ render ++ fonts);
+      in lib.concatLines (xkb ++ render ++ fonts);
 
     hardware.graphics.enable = mkIf cfg.hwRender true;
 
diff --git a/nixos/modules/services/video/frigate.nix b/nixos/modules/services/video/frigate.nix
index c3ec4a3c76c34..5fa67b5ca2649 100644
--- a/nixos/modules/services/video/frigate.nix
+++ b/nixos/modules/services/video/frigate.nix
@@ -403,7 +403,7 @@ in
       path = with pkgs; [
         # unfree:
         # config.boot.kernelPackages.nvidiaPackages.latest.bin
-        ffmpeg_5-headless
+        ffmpeg-headless
         libva-utils
         procps
         radeontop
diff --git a/nixos/modules/services/video/replay-sorcery.nix b/nixos/modules/services/video/replay-sorcery.nix
deleted file mode 100644
index abe7202a4a862..0000000000000
--- a/nixos/modules/services/video/replay-sorcery.nix
+++ /dev/null
@@ -1,72 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.replay-sorcery;
-  configFile = generators.toKeyValue {} cfg.settings;
-in
-{
-  options = with types; {
-    services.replay-sorcery = {
-      enable = mkEnableOption "the ReplaySorcery service for instant-replays";
-
-      enableSysAdminCapability = mkEnableOption ''
-        the system admin capability to support hardware accelerated
-        video capture. This is equivalent to running ReplaySorcery as
-        root, so use with caution'';
-
-      autoStart = mkOption {
-        type = bool;
-        default = false;
-        description = "Automatically start ReplaySorcery when graphical-session.target starts.";
-      };
-
-      settings = mkOption {
-        type = attrsOf (oneOf [ str int ]);
-        default = {};
-        description = "System-wide configuration for ReplaySorcery (/etc/replay-sorcery.conf).";
-        example = literalExpression ''
-          {
-            videoInput = "hwaccel"; # requires `services.replay-sorcery.enableSysAdminCapability = true`
-            videoFramerate = 60;
-          }
-        '';
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    environment = {
-      systemPackages = [ pkgs.replay-sorcery ];
-      etc."replay-sorcery.conf".text = configFile;
-    };
-
-    security.wrappers = mkIf cfg.enableSysAdminCapability {
-      replay-sorcery = {
-        owner = "root";
-        group = "root";
-        capabilities = "cap_sys_admin+ep";
-        source = "${pkgs.replay-sorcery}/bin/replay-sorcery";
-      };
-    };
-
-    systemd = {
-      packages = [ pkgs.replay-sorcery ];
-      user.services.replay-sorcery = {
-        wantedBy = mkIf cfg.autoStart [ "graphical-session.target" ];
-        partOf = mkIf cfg.autoStart [ "graphical-session.target" ];
-        serviceConfig = {
-          ExecStart = mkIf cfg.enableSysAdminCapability [
-            "" # Tell systemd to clear the existing ExecStart list, to prevent appending to it.
-            "${config.security.wrapperDir}/replay-sorcery"
-          ];
-        };
-      };
-    };
-  };
-
-  meta = {
-    maintainers = with maintainers; [ kira-bruneau ];
-  };
-}
diff --git a/nixos/modules/services/web-apps/akkoma.nix b/nixos/modules/services/web-apps/akkoma.nix
index 8ba3c7eaa1e6a..17c221778d89a 100644
--- a/nixos/modules/services/web-apps/akkoma.nix
+++ b/nixos/modules/services/web-apps/akkoma.nix
@@ -452,9 +452,9 @@ in {
 
       extraPackages = mkOption {
         type = with types; listOf package;
-        default = with pkgs; [ exiftool ffmpeg_5-headless graphicsmagick-imagemagick-compat ];
-        defaultText = literalExpression "with pkgs; [ exiftool graphicsmagick-imagemagick-compat ffmpeg_5-headless ]";
-        example = literalExpression "with pkgs; [ exiftool imagemagick ffmpeg_5-full ]";
+        default = with pkgs; [ exiftool ffmpeg-headless graphicsmagick-imagemagick-compat ];
+        defaultText = literalExpression "with pkgs; [ exiftool ffmpeg-headless graphicsmagick-imagemagick-compat ]";
+        example = literalExpression "with pkgs; [ exiftool ffmpeg-full imagemagick ]";
         description = ''
           List of extra packages to include in the executable search path of the service unit.
           These are needed by various configurable components such as:
diff --git a/nixos/modules/services/web-apps/cryptpad.nix b/nixos/modules/services/web-apps/cryptpad.nix
new file mode 100644
index 0000000000000..770eefc007395
--- /dev/null
+++ b/nixos/modules/services/web-apps/cryptpad.nix
@@ -0,0 +1,293 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  cfg = config.services.cryptpad;
+
+  inherit (lib)
+    mkIf
+    mkMerge
+    mkOption
+    strings
+    types
+    ;
+
+  # The Cryptpad configuration file isn't JSON, but a JavaScript source file that assigns a JSON value
+  # to a variable.
+  cryptpadConfigFile = builtins.toFile "cryptpad_config.js" ''
+    module.exports = ${builtins.toJSON cfg.settings}
+  '';
+
+  # Derive domain names for Nginx configuration from Cryptpad configuration
+  mainDomain = strings.removePrefix "https://" cfg.settings.httpUnsafeOrigin;
+  sandboxDomain =
+    if cfg.settings.httpSafeOrigin == null then
+      mainDomain
+    else
+      strings.removePrefix "https://" cfg.settings.httpSafeOrigin;
+
+in
+{
+  options.services.cryptpad = {
+    enable = lib.mkEnableOption "cryptpad";
+
+    package = lib.mkPackageOption pkgs "cryptpad" { };
+
+    configureNginx = mkOption {
+      description = ''
+        Configure Nginx as a reverse proxy for Cryptpad.
+        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.
+      '';
+      type = types.bool;
+      default = false;
+    };
+
+    settings = mkOption {
+      description = ''
+        Cryptpad configuration settings.
+        See https://github.com/cryptpad/cryptpad/blob/main/config/config.example.js for a more extensive
+        reference documentation.
+        Test your deployed instance through `https://<domain>/checkup/`.
+      '';
+      type = types.submodule {
+        freeformType = (pkgs.formats.json { }).type;
+        options = {
+          httpUnsafeOrigin = mkOption {
+            type = types.str;
+            example = "https://cryptpad.example.com";
+            default = "";
+            description = "This is the URL that users will enter to load your instance";
+          };
+          httpSafeOrigin = mkOption {
+            type = types.nullOr types.str;
+            example = "https://cryptpad-ui.example.com. Apparently optional but recommended.";
+            description = "Cryptpad sandbox URL";
+          };
+          httpAddress = mkOption {
+            type = types.str;
+            default = "127.0.0.1";
+            description = "Address on which the Node.js server should listen";
+          };
+          httpPort = mkOption {
+            type = types.int;
+            default = 3000;
+            description = "Port on which the Node.js server should listen";
+          };
+          websocketPort = mkOption {
+            type = types.int;
+            default = 3003;
+            description = "Port for the websocket that needs to be separate";
+          };
+          maxWorkers = mkOption {
+            type = types.nullOr types.int;
+            default = null;
+            description = "Number of child processes, defaults to number of cores available";
+          };
+          adminKeys = mkOption {
+            type = types.listOf types.str;
+            default = [ ];
+            description = "List of public signing keys of users that can access the admin panel";
+            example = [ "[cryptpad-user1@my.awesome.website/YZgXQxKR0Rcb6r6CmxHPdAGLVludrAF2lEnkbx1vVOo=]" ];
+          };
+          logToStdout = mkOption {
+            type = types.bool;
+            default = true;
+            description = "Controls whether log output should go to stdout of the systemd service";
+          };
+          logLevel = mkOption {
+            type = types.str;
+            default = "info";
+            description = "Controls log level";
+          };
+          blockDailyCheck = mkOption {
+            type = types.bool;
+            default = true;
+            description = ''
+              Disable telemetry. This setting is only effective if the 'Disable server telemetry'
+              setting in the admin menu has been untouched, and will be ignored by cryptpad once
+              that option is set either way.
+              Note that due to the service confinement, just enabling the option in the admin
+              menu will not be able to resolve DNS and fail; this setting must be set as well.
+            '';
+          };
+          installMethod = mkOption {
+            type = types.str;
+            default = "nixos";
+            description = ''
+              Install method is listed in telemetry if you agree to it through the consentToContact
+              setting in the admin panel.
+            '';
+          };
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable (mkMerge [
+    {
+      systemd.services.cryptpad = {
+        description = "Cryptpad service";
+        wantedBy = [ "multi-user.target" ];
+        after = [ "networking.target" ];
+        serviceConfig = {
+          BindReadOnlyPaths = [
+            cryptpadConfigFile
+            # apparently needs proc for workers management
+            "/proc"
+            "/dev/urandom"
+          ];
+          DynamicUser = true;
+          Environment = [
+            "CRYPTPAD_CONFIG=${cryptpadConfigFile}"
+            "HOME=%S/cryptpad"
+          ];
+          ExecStart = lib.getExe cfg.package;
+          Restart = "always";
+          StateDirectory = "cryptpad";
+          WorkingDirectory = "%S/cryptpad";
+          # security way too many numerous options, from the systemd-analyze security output
+          # at end of test: block everything except
+          # - SystemCallFiters=@resources is required for node
+          # - MemoryDenyWriteExecute for node JIT
+          # - RestrictAddressFamilies=~AF_(INET|INET6) / PrivateNetwork to bind to sockets
+          # - IPAddressDeny likewise allow localhost if binding to localhost or any otherwise
+          # - PrivateUsers somehow service doesn't start with that
+          # - DeviceAllow (char-rtc r added by ProtectClock)
+          AmbientCapabilities = "";
+          CapabilityBoundingSet = "";
+          DeviceAllow = "";
+          LockPersonality = true;
+          NoNewPrivileges = true;
+          PrivateDevices = true;
+          PrivateTmp = true;
+          ProcSubset = "pid";
+          ProtectClock = true;
+          ProtectControlGroups = true;
+          ProtectHome = true;
+          ProtectHostname = true;
+          ProtectKernelLogs = true;
+          ProtectKernelModules = true;
+          ProtectKernelTunables = true;
+          ProtectProc = "invisible";
+          ProtectSystem = "strict";
+          RemoveIPC = true;
+          RestrictAddressFamilies = [
+            "AF_INET"
+            "AF_INET6"
+          ];
+          RestrictNamespaces = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+          RuntimeDirectoryMode = "700";
+          SocketBindAllow = [
+            "tcp:${builtins.toString cfg.settings.httpPort}"
+            "tcp:${builtins.toString cfg.settings.websocketPort}"
+          ];
+          SocketBindDeny = [ "any" ];
+          StateDirectoryMode = "0700";
+          SystemCallArchitectures = "native";
+          SystemCallFilter = [
+            "@pkey"
+            "@system-service"
+            "~@chown"
+            "~@keyring"
+            "~@memlock"
+            "~@privileged"
+            "~@resources"
+            "~@setuid"
+            "~@timer"
+          ];
+          UMask = "0077";
+        };
+        confinement = {
+          enable = true;
+          binSh = null;
+          mode = "chroot-only";
+        };
+      };
+    }
+    # block external network access if not phoning home and
+    # binding to localhost (default)
+    (mkIf
+      (
+        cfg.settings.blockDailyCheck
+        && (builtins.elem cfg.settings.httpAddress [
+          "127.0.0.1"
+          "::1"
+        ])
+      )
+      {
+        systemd.services.cryptpad = {
+          serviceConfig = {
+            IPAddressAllow = [ "localhost" ];
+            IPAddressDeny = [ "any" ];
+          };
+        };
+      }
+    )
+    # .. conversely allow DNS & TLS if telemetry is explicitly enabled
+    (mkIf (!cfg.settings.blockDailyCheck) {
+      systemd.services.cryptpad = {
+        serviceConfig = {
+          BindReadOnlyPaths = [
+            "-/etc/resolv.conf"
+            "-/run/systemd"
+            "/etc/hosts"
+            "/etc/ssl/certs/ca-certificates.crt"
+          ];
+        };
+      };
+    })
+
+    (mkIf cfg.configureNginx {
+      assertions = [
+        {
+          assertion = cfg.settings.httpUnsafeOrigin != "";
+          message = "services.cryptpad.settings.httpUnsafeOrigin is required";
+        }
+        {
+          assertion = strings.hasPrefix "https://" cfg.settings.httpUnsafeOrigin;
+          message = "services.cryptpad.settings.httpUnsafeOrigin must start with https://";
+        }
+        {
+          assertion =
+            cfg.settings.httpSafeOrigin == null || strings.hasPrefix "https://" cfg.settings.httpSafeOrigin;
+          message = "services.cryptpad.settings.httpSafeOrigin must start with https:// (or be unset)";
+        }
+      ];
+      services.nginx = {
+        enable = true;
+        recommendedTlsSettings = true;
+        recommendedProxySettings = true;
+        recommendedOptimisation = true;
+        recommendedGzipSettings = true;
+
+        virtualHosts = mkMerge [
+          {
+            "${mainDomain}" = {
+              serverAliases = lib.optionals (cfg.settings.httpSafeOrigin != null) [ sandboxDomain ];
+              enableACME = lib.mkDefault true;
+              forceSSL = true;
+              locations."/" = {
+                proxyPass = "http://${cfg.settings.httpAddress}:${builtins.toString cfg.settings.httpPort}";
+                extraConfig = ''
+                  client_max_body_size 150m;
+                '';
+              };
+              locations."/cryptpad_websocket" = {
+                proxyPass = "http://${cfg.settings.httpAddress}:${builtins.toString cfg.settings.websocketPort}";
+                proxyWebsockets = true;
+              };
+            };
+          }
+        ];
+      };
+    })
+  ]);
+}
diff --git a/nixos/modules/services/web-apps/eintopf.nix b/nixos/modules/services/web-apps/eintopf.nix
new file mode 100644
index 0000000000000..a2b304445597e
--- /dev/null
+++ b/nixos/modules/services/web-apps/eintopf.nix
@@ -0,0 +1,92 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.eintopf;
+
+in {
+  options.services.eintopf = {
+
+    enable = mkEnableOption "Eintopf community event calendar web app";
+
+    settings = mkOption {
+      type = types.attrsOf types.str;
+      default = { };
+      description = ''
+        Settings to configure web service. See
+        <https://codeberg.org/Klasse-Methode/eintopf/src/branch/main/DEPLOYMENT.md>
+        for available options.
+      '';
+      example = literalExpression ''
+        {
+          EINTOPF_ADDR = ":1234";
+          EINTOPF_ADMIN_EMAIL = "admin@example.org";
+          EINTOPF_TIMEZONE = "Europe/Berlin";
+        }
+      '';
+    };
+
+    secrets = lib.mkOption {
+      type = with types; listOf path;
+      description = ''
+        A list of files containing the various secrets. Should be in the
+        format expected by systemd's `EnvironmentFile` directory.
+      '';
+      default = [ ];
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.services.eintopf = {
+      description = "Community event calendar web app";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" ];
+      wants = [ "network-online.target" ];
+      environment = cfg.settings;
+      serviceConfig = {
+        ExecStart = "${pkgs.eintopf}/bin/eintopf";
+        WorkingDirectory = "/var/lib/eintopf";
+        StateDirectory = "eintopf" ;
+        EnvironmentFile = [ cfg.secrets ];
+
+        # hardening
+        AmbientCapabilities = "";
+        CapabilityBoundingSet = "" ;
+        DevicePolicy = "closed";
+        DynamicUser = true;
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        NoNewPrivileges = true;
+        PrivateDevices = true;
+        PrivateTmp = true;
+        PrivateUsers = true;
+        ProcSubset = "pid";
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        ProtectSystem = "strict";
+        RemoveIPC = true;
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [ "@system-service" "~@privileged" ];
+        UMask = "0077";
+      };
+    };
+
+  };
+
+  meta.maintainers = with lib.maintainers; [ onny ];
+
+}
diff --git a/nixos/modules/services/web-apps/freshrss.nix b/nixos/modules/services/web-apps/freshrss.nix
index 7a22e15231923..53366e2c891e9 100644
--- a/nixos/modules/services/web-apps/freshrss.nix
+++ b/nixos/modules/services/web-apps/freshrss.nix
@@ -19,7 +19,7 @@ in
   meta.maintainers = with maintainers; [ etu stunkymonkey mattchrist ];
 
   options.services.freshrss = {
-    enable = mkEnableOption "FreshRSS RSS aggregator and reader with php-fpm backend.";
+    enable = mkEnableOption "FreshRSS RSS aggregator and reader with php-fpm backend";
 
     package = mkPackageOption pkgs "freshrss" { };
 
@@ -281,7 +281,10 @@ in
               ${if cfg.database.passFile != null then "--db-password" else null} = ''"$(cat ${cfg.database.passFile})"'';
               ${if cfg.database.user != null then "--db-user" else null} = ''"${cfg.database.user}"'';
               ${if cfg.database.tableprefix != null then "--db-prefix" else null} = ''"${cfg.database.tableprefix}"'';
+              # hostname:port e.g. "localhost:5432"
               ${if cfg.database.host != null && cfg.database.port != null then "--db-host" else null} = ''"${cfg.database.host}:${toString cfg.database.port}"'';
+              # socket path e.g. "/run/postgresql"
+              ${if cfg.database.host != null && cfg.database.port == null then "--db-host" else null} = ''"${cfg.database.host}"'';
             });
         in
         {
diff --git a/nixos/modules/services/web-apps/glance.md b/nixos/modules/services/web-apps/glance.md
new file mode 100644
index 0000000000000..f65b32b3ba914
--- /dev/null
+++ b/nixos/modules/services/web-apps/glance.md
@@ -0,0 +1,39 @@
+# Glance {#module-services-glance}
+
+Glance is a self-hosted dashboard that puts all your feeds in one place.
+
+Visit [the Glance project page](https://github.com/glanceapp/glance) to learn
+more about it.
+
+## Quickstart {#module-services-glance-quickstart}
+
+Checkout the [configuration docs](https://github.com/glanceapp/glance/blob/main/docs/configuration.md) to learn more.
+Use the following configuration to start a public instance of Glance locally:
+
+```nix
+{
+  services.glance = {
+    enable = true;
+    settings = {
+      pages = [
+        {
+          name = "Home";
+          columns = [
+            {
+              size = "full";
+              widgets = [
+                { type = "calendar"; }
+                {
+                  type = "weather";
+                  location = "Nivelles, Belgium";
+                }
+              ];
+            }
+          ];
+        }
+      ];
+    };
+    openFirewall = true;
+  };
+}
+```
diff --git a/nixos/modules/services/web-apps/glance.nix b/nixos/modules/services/web-apps/glance.nix
new file mode 100644
index 0000000000000..fbc310daea770
--- /dev/null
+++ b/nixos/modules/services/web-apps/glance.nix
@@ -0,0 +1,141 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+let
+  cfg = config.services.glance;
+
+  inherit (lib)
+    mkEnableOption
+    mkPackageOption
+    mkOption
+    mkIf
+    getExe
+    types
+    ;
+
+  settingsFormat = pkgs.formats.yaml { };
+in
+{
+  options.services.glance = {
+    enable = mkEnableOption "glance";
+    package = mkPackageOption pkgs "glance" { };
+
+    settings = mkOption {
+      type = types.submodule {
+        freeformType = settingsFormat.type;
+        options = {
+          server = {
+            host = mkOption {
+              description = "Glance bind address";
+              default = "127.0.0.1";
+              example = "0.0.0.0";
+              type = types.str;
+            };
+            port = mkOption {
+              description = "Glance port to listen on";
+              default = 8080;
+              example = 5678;
+              type = types.port;
+            };
+          };
+          pages = mkOption {
+            type = settingsFormat.type;
+            description = ''
+              List of pages to be present on the dashboard.
+
+              See <https://github.com/glanceapp/glance/blob/main/docs/configuration.md#pages--columns>
+            '';
+            default = [
+              {
+                name = "Calendar";
+                columns = [
+                  {
+                    size = "full";
+                    widgets = [ { type = "calendar"; } ];
+                  }
+                ];
+              }
+            ];
+            example = [
+              {
+                name = "Home";
+                columns = [
+                  {
+                    size = "full";
+                    widgets = [
+                      { type = "calendar"; }
+                      {
+                        type = "weather";
+                        location = "Nivelles, Belgium";
+                      }
+                    ];
+                  }
+                ];
+              }
+            ];
+          };
+        };
+      };
+      default = { };
+      description = ''
+        Configuration written to a yaml file that is read by glance. See
+        <https://github.com/glanceapp/glance/blob/main/docs/configuration.md>
+        for more.
+      '';
+    };
+
+    openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to open the firewall for Glance.
+        This adds `services.glance.settings.server.port` to `networking.firewall.allowedTCPPorts`.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.glance = {
+      description = "Glance feed dashboard server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        ExecStart =
+          let
+            glance-yaml = settingsFormat.generate "glance.yaml" cfg.settings;
+          in
+          "${getExe cfg.package} --config ${glance-yaml}";
+        WorkingDirectory = "/var/lib/glance";
+        StateDirectory = "glance";
+        RuntimeDirectory = "glance";
+        RuntimeDirectoryMode = "0755";
+        PrivateTmp = true;
+        DynamicUser = true;
+        DevicePolicy = "closed";
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        PrivateUsers = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectControlGroups = true;
+        ProcSubset = "pid";
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        SystemCallArchitectures = "native";
+        UMask = "0077";
+      };
+    };
+
+    networking.firewall = mkIf cfg.openFirewall { allowedTCPPorts = [ cfg.settings.server.port ]; };
+  };
+
+  meta.doc = ./glance.md;
+  meta.maintainers = [ lib.maintainers.drupol ];
+}
diff --git a/nixos/modules/services/web-apps/goatcounter.nix b/nixos/modules/services/web-apps/goatcounter.nix
new file mode 100644
index 0000000000000..318915fb88fff
--- /dev/null
+++ b/nixos/modules/services/web-apps/goatcounter.nix
@@ -0,0 +1,80 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  inherit (lib) types;
+  cfg = config.services.goatcounter;
+  stateDir = "goatcounter";
+in
+
+{
+  options = {
+    services.goatcounter = {
+      enable = lib.mkEnableOption "goatcounter";
+
+      package = lib.mkPackageOption pkgs "goatcounter" { };
+
+      address = lib.mkOption {
+        type = types.str;
+        default = "127.0.0.1";
+        description = "Web interface address.";
+      };
+
+      port = lib.mkOption {
+        type = types.port;
+        default = 8081;
+        description = "Web interface port.";
+      };
+
+      proxy = lib.mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether Goatcounter service is running behind a reverse proxy. Will listen for HTTPS if `false`.
+          Refer to [documentation](https://github.com/arp242/goatcounter?tab=readme-ov-file#running) for more details.
+        '';
+      };
+
+      extraArgs = lib.mkOption {
+        type = with types; listOf str;
+        default = [ ];
+        description = ''
+          List of extra arguments to be passed to goatcounter cli.
+          See {command}`goatcounter help serve` for more information.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.goatcounter = {
+      description = "Easy web analytics. No tracking of personal data.";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = lib.escapeShellArgs (
+          [
+            (lib.getExe cfg.package)
+            "serve"
+            "-listen"
+            "${cfg.address}:${toString cfg.port}"
+          ]
+          ++ lib.optionals cfg.proxy [
+            "-tls"
+            "proxy"
+          ]
+          ++ cfg.extraArgs
+        );
+        DynamicUser = true;
+        StateDirectory = stateDir;
+        WorkingDirectory = "%S/${stateDir}";
+        Restart = "always";
+      };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ bhankas ];
+}
diff --git a/nixos/modules/services/web-apps/gotify-server.nix b/nixos/modules/services/web-apps/gotify-server.nix
index b700fd14ee52f..5f5a9f9f86bbc 100644
--- a/nixos/modules/services/web-apps/gotify-server.nix
+++ b/nixos/modules/services/web-apps/gotify-server.nix
@@ -1,49 +1,90 @@
-{ pkgs, lib, config, ... }:
-
-with lib;
+{
+  pkgs,
+  lib,
+  config,
+  ...
+}:
 
 let
   cfg = config.services.gotify;
-in {
-  options = {
-    services.gotify = {
-      enable = mkEnableOption "Gotify webserver";
-
-      port = mkOption {
-        type = types.port;
-        description = ''
-          Port the server listens to.
-        '';
-      };
+in
+{
+  imports = [
+    (lib.mkRenamedOptionModule
+      [
+        "services"
+        "gotify"
+        "port"
+      ]
+      [
+        "services"
+        "gotify"
+        "environment"
+        "GOTIFY_SERVER_PORT"
+      ]
+    )
+  ];
+
+  options.services.gotify = {
+    enable = lib.mkEnableOption "Gotify webserver";
 
-      stateDirectoryName = mkOption {
-        type = types.str;
-        default = "gotify-server";
-        description = ''
-          The name of the directory below {file}`/var/lib` where
-          gotify stores its runtime data.
-        '';
+    package = lib.mkPackageOption pkgs "gotify-server" { };
+
+    environment = lib.mkOption {
+      type = lib.types.attrsOf (
+        lib.types.oneOf [
+          lib.types.str
+          lib.types.int
+        ]
+      );
+      default = { };
+      example = {
+        GOTIFY_SERVER_PORT = 8080;
+        GOTIFY_DATABASE_DIALECT = "sqlite3";
       };
+      description = ''
+        Config environment variables for the gotify-server.
+        See https://gotify.net/docs/config for more details.
+      '';
+    };
+
+    environmentFiles = lib.mkOption {
+      type = lib.types.listOf lib.types.path;
+      default = [ ];
+      description = ''
+        Files containing additional config environment variables for gotify-server.
+        Secrets should be set in environmentFiles instead of environment.
+      '';
+    };
+
+    stateDirectoryName = lib.mkOption {
+      type = lib.types.str;
+      default = "gotify-server";
+      description = ''
+        The name of the directory below {file}`/var/lib` where
+        gotify stores its runtime data.
+      '';
     };
   };
 
-  config = mkIf cfg.enable {
+  config = lib.mkIf cfg.enable {
     systemd.services.gotify-server = {
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
       description = "Simple server for sending and receiving messages";
 
-      environment = {
-        GOTIFY_SERVER_PORT = toString cfg.port;
-      };
+      environment = lib.mapAttrs (_: toString) cfg.environment;
 
       serviceConfig = {
         WorkingDirectory = "/var/lib/${cfg.stateDirectoryName}";
         StateDirectory = cfg.stateDirectoryName;
+        EnvironmentFile = cfg.environmentFiles;
         Restart = "always";
-        DynamicUser = "yes";
-        ExecStart = "${pkgs.gotify-server}/bin/server";
+        DynamicUser = true;
+        ExecStart = lib.getExe cfg.package;
       };
     };
   };
+
+  meta.maintainers = with lib.maintainers; [ DCsunset ];
 }
diff --git a/nixos/modules/services/web-apps/ifm.nix b/nixos/modules/services/web-apps/ifm.nix
new file mode 100644
index 0000000000000..d5621866a9a3c
--- /dev/null
+++ b/nixos/modules/services/web-apps/ifm.nix
@@ -0,0 +1,81 @@
+{ config, lib, pkgs, ...}:
+let
+  cfg = config.services.ifm;
+
+  version = "4.0.2";
+  src = pkgs.fetchurl {
+    url = "https://github.com/misterunknown/ifm/releases/download/v${version}/cdn.ifm.php";
+    hash = "sha256-37WbRM6D7JGmd//06zMhxMGIh8ioY8vRUmxX4OHgqBE=";
+  };
+
+  php = pkgs.php83;
+in {
+  options.services.ifm = {
+    enable = lib.mkEnableOption ''
+      Improved file manager, a single-file web-based filemanager
+
+      Lightweight and minimal, served using PHP's built-in server
+  '';
+
+    dataDir = lib.mkOption {
+      type = lib.types.str;
+      description = "Directory to serve throught the file managing service";
+    };
+
+    listenAddress = lib.mkOption {
+      type = lib.types.str;
+      default = "127.0.0.1";
+      description = "Address on which the service is listening";
+      example = "0.0.0.0";
+    };
+
+    port = lib.mkOption {
+      type = lib.types.port;
+      default = 9090;
+      description = "Port on which to serve the IFM service";
+    };
+
+    settings = lib.mkOption {
+      type = with lib.types; attrsOf anything;
+      default = {};
+      description = ''
+        Configuration of the IFM service.
+
+        See [the documentation](https://github.com/misterunknown/ifm/wiki/Configuration)
+        for available options and default values.
+      '';
+      example = {
+        IFM_GUI_SHOWPATH = 0;
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.ifm = {
+      description = "Improved file manager, a single-file web based filemanager";
+
+      after = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      environment = {
+        IFM_ROOT_DIR = "/data";
+      } // (builtins.mapAttrs (_: val: toString val) cfg.settings);
+
+      script = ''
+        mkdir -p /tmp/ifm
+        ln -s ${src} /tmp/ifm/index.php
+        ${lib.getExe php} -S ${cfg.listenAddress}:${builtins.toString cfg.port} -t /tmp/ifm
+      '';
+
+      serviceConfig = {
+        DynamicUser = true;
+        User = "ifm";
+        StandardOutput = "journal";
+        BindPaths = "${cfg.dataDir}:/data";
+        PrivateTmp = true;
+      };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ litchipi ];
+}
diff --git a/nixos/modules/services/web-apps/jitsi-meet.nix b/nixos/modules/services/web-apps/jitsi-meet.nix
index 247b65c786636..39aa7379c0edf 100644
--- a/nixos/modules/services/web-apps/jitsi-meet.nix
+++ b/nixos/modules/services/web-apps/jitsi-meet.nix
@@ -398,30 +398,29 @@ in
       before = [ "jicofo.service" "jitsi-videobridge2.service" ] ++ (optional cfg.prosody.enable "prosody.service") ++ (optional cfg.jigasi.enable "jigasi.service");
       serviceConfig = {
         Type = "oneshot";
+        UMask = "027";
+        User = "root";
+        Group = "jitsi-meet";
+        WorkingDirectory = "/var/lib/jitsi-meet";
       };
 
       script = let
         secrets = [ "jicofo-component-secret" "jicofo-user-secret" "jibri-auth-secret" "jibri-recorder-secret" ] ++ (optionals cfg.jigasi.enable [ "jigasi-user-secret" "jigasi-component-secret" ]) ++ (optional (cfg.videobridge.passwordFile == null) "videobridge-secret");
       in
       ''
-        cd /var/lib/jitsi-meet
         ${concatMapStringsSep "\n" (s: ''
           if [ ! -f ${s} ]; then
             tr -dc a-zA-Z0-9 </dev/urandom | head -c 64 > ${s}
-            chown root:jitsi-meet ${s}
-            chmod 640 ${s}
           fi
         '') secrets}
 
         # for easy access in prosody
         echo "JICOFO_COMPONENT_SECRET=$(cat jicofo-component-secret)" > secrets-env
         echo "JIGASI_COMPONENT_SECRET=$(cat jigasi-component-secret)" >> secrets-env
-        chown root:jitsi-meet secrets-env
-        chmod 640 secrets-env
       ''
       + optionalString cfg.prosody.enable ''
         # generate self-signed certificates
-        if [ ! -f /var/lib/jitsi-meet.crt ]; then
+        if [ ! -f /var/lib/jitsi-meet/jitsi-meet.crt ]; then
           ${getBin pkgs.openssl}/bin/openssl req \
             -x509 \
             -newkey rsa:4096 \
@@ -430,8 +429,7 @@ in
             -days 36500 \
             -nodes \
             -subj '/CN=${cfg.hostName}/CN=auth.${cfg.hostName}'
-          chmod 640 /var/lib/jitsi-meet/jitsi-meet.{crt,key}
-          chown root:jitsi-meet /var/lib/jitsi-meet/jitsi-meet.{crt,key}
+          chmod 640 /var/lib/jitsi-meet/jitsi-meet.key
         fi
       '';
     };
diff --git a/nixos/modules/services/web-apps/mealie.nix b/nixos/modules/services/web-apps/mealie.nix
index 2484b2489c0d0..ab94103954e13 100644
--- a/nixos/modules/services/web-apps/mealie.nix
+++ b/nixos/modules/services/web-apps/mealie.nix
@@ -57,7 +57,6 @@ in
 
       environment = {
         PRODUCTION = "true";
-        ALEMBIC_CONFIG_FILE="${pkg}/config/alembic.ini";
         API_PORT = toString cfg.port;
         BASE_URL = "http://localhost:${toString cfg.port}";
         DATA_DIR = "/var/lib/mealie";
diff --git a/nixos/modules/services/web-apps/mediawiki.nix b/nixos/modules/services/web-apps/mediawiki.nix
index b11626ec2dc3b..22e61ea3a6836 100644
--- a/nixos/modules/services/web-apps/mediawiki.nix
+++ b/nixos/modules/services/web-apps/mediawiki.nix
@@ -19,7 +19,7 @@ let
   stateDir = "/var/lib/mediawiki";
 
   # https://www.mediawiki.org/wiki/Compatibility
-  php = pkgs.php81;
+  php = pkgs.php82;
 
   pkg = pkgs.stdenv.mkDerivation rec {
     pname = "mediawiki-full";
@@ -64,129 +64,135 @@ let
   else
     throw "Unsupported database type: ${cfg.database.type} for socket: ${cfg.database.socket}";
 
-  mediawikiConfig = pkgs.writeText "LocalSettings.php" ''
-    <?php
-      # Protect against web entry
-      if ( !defined( 'MEDIAWIKI' ) ) {
-        exit;
-      }
+  mediawikiConfig = pkgs.writeTextFile {
+    name = "LocalSettings.php";
+    checkPhase = ''
+      ${php}/bin/php --syntax-check "$target"
+    '';
+    text = ''
+      <?php
+        # Protect against web entry
+        if ( !defined( 'MEDIAWIKI' ) ) {
+          exit;
+        }
 
-      $wgSitename = "${cfg.name}";
-      $wgMetaNamespace = false;
-
-      ## The URL base path to the directory containing the wiki;
-      ## defaults for all runtime URL paths are based off of this.
-      ## For more information on customizing the URLs
-      ## (like /w/index.php/Page_title to /wiki/Page_title) please see:
-      ## https://www.mediawiki.org/wiki/Manual:Short_URL
-      $wgScriptPath = "${lib.optionalString (cfg.webserver == "nginx") "/w"}";
+        $wgSitename = "${cfg.name}";
+        $wgMetaNamespace = false;
+
+        ## The URL base path to the directory containing the wiki;
+        ## defaults for all runtime URL paths are based off of this.
+        ## For more information on customizing the URLs
+        ## (like /w/index.php/Page_title to /wiki/Page_title) please see:
+        ## https://www.mediawiki.org/wiki/Manual:Short_URL
+        $wgScriptPath = "${lib.optionalString (cfg.webserver == "nginx") "/w"}";
 
-      ## The protocol and server name to use in fully-qualified URLs
-      $wgServer = "${cfg.url}";
+        ## The protocol and server name to use in fully-qualified URLs
+        $wgServer = "${cfg.url}";
 
-      ## The URL path to static resources (images, scripts, etc.)
-      $wgResourceBasePath = $wgScriptPath;
+        ## The URL path to static resources (images, scripts, etc.)
+        $wgResourceBasePath = $wgScriptPath;
 
-      ${lib.optionalString (cfg.webserver == "nginx") ''
-        $wgArticlePath = "/wiki/$1";
-        $wgUsePathInfo = true;
-      ''}
+        ${lib.optionalString (cfg.webserver == "nginx") ''
+          $wgArticlePath = "/wiki/$1";
+          $wgUsePathInfo = true;
+        ''}
 
-      ## The URL path to the logo.  Make sure you change this from the default,
-      ## or else you'll overwrite your logo when you upgrade!
-      $wgLogo = "$wgResourceBasePath/resources/assets/wiki.png";
+        ## The URL path to the logo.  Make sure you change this from the default,
+        ## or else you'll overwrite your logo when you upgrade!
+        $wgLogo = "$wgResourceBasePath/resources/assets/wiki.png";
 
-      ## UPO means: this is also a user preference option
+        ## UPO means: this is also a user preference option
 
-      $wgEnableEmail = true;
-      $wgEnableUserEmail = true; # UPO
+        $wgEnableEmail = true;
+        $wgEnableUserEmail = true; # UPO
 
-      $wgPasswordSender = "${cfg.passwordSender}";
+        $wgPasswordSender = "${cfg.passwordSender}";
 
-      $wgEnotifUserTalk = false; # UPO
-      $wgEnotifWatchlist = false; # UPO
-      $wgEmailAuthentication = true;
+        $wgEnotifUserTalk = false; # UPO
+        $wgEnotifWatchlist = false; # UPO
+        $wgEmailAuthentication = true;
 
-      ## Database settings
-      $wgDBtype = "${cfg.database.type}";
-      $wgDBserver = "${dbAddr}";
-      $wgDBport = "${toString cfg.database.port}";
-      $wgDBname = "${cfg.database.name}";
-      $wgDBuser = "${cfg.database.user}";
-      ${optionalString (cfg.database.passwordFile != null) "$wgDBpassword = file_get_contents(\"${cfg.database.passwordFile}\");"}
+        ## Database settings
+        $wgDBtype = "${cfg.database.type}";
+        $wgDBserver = "${dbAddr}";
+        $wgDBport = "${toString cfg.database.port}";
+        $wgDBname = "${cfg.database.name}";
+        $wgDBuser = "${cfg.database.user}";
+        ${optionalString (cfg.database.passwordFile != null) "$wgDBpassword = file_get_contents(\"${cfg.database.passwordFile}\");"}
 
-      ${optionalString (cfg.database.type == "mysql" && cfg.database.tablePrefix != null) ''
-        # MySQL specific settings
-        $wgDBprefix = "${cfg.database.tablePrefix}";
-      ''}
+        ${optionalString (cfg.database.type == "mysql" && cfg.database.tablePrefix != null) ''
+          # MySQL specific settings
+          $wgDBprefix = "${cfg.database.tablePrefix}";
+        ''}
 
-      ${optionalString (cfg.database.type == "mysql") ''
-        # MySQL table options to use during installation or update
-        $wgDBTableOptions = "ENGINE=InnoDB, DEFAULT CHARSET=binary";
-      ''}
+        ${optionalString (cfg.database.type == "mysql") ''
+          # MySQL table options to use during installation or update
+          $wgDBTableOptions = "ENGINE=InnoDB, DEFAULT CHARSET=binary";
+        ''}
 
-      ## Shared memory settings
-      $wgMainCacheType = CACHE_NONE;
-      $wgMemCachedServers = [];
+        ## Shared memory settings
+        $wgMainCacheType = CACHE_NONE;
+        $wgMemCachedServers = [];
 
-      ${optionalString (cfg.uploadsDir != null) ''
-        $wgEnableUploads = true;
-        $wgUploadDirectory = "${cfg.uploadsDir}";
-      ''}
+        ${optionalString (cfg.uploadsDir != null) ''
+          $wgEnableUploads = true;
+          $wgUploadDirectory = "${cfg.uploadsDir}";
+        ''}
 
-      $wgUseImageMagick = true;
-      $wgImageMagickConvertCommand = "${pkgs.imagemagick}/bin/convert";
+        $wgUseImageMagick = true;
+        $wgImageMagickConvertCommand = "${pkgs.imagemagick}/bin/convert";
 
-      # InstantCommons allows wiki to use images from https://commons.wikimedia.org
-      $wgUseInstantCommons = false;
+        # InstantCommons allows wiki to use images from https://commons.wikimedia.org
+        $wgUseInstantCommons = false;
 
-      # Periodically send a pingback to https://www.mediawiki.org/ with basic data
-      # about this MediaWiki instance. The Wikimedia Foundation shares this data
-      # with MediaWiki developers to help guide future development efforts.
-      $wgPingback = true;
+        # Periodically send a pingback to https://www.mediawiki.org/ with basic data
+        # about this MediaWiki instance. The Wikimedia Foundation shares this data
+        # with MediaWiki developers to help guide future development efforts.
+        $wgPingback = true;
 
-      ## If you use ImageMagick (or any other shell command) on a
-      ## Linux server, this will need to be set to the name of an
-      ## available UTF-8 locale
-      $wgShellLocale = "C.UTF-8";
+        ## If you use ImageMagick (or any other shell command) on a
+        ## Linux server, this will need to be set to the name of an
+        ## available UTF-8 locale
+        $wgShellLocale = "C.UTF-8";
 
-      ## Set $wgCacheDirectory to a writable directory on the web server
-      ## to make your wiki go slightly faster. The directory should not
-      ## be publicly accessible from the web.
-      $wgCacheDirectory = "${cacheDir}";
+        ## Set $wgCacheDirectory to a writable directory on the web server
+        ## to make your wiki go slightly faster. The directory should not
+        ## be publicly accessible from the web.
+        $wgCacheDirectory = "${cacheDir}";
 
-      # Site language code, should be one of the list in ./languages/data/Names.php
-      $wgLanguageCode = "en";
+        # Site language code, should be one of the list in ./languages/data/Names.php
+        $wgLanguageCode = "en";
 
-      $wgSecretKey = file_get_contents("${stateDir}/secret.key");
+        $wgSecretKey = file_get_contents("${stateDir}/secret.key");
 
-      # Changing this will log out all existing sessions.
-      $wgAuthenticationTokenVersion = "";
+        # Changing this will log out all existing sessions.
+        $wgAuthenticationTokenVersion = "";
 
-      ## For attaching licensing metadata to pages, and displaying an
-      ## appropriate copyright notice / icon. GNU Free Documentation
-      ## License and Creative Commons licenses are supported so far.
-      $wgRightsPage = ""; # Set to the title of a wiki page that describes your license/copyright
-      $wgRightsUrl = "";
-      $wgRightsText = "";
-      $wgRightsIcon = "";
+        ## For attaching licensing metadata to pages, and displaying an
+        ## appropriate copyright notice / icon. GNU Free Documentation
+        ## License and Creative Commons licenses are supported so far.
+        $wgRightsPage = ""; # Set to the title of a wiki page that describes your license/copyright
+        $wgRightsUrl = "";
+        $wgRightsText = "";
+        $wgRightsIcon = "";
 
-      # Path to the GNU diff3 utility. Used for conflict resolution.
-      $wgDiff = "${pkgs.diffutils}/bin/diff";
-      $wgDiff3 = "${pkgs.diffutils}/bin/diff3";
+        # Path to the GNU diff3 utility. Used for conflict resolution.
+        $wgDiff = "${pkgs.diffutils}/bin/diff";
+        $wgDiff3 = "${pkgs.diffutils}/bin/diff3";
 
-      # Enabled skins.
-      ${concatStringsSep "\n" (mapAttrsToList (k: v: "wfLoadSkin('${k}');") cfg.skins)}
+        # Enabled skins.
+        ${concatStringsSep "\n" (mapAttrsToList (k: v: "wfLoadSkin('${k}');") cfg.skins)}
 
-      # Enabled extensions.
-      ${concatStringsSep "\n" (mapAttrsToList (k: v: "wfLoadExtension('${k}');") cfg.extensions)}
+        # Enabled extensions.
+        ${concatStringsSep "\n" (mapAttrsToList (k: v: "wfLoadExtension('${k}');") cfg.extensions)}
 
 
-      # End of automatically generated settings.
-      # Add more configuration options below.
+        # End of automatically generated settings.
+        # Add more configuration options below.
 
-      ${cfg.extraConfig}
-  '';
+        ${cfg.extraConfig}
+      '';
+    };
 
   withTrailingSlash = str: if lib.hasSuffix "/" str then str else "${str}/";
 in
diff --git a/nixos/modules/services/web-apps/meme-bingo-web.nix b/nixos/modules/services/web-apps/meme-bingo-web.nix
index ab123ba8ef9ca..4623bc22c26ff 100644
--- a/nixos/modules/services/web-apps/meme-bingo-web.nix
+++ b/nixos/modules/services/web-apps/meme-bingo-web.nix
@@ -17,7 +17,7 @@ in {
 
       baseUrl = mkOption {
         description = ''
-          URL to be used for the HTML <base> element on all HTML routes.
+          URL to be used for the HTML \<base\> element on all HTML routes.
         '';
         type = types.str;
         default = "http://localhost:41678/";
@@ -36,7 +36,7 @@ in {
 
   config = mkIf cfg.enable {
     systemd.services.meme-bingo-web = {
-      description = "A web app for playing meme bingos.";
+      description = "A web app for playing meme bingos";
       wantedBy = [ "multi-user.target" ];
 
       environment = {
@@ -59,6 +59,7 @@ in {
         # Hardening
         CapabilityBoundingSet = [ "" ];
         DeviceAllow = [ "/dev/random" ];
+        InaccessiblePaths = [ "/dev/shm" "/sys" ];
         LockPersonality = true;
         PrivateDevices = true;
         PrivateUsers = true;
@@ -73,6 +74,7 @@ in {
         ProtectKernelTunables = true;
         ProtectProc = "invisible";
         RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+        RestrictFilesystems = [ "@basic-api" "~sysfs" ];
         RestrictNamespaces = true;
         RestrictRealtime = true;
         SystemCallArchitectures = "native";
diff --git a/nixos/modules/services/web-apps/miniflux.nix b/nixos/modules/services/web-apps/miniflux.nix
index 61243a63c582e..b733ceec74dbe 100644
--- a/nixos/modules/services/web-apps/miniflux.nix
+++ b/nixos/modules/services/web-apps/miniflux.nix
@@ -49,7 +49,8 @@ in
       };
 
       adminCredentialsFile = mkOption {
-        type = types.path;
+        type = types.nullOr types.path;
+        default = null;
         description = ''
           File containing the ADMIN_USERNAME and
           ADMIN_PASSWORD (length >= 6) in the format of
@@ -61,11 +62,16 @@ in
   };
 
   config = mkIf cfg.enable {
+    assertions = [
+      { assertion = cfg.config.CREATE_ADMIN == 0 || cfg.adminCredentialsFile != null;
+        message = "services.miniflux.adminCredentialsFile must be set if services.miniflux.config.CREATE_ADMIN is 1";
+      }
+    ];
     services.miniflux.config = {
       LISTEN_ADDR = mkDefault defaultAddress;
       DATABASE_URL = lib.mkIf cfg.createDatabaseLocally "user=miniflux host=/run/postgresql dbname=miniflux";
       RUN_MIGRATIONS = 1;
-      CREATE_ADMIN = 1;
+      CREATE_ADMIN = lib.mkDefault 1;
       WATCHDOG = 1;
     };
 
@@ -103,7 +109,7 @@ in
         DynamicUser = true;
         RuntimeDirectory = "miniflux";
         RuntimeDirectoryMode = "0750";
-        EnvironmentFile = cfg.adminCredentialsFile;
+        EnvironmentFile = lib.mkIf (cfg.adminCredentialsFile != null) cfg.adminCredentialsFile;
         WatchdogSec = 60;
         WatchdogSignal = "SIGKILL";
         Restart = "always";
diff --git a/nixos/modules/services/web-apps/misskey.nix b/nixos/modules/services/web-apps/misskey.nix
new file mode 100644
index 0000000000000..8a5c4bd927660
--- /dev/null
+++ b/nixos/modules/services/web-apps/misskey.nix
@@ -0,0 +1,418 @@
+{
+  config,
+  pkgs,
+  lib,
+  ...
+}:
+
+let
+  cfg = config.services.misskey;
+  settingsFormat = pkgs.formats.yaml { };
+  redisType = lib.types.submodule {
+    freeformType = lib.types.attrsOf settingsFormat.type;
+    options = {
+      host = lib.mkOption {
+        type = lib.types.str;
+        default = "localhost";
+        description = "The Redis host.";
+      };
+      port = lib.mkOption {
+        type = lib.types.port;
+        default = 6379;
+        description = "The Redis port.";
+      };
+    };
+  };
+  settings = lib.mkOption {
+    description = ''
+      Configuration for Misskey, see
+      [`example.yml`](https://github.com/misskey-dev/misskey/blob/develop/.config/example.yml)
+      for all supported options.
+    '';
+    type = lib.types.submodule {
+      freeformType = lib.types.attrsOf settingsFormat.type;
+      options = {
+        url = lib.mkOption {
+          type = lib.types.str;
+          example = "https://example.tld/";
+          description = ''
+            The final user-facing URL. Do not change after running Misskey for the first time.
+
+            This needs to match up with the configured reverse proxy and is automatically configured when using `services.misskey.reverseProxy`.
+          '';
+        };
+        port = lib.mkOption {
+          type = lib.types.port;
+          default = 3000;
+          description = "The port your Misskey server should listen on.";
+        };
+        socket = lib.mkOption {
+          type = lib.types.nullOr lib.types.path;
+          default = null;
+          example = "/path/to/misskey.sock";
+          description = "The UNIX socket your Misskey server should listen on.";
+        };
+        chmodSocket = lib.mkOption {
+          type = lib.types.nullOr lib.types.str;
+          default = null;
+          example = "777";
+          description = "The file access mode of the UNIX socket.";
+        };
+        db = lib.mkOption {
+          description = "Database settings.";
+          type = lib.types.submodule {
+            options = {
+              host = lib.mkOption {
+                type = lib.types.str;
+                default = "/var/run/postgresql";
+                example = "localhost";
+                description = "The PostgreSQL host.";
+              };
+              port = lib.mkOption {
+                type = lib.types.port;
+                default = 5432;
+                description = "The PostgreSQL port.";
+              };
+              db = lib.mkOption {
+                type = lib.types.str;
+                default = "misskey";
+                description = "The database name.";
+              };
+              user = lib.mkOption {
+                type = lib.types.str;
+                default = "misskey";
+                description = "The user used for database authentication.";
+              };
+              pass = lib.mkOption {
+                type = lib.types.nullOr lib.types.str;
+                default = null;
+                description = "The password used for database authentication.";
+              };
+              disableCache = lib.mkOption {
+                type = lib.types.bool;
+                default = false;
+                description = "Whether to disable caching queries.";
+              };
+              extra = lib.mkOption {
+                type = lib.types.nullOr (lib.types.attrsOf settingsFormat.type);
+                default = null;
+                example = {
+                  ssl = true;
+                };
+                description = "Extra connection options.";
+              };
+            };
+          };
+          default = { };
+        };
+        redis = lib.mkOption {
+          type = redisType;
+          default = { };
+          description = "`ioredis` options. See [`README`](https://github.com/redis/ioredis?tab=readme-ov-file#connect-to-redis) for reference.";
+        };
+        redisForPubsub = lib.mkOption {
+          type = lib.types.nullOr redisType;
+          default = null;
+          description = "`ioredis` options for pubsub. See [`README`](https://github.com/redis/ioredis?tab=readme-ov-file#connect-to-redis) for reference.";
+        };
+        redisForJobQueue = lib.mkOption {
+          type = lib.types.nullOr redisType;
+          default = null;
+          description = "`ioredis` options for the job queue. See [`README`](https://github.com/redis/ioredis?tab=readme-ov-file#connect-to-redis) for reference.";
+        };
+        redisForTimelines = lib.mkOption {
+          type = lib.types.nullOr redisType;
+          default = null;
+          description = "`ioredis` options for timelines. See [`README`](https://github.com/redis/ioredis?tab=readme-ov-file#connect-to-redis) for reference.";
+        };
+        meilisearch = lib.mkOption {
+          description = "Meilisearch connection options.";
+          type = lib.types.nullOr (
+            lib.types.submodule {
+              options = {
+                host = lib.mkOption {
+                  type = lib.types.str;
+                  default = "localhost";
+                  description = "The Meilisearch host.";
+                };
+                port = lib.mkOption {
+                  type = lib.types.port;
+                  default = 7700;
+                  description = "The Meilisearch port.";
+                };
+                apiKey = lib.mkOption {
+                  type = lib.types.nullOr lib.types.str;
+                  default = null;
+                  description = "The Meilisearch API key.";
+                };
+                ssl = lib.mkOption {
+                  type = lib.types.bool;
+                  default = false;
+                  description = "Whether to connect via SSL.";
+                };
+                index = lib.mkOption {
+                  type = lib.types.nullOr lib.types.str;
+                  default = null;
+                  description = "Meilisearch index to use.";
+                };
+                scope = lib.mkOption {
+                  type = lib.types.enum [
+                    "local"
+                    "global"
+                  ];
+                  default = "local";
+                  description = "The search scope.";
+                };
+              };
+            }
+          );
+          default = null;
+        };
+        id = lib.mkOption {
+          type = lib.types.enum [
+            "aid"
+            "aidx"
+            "meid"
+            "ulid"
+            "objectid"
+          ];
+          default = "aidx";
+          description = "The ID generation method to use. Do not change after starting Misskey for the first time.";
+        };
+      };
+    };
+  };
+in
+
+{
+  options = {
+    services.misskey = {
+      enable = lib.mkEnableOption "misskey";
+      package = lib.mkPackageOption pkgs "misskey" { };
+      inherit settings;
+      database = {
+        createLocally = lib.mkOption {
+          type = lib.types.bool;
+          default = false;
+          description = "Create the PostgreSQL database locally. Sets `services.misskey.settings.db.{db,host,port,user,pass}`.";
+        };
+        passwordFile = lib.mkOption {
+          type = lib.types.nullOr lib.types.path;
+          default = null;
+          description = "The path to a file containing the database password. Sets `services.misskey.settings.db.pass`.";
+        };
+      };
+      redis = {
+        createLocally = lib.mkOption {
+          type = lib.types.bool;
+          default = false;
+          description = "Create and use a local Redis instance. Sets `services.misskey.settings.redis.host`.";
+        };
+        passwordFile = lib.mkOption {
+          type = lib.types.nullOr lib.types.path;
+          default = null;
+          description = "The path to a file containing the Redis password. Sets `services.misskey.settings.redis.pass`.";
+        };
+      };
+      meilisearch = {
+        createLocally = lib.mkOption {
+          type = lib.types.bool;
+          default = false;
+          description = "Create and use a local Meilisearch instance. Sets `services.misskey.settings.meilisearch.{host,port,ssl}`.";
+        };
+        keyFile = lib.mkOption {
+          type = lib.types.nullOr lib.types.path;
+          default = null;
+          description = "The path to a file containing the Meilisearch API key. Sets `services.misskey.settings.meilisearch.apiKey`.";
+        };
+      };
+      reverseProxy = {
+        enable = lib.mkEnableOption "a HTTP reverse proxy for Misskey";
+        webserver = lib.mkOption {
+          type = lib.types.attrTag {
+            nginx = lib.mkOption {
+              type = lib.types.submodule (import ../web-servers/nginx/vhost-options.nix);
+              default = { };
+              description = ''
+                Extra configuration for the nginx virtual host of Misskey.
+                Set to `{ }` to use the default configuration.
+              '';
+            };
+            caddy = lib.mkOption {
+              type = lib.types.submodule (
+                import ../web-servers/caddy/vhost-options.nix { cfg = config.services.caddy; }
+              );
+              default = { };
+              description = ''
+                Extra configuration for the caddy virtual host of Misskey.
+                Set to `{ }` to use the default configuration.
+              '';
+            };
+          };
+          description = "The webserver to use as the reverse proxy.";
+        };
+        host = lib.mkOption {
+          type = lib.types.nullOr lib.types.str;
+          description = ''
+            The fully qualified domain name to bind to. Sets `services.misskey.settings.url`.
+
+            This is required when using `services.misskey.reverseProxy.enable = true`.
+          '';
+          example = "misskey.example.com";
+          default = null;
+        };
+        ssl = lib.mkOption {
+          type = lib.types.nullOr lib.types.bool;
+          description = ''
+            Whether to enable SSL for the reverse proxy. Sets `services.misskey.settings.url`.
+
+            This is required when using `services.misskey.reverseProxy.enable = true`.
+          '';
+          example = true;
+          default = null;
+        };
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    assertions = [
+      {
+        assertion =
+          cfg.reverseProxy.enable -> ((cfg.reverseProxy.host != null) && (cfg.reverseProxy.ssl != null));
+        message = "`services.misskey.reverseProxy.enable` requires `services.misskey.reverseProxy.host` and `services.misskey.reverseProxy.ssl` to be set.";
+      }
+    ];
+
+    services.misskey.settings = lib.mkMerge [
+      (lib.mkIf cfg.database.createLocally {
+        db = {
+          db = lib.mkDefault "misskey";
+          # Use unix socket instead of localhost to allow PostgreSQL peer authentication,
+          # required for `services.postgresql.ensureUsers`
+          host = lib.mkDefault "/var/run/postgresql";
+          port = lib.mkDefault config.services.postgresql.settings.port;
+          user = lib.mkDefault "misskey";
+          pass = lib.mkDefault null;
+        };
+      })
+      (lib.mkIf (cfg.database.passwordFile != null) { db.pass = lib.mkDefault "@DATABASE_PASSWORD@"; })
+      (lib.mkIf cfg.redis.createLocally { redis.host = lib.mkDefault "localhost"; })
+      (lib.mkIf (cfg.redis.passwordFile != null) { redis.pass = lib.mkDefault "@REDIS_PASSWORD@"; })
+      (lib.mkIf cfg.meilisearch.createLocally {
+        meilisearch = {
+          host = lib.mkDefault "localhost";
+          port = lib.mkDefault config.services.meilisearch.listenPort;
+          ssl = lib.mkDefault false;
+        };
+      })
+      (lib.mkIf (cfg.meilisearch.keyFile != null) {
+        meilisearch.apiKey = lib.mkDefault "@MEILISEARCH_KEY@";
+      })
+      (lib.mkIf cfg.reverseProxy.enable {
+        url = lib.mkDefault "${
+          if cfg.reverseProxy.ssl then "https" else "http"
+        }://${cfg.reverseProxy.host}";
+      })
+    ];
+
+    systemd.services.misskey = {
+      after = [
+        "network-online.target"
+        "postgresql.service"
+      ];
+      wants = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+      environment = {
+        MISSKEY_CONFIG_YML = "/run/misskey/default.yml";
+      };
+      preStart =
+        ''
+          install -m 700 ${settingsFormat.generate "misskey-config.yml" cfg.settings} /run/misskey/default.yml
+        ''
+        + (lib.optionalString (cfg.database.passwordFile != null) ''
+          ${pkgs.replace-secret}/bin/replace-secret '@DATABASE_PASSWORD@' "${cfg.database.passwordFile}" /run/misskey/default.yml
+        '')
+        + (lib.optionalString (cfg.redis.passwordFile != null) ''
+          ${pkgs.replace-secret}/bin/replace-secret '@REDIS_PASSWORD@' "${cfg.redis.passwordFile}" /run/misskey/default.yml
+        '')
+        + (lib.optionalString (cfg.meilisearch.keyFile != null) ''
+          ${pkgs.replace-secret}/bin/replace-secret '@MEILISEARCH_KEY@' "${cfg.meilisearch.keyFile}" /run/misskey/default.yml
+        '');
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/misskey migrateandstart";
+        RuntimeDirectory = "misskey";
+        RuntimeDirectoryMode = "700";
+        StateDirectory = "misskey";
+        StateDirectoryMode = "700";
+        TimeoutSec = 60;
+        DynamicUser = true;
+        User = "misskey";
+        LockPersonality = true;
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectProc = "invisible";
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX AF_NETLINK";
+      };
+    };
+
+    services.postgresql = lib.mkIf cfg.database.createLocally {
+      enable = true;
+      ensureDatabases = [ "misskey" ];
+      ensureUsers = [
+        {
+          name = "misskey";
+          ensureDBOwnership = true;
+        }
+      ];
+    };
+
+    services.redis.servers = lib.mkIf cfg.redis.createLocally {
+      misskey = {
+        enable = true;
+        port = cfg.settings.redis.port;
+      };
+    };
+
+    services.meilisearch = lib.mkIf cfg.meilisearch.createLocally { enable = true; };
+
+    services.caddy = lib.mkIf (cfg.reverseProxy.enable && cfg.reverseProxy.webserver ? caddy) {
+      enable = true;
+      virtualHosts.${cfg.settings.url} = lib.mkMerge [
+        cfg.reverseProxy.webserver.caddy
+        {
+          hostName = lib.mkDefault cfg.settings.url;
+          extraConfig = ''
+            reverse_proxy localhost:${toString cfg.settings.port}
+          '';
+        }
+      ];
+    };
+
+    services.nginx = lib.mkIf (cfg.reverseProxy.enable && cfg.reverseProxy.webserver ? nginx) {
+      enable = true;
+      virtualHosts.${cfg.reverseProxy.host} = lib.mkMerge [
+        cfg.reverseProxy.webserver.nginx
+        {
+          locations."/" = {
+            proxyPass = lib.mkDefault "http://localhost:${toString cfg.settings.port}";
+            proxyWebsockets = lib.mkDefault true;
+            recommendedProxySettings = lib.mkDefault true;
+          };
+        }
+        (lib.mkIf (cfg.reverseProxy.ssl != null) { forceSSL = lib.mkDefault cfg.reverseProxy.ssl; })
+      ];
+    };
+  };
+
+  meta = {
+    maintainers = [ lib.maintainers.feathecutie ];
+  };
+}
diff --git a/nixos/modules/services/web-apps/movim.nix b/nixos/modules/services/web-apps/movim.nix
index 29bed0e067fa4..51c3156fef063 100644
--- a/nixos/modules/services/web-apps/movim.nix
+++ b/nixos/modules/services/web-apps/movim.nix
@@ -86,8 +86,8 @@ let
                   # `cfg.podConfig` to prevent confusing situtions where the
                   # values are rewritten on server reboot
                   ''
-                    substituteInPlace ${appDir}/app/widgets/AdminMain/adminmain.tpl \
-                      --replace-warn 'name="${k}"' 'name="${k}" disabled'
+                    substituteInPlace ${appDir}/app/Widgets/AdminMain/adminmain.tpl \
+                      --replace-warn 'name="${k}"' 'name="${k}" readonly'
                   '')
               [ ]
               cfg.podConfig));
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
index bfb3e73e65102..c8c4fe4b4d612 100644
--- a/nixos/modules/services/web-apps/nextcloud.nix
+++ b/nixos/modules/services/web-apps/nextcloud.nix
@@ -91,10 +91,10 @@ let
     cd ${webroot}
     sudo=exec
     if [[ "$USER" != nextcloud ]]; then
-      sudo='exec /run/wrappers/bin/sudo -u nextcloud --preserve-env=NEXTCLOUD_CONFIG_DIR --preserve-env=OC_PASS'
+      sudo='exec /run/wrappers/bin/sudo -u nextcloud'
     fi
-    export NEXTCLOUD_CONFIG_DIR="${datadir}/config"
-    $sudo \
+    $sudo ${pkgs.coreutils}/bin/env \
+      NEXTCLOUD_CONFIG_DIR="${datadir}/config" \
       ${phpCli} \
       occ "$@"
   '';
diff --git a/nixos/modules/services/web-apps/onlyoffice.nix b/nixos/modules/services/web-apps/onlyoffice.nix
index 545ca68ccaac2..0d0e01d4f7bc0 100644
--- a/nixos/modules/services/web-apps/onlyoffice.nix
+++ b/nixos/modules/services/web-apps/onlyoffice.nix
@@ -1,24 +1,22 @@
 { lib, config, pkgs, ... }:
 
-with lib;
-
 let
   cfg = config.services.onlyoffice;
 in
 {
   options.services.onlyoffice = {
-    enable = mkEnableOption "OnlyOffice DocumentServer";
+    enable = lib.mkEnableOption "OnlyOffice DocumentServer";
 
-    enableExampleServer = mkEnableOption "OnlyOffice example server";
+    enableExampleServer = lib.mkEnableOption "OnlyOffice example server";
 
-    hostname = mkOption {
-      type = types.str;
+    hostname = lib.mkOption {
+      type = lib.types.str;
       default = "localhost";
-      description = "FQDN for the onlyoffice instance.";
+      description = "FQDN for the OnlyOffice instance.";
     };
 
-    jwtSecretFile = mkOption {
-      type = types.nullOr types.str;
+    jwtSecretFile = lib.mkOption {
+      type = lib.types.nullOr lib.types.str;
       default = null;
       description = ''
         Path to a file that contains the secret to sign web requests using JSON Web Tokens.
@@ -26,34 +24,34 @@ in
       '';
     };
 
-    package = mkPackageOption pkgs "onlyoffice-documentserver" { };
+    package = lib.mkPackageOption pkgs "onlyoffice-documentserver" { };
 
-    port = mkOption {
-      type = types.port;
+    port = lib.mkOption {
+      type = lib.types.port;
       default = 8000;
-      description = "Port the OnlyOffice DocumentServer should listens on.";
+      description = "Port the OnlyOffice document server should listen on.";
     };
 
-    examplePort = mkOption {
-      type = types.port;
+    examplePort = lib.mkOption {
+      type = lib.types.port;
       default = null;
-      description = "Port the OnlyOffice Example server should listens on.";
+      description = "Port the OnlyOffice example server should listen on.";
     };
 
-    postgresHost = mkOption {
-      type = types.str;
+    postgresHost = lib.mkOption {
+      type = lib.types.str;
       default = "/run/postgresql";
       description = "The Postgresql hostname or socket path OnlyOffice should connect to.";
     };
 
-    postgresName = mkOption {
-      type = types.str;
+    postgresName = lib.mkOption {
+      type = lib.types.str;
       default = "onlyoffice";
-      description = "The name of database OnlyOffice should user.";
+      description = "The name of database OnlyOffice should use.";
     };
 
-    postgresPasswordFile = mkOption {
-      type = types.nullOr types.str;
+    postgresPasswordFile = lib.mkOption {
+      type = lib.types.nullOr lib.types.str;
       default = null;
       description = ''
         Path to a file that contains the password OnlyOffice should use to connect to Postgresql.
@@ -61,8 +59,8 @@ in
       '';
     };
 
-    postgresUser = mkOption {
-      type = types.str;
+    postgresUser = lib.mkOption {
+      type = lib.types.str;
       default = "onlyoffice";
       description = ''
         The username OnlyOffice should use to connect to Postgresql.
@@ -70,8 +68,8 @@ in
       '';
     };
 
-    rabbitmqUrl = mkOption {
-      type = types.str;
+    rabbitmqUrl = lib.mkOption {
+      type = lib.types.str;
       default = "amqp://guest:guest@localhost:5672";
       description = "The Rabbitmq in amqp URI style OnlyOffice should connect to.";
     };
@@ -80,10 +78,10 @@ in
   config = lib.mkIf cfg.enable {
     services = {
       nginx = {
-        enable = mkDefault true;
+        enable = lib.mkDefault true;
         # misses text/csv, font/ttf, application/x-font-ttf, application/rtf, application/wasm
-        recommendedGzipSettings = mkDefault true;
-        recommendedProxySettings = mkDefault true;
+        recommendedGzipSettings = lib.mkDefault true;
+        recommendedProxySettings = lib.mkDefault true;
 
         upstreams = {
           # /etc/nginx/includes/http-common.conf
diff --git a/nixos/modules/services/web-apps/pretix.nix b/nixos/modules/services/web-apps/pretix.nix
index 0fb635964fe65..d298caab5b862 100644
--- a/nixos/modules/services/web-apps/pretix.nix
+++ b/nixos/modules/services/web-apps/pretix.nix
@@ -249,7 +249,7 @@ in
             };
 
             host = mkOption {
-              type = with types; nullOr types.path;
+              type = with types; nullOr path;
               default = if cfg.settings.database.backend == "postgresql" then "/run/postgresql" else null;
               defaultText = literalExpression ''
                 if config.services.pretix.settings..database.backend == "postgresql" then "/run/postgresql"
@@ -535,9 +535,10 @@ in
           fi
         '';
         serviceConfig = {
-          TimeoutStartSec = "5min";
+          TimeoutStartSec = "15min";
           ExecStart = "${getExe' pythonEnv "gunicorn"} --bind unix:/run/pretix/pretix.sock ${cfg.gunicorn.extraArgs} pretix.wsgi";
           RuntimeDirectory = "pretix";
+          Restart = "on-failure";
         };
       };
 
@@ -559,7 +560,10 @@ in
           "postgresql.service"
         ];
         wantedBy = [ "multi-user.target" ];
-        serviceConfig.ExecStart = "${getExe' pythonEnv "celery"} -A pretix.celery_app worker ${cfg.celery.extraArgs}";
+        serviceConfig = {
+          ExecStart = "${getExe' pythonEnv "celery"} -A pretix.celery_app worker ${cfg.celery.extraArgs}";
+          Restart = "on-failure";
+        };
       };
 
       nginx.serviceConfig.SupplementaryGroups = mkIf cfg.nginx.enable [ "pretix" ];
diff --git a/nixos/modules/services/web-apps/screego.nix b/nixos/modules/services/web-apps/screego.nix
new file mode 100644
index 0000000000000..9258e63c5d479
--- /dev/null
+++ b/nixos/modules/services/web-apps/screego.nix
@@ -0,0 +1,96 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  inherit (lib) mkOption types mkIf;
+  cfg = config.services.screego;
+  defaultSettings = {
+    SCREEGO_SERVER_ADDRESS = "127.0.0.1:5050";
+    SCREEGO_TURN_ADDRESS = "0.0.0.0:3478";
+    SCREEGO_TURN_PORT_RANGE = "50000:55000";
+    SCREEGO_SESSION_TIMEOUT_SECONDS = "0";
+    SCREEGO_CLOSE_ROOM_WHEN_OWNER_LEAVES = "true";
+    SCREEGO_AUTH_MODE = "turn";
+    SCREEGO_LOG_LEVEL = "info";
+  };
+in
+{
+  meta.maintainers = with lib.maintainers; [ pinpox ];
+
+  options.services.screego = {
+
+    enable = lib.mkEnableOption "screego screen-sharing server for developers";
+
+    openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Open the firewall port(s).
+      '';
+    };
+
+    environmentFile = mkOption {
+      default = null;
+      description = ''
+        Environment file (see {manpage}`systemd.exec(5)` "EnvironmentFile="
+        section for the syntax) passed to the service. This option can be
+        used to safely include secrets in the configuration.
+      '';
+      example = "/run/secrets/screego-envfile";
+      type = with types; nullOr path;
+    };
+
+    settings = lib.mkOption {
+      type = types.attrsOf types.str;
+      description = ''
+        Screego settings passed as Nix attribute set, they will be merged with
+        the defaults. Settings will be passed as environment variables.
+
+        See https://screego.net/#/config for possible values
+      '';
+      default = defaultSettings;
+      example = {
+        SCREEGO_EXTERNAL_IP = "dns:example.com";
+      };
+    };
+  };
+
+  config =
+    let
+      # User-provided settings should be merged with default settings,
+      # overwriting where necessary
+      mergedConfig = defaultSettings // cfg.settings;
+      turnUDPPorts = lib.splitString ":" mergedConfig.SCREEGO_TURN_PORT_RANGE;
+      turnPort = lib.toInt (builtins.elemAt (lib.splitString ":" mergedConfig.SCREEGO_TURN_ADDRESS) 1);
+    in
+    mkIf (cfg.enable) {
+
+      networking.firewall = lib.mkIf cfg.openFirewall {
+        allowedTCPPorts = [ turnPort ];
+        allowedUDPPorts = [ turnPort ];
+        allowedUDPPortRanges = [
+          {
+            from = lib.toInt (builtins.elemAt turnUDPPorts 0);
+            to = lib.toInt (builtins.elemAt turnUDPPorts 1);
+          }
+        ];
+      };
+
+      systemd.services.screego = {
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+        description = "screego screen-sharing for developers";
+        environment = mergedConfig;
+        serviceConfig = {
+          DynamicUser = true;
+          ExecStart = "${lib.getExe pkgs.screego} serve";
+          Restart = "on-failure";
+          RestartSec = "5s";
+        } // lib.optionalAttrs (cfg.environmentFile != null) { EnvironmentFile = cfg.environmentFile; };
+      };
+    };
+}
diff --git a/nixos/modules/services/web-apps/sogo.nix b/nixos/modules/services/web-apps/sogo.nix
index 78b577f18f282..1102418077664 100644
--- a/nixos/modules/services/web-apps/sogo.nix
+++ b/nixos/modules/services/web-apps/sogo.nix
@@ -2,17 +2,13 @@
   cfg = config.services.sogo;
 
   preStart = pkgs.writeShellScriptBin "sogo-prestart" ''
-    touch /etc/sogo/sogo.conf
-    chown sogo:sogo /etc/sogo/sogo.conf
-    chmod 640 /etc/sogo/sogo.conf
-
     ${if (cfg.configReplaces != {}) then ''
       # Insert secrets
       ${concatStringsSep "\n" (mapAttrsToList (k: v: ''export ${k}="$(cat "${v}" | tr -d '\n')"'') cfg.configReplaces)}
 
-      ${pkgs.perl}/bin/perl -p ${concatStringsSep " " (mapAttrsToList (k: v: '' -e 's/${k}/''${ENV{"${k}"}}/g;' '') cfg.configReplaces)} /etc/sogo/sogo.conf.raw > /etc/sogo/sogo.conf
+      ${pkgs.perl}/bin/perl -p ${concatStringsSep " " (mapAttrsToList (k: v: '' -e 's/${k}/''${ENV{"${k}"}}/g;' '') cfg.configReplaces)} /etc/sogo/sogo.conf.raw | install -m 640 -o sogo -g sogo /dev/stdin /etc/sogo/sogo.conf
     '' else ''
-      cp /etc/sogo/sogo.conf.raw /etc/sogo/sogo.conf
+      install -m 640 -o sogo -g sogo /etc/sogo/sogo.conf.raw /etc/sogo/sogo.conf
     ''}
   '';
 
diff --git a/nixos/modules/services/web-apps/stirling-pdf.nix b/nixos/modules/services/web-apps/stirling-pdf.nix
new file mode 100644
index 0000000000000..cd8638aff14d0
--- /dev/null
+++ b/nixos/modules/services/web-apps/stirling-pdf.nix
@@ -0,0 +1,110 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  cfg = config.services.stirling-pdf;
+in
+{
+  options.services.stirling-pdf = {
+    enable = lib.mkEnableOption "the stirling-pdf service";
+
+    package = lib.mkPackageOption pkgs "stirling-pdf" { };
+
+    environment = lib.mkOption {
+      type = lib.types.attrsOf (
+        lib.types.oneOf [
+          lib.types.str
+          lib.types.int
+        ]
+      );
+      default = { };
+      example = {
+        SERVER_PORT = 8080;
+        INSTALL_BOOK_AND_ADVANCED_HTML_OPS = "true";
+      };
+      description = ''
+        Environment variables for the stirling-pdf app.
+        See https://github.com/Stirling-Tools/Stirling-PDF#customisation for available options.
+      '';
+    };
+
+    environmentFiles = lib.mkOption {
+      type = lib.types.listOf lib.types.path;
+      default = [ ];
+      description = ''
+        Files containing additional environment variables to pass to Stirling PDF.
+        Secrets should be added in environmentFiles instead of environment.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.stirling-pdf = {
+      environment = lib.mapAttrs (_: toString) cfg.environment;
+
+      # following https://github.com/Stirling-Tools/Stirling-PDF#locally
+      path = with pkgs; [
+        unpaper
+        libreoffice
+        ocrmypdf
+        poppler_utils
+        unoconv
+        opencv
+        pngquant
+        tesseract
+        python3Packages.weasyprint
+        calibre
+      ];
+
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        BindReadOnlyPaths = [ "${pkgs.tesseract}/share/tessdata:/usr/share/tessdata" ];
+        CacheDirectory = "stirling-pdf";
+        Environment = [ "HOME=%S/stirling-pdf" ];
+        EnvironmentFile = cfg.environmentFiles;
+        ExecStart = lib.getExe cfg.package;
+        RuntimeDirectory = "stirling-pdf";
+        StateDirectory = "stirling-pdf";
+        SuccessExitStatus = 143;
+        User = "stirling-pdf";
+        WorkingDirectory = "/var/lib/stirling-pdf";
+
+        # Hardening
+        CapabilityBoundingSet = "";
+        DynamicUser = true;
+        LockPersonality = true;
+        NoNewPrivileges = true;
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProcSubset = "pid";
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        RestrictAddressFamilies = [
+          "AF_INET"
+          "AF_INET6"
+          "AF_UNIX"
+        ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [
+          "~@cpu-emulation @debug @keyring @mount @obsolete @privileged @resources @clock @setuid @chown"
+        ];
+        UMask = "0077";
+      };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ DCsunset ];
+}
diff --git a/nixos/modules/services/web-apps/weblate.nix b/nixos/modules/services/web-apps/weblate.nix
new file mode 100644
index 0000000000000..398d634a2b928
--- /dev/null
+++ b/nixos/modules/services/web-apps/weblate.nix
@@ -0,0 +1,388 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  cfg = config.services.weblate;
+
+  dataDir = "/var/lib/weblate";
+  settingsDir = "${dataDir}/settings";
+
+  finalPackage = cfg.package.overridePythonAttrs (old: {
+    # We only support the PostgreSQL backend in this module
+    dependencies = old.dependencies ++ cfg.package.optional-dependencies.postgres;
+    # Use a settings module in dataDir, to avoid having to rebuild the package
+    # when user changes settings.
+    makeWrapperArgs = (old.makeWrapperArgs or [ ]) ++ [
+      "--set PYTHONPATH  \"${settingsDir}\""
+      "--set DJANGO_SETTINGS_MODULE \"settings\""
+    ];
+  });
+  inherit (finalPackage) python;
+
+  pythonEnv = python.buildEnv.override {
+    extraLibs = with python.pkgs; [
+      (toPythonModule finalPackage)
+      celery
+    ];
+  };
+
+  # This extends and overrides the weblate/settings_example.py code found in upstream.
+  weblateConfig =
+    ''
+      # This was autogenerated by the NixOS module.
+
+      SITE_TITLE = "Weblate"
+      SITE_DOMAIN = "${cfg.localDomain}"
+      # TLS terminates at the reverse proxy, but this setting controls how links to weblate are generated.
+      ENABLE_HTTPS = True
+      SESSION_COOKIE_SECURE = ENABLE_HTTPS
+      DATA_DIR = "${dataDir}"
+      CACHE_DIR = f"{DATA_DIR}/cache"
+      STATIC_ROOT = "${finalPackage.static}/static"
+      MEDIA_ROOT = "/var/lib/weblate/media"
+      COMPRESS_ROOT = "${finalPackage.static}/compressor-cache"
+      DEBUG = False
+
+      DATABASES = {
+        "default": {
+          "ENGINE": "django.db.backends.postgresql",
+          "HOST": "/run/postgresql",
+          "NAME": "weblate",
+          "USER": "weblate",
+        }
+      }
+
+      with open("${cfg.djangoSecretKeyFile}") as f:
+        SECRET_KEY = f.read().rstrip("\n")
+
+      CACHES = {
+        "default": {
+          "BACKEND": "django_redis.cache.RedisCache",
+          "LOCATION": "unix://${config.services.redis.servers.weblate.unixSocket}",
+          "OPTIONS": {
+              "CLIENT_CLASS": "django_redis.client.DefaultClient",
+              "PASSWORD": None,
+              "CONNECTION_POOL_KWARGS": {},
+          },
+          "KEY_PREFIX": "weblate",
+          "TIMEOUT": 3600,
+        },
+        "avatar": {
+          "BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
+          "LOCATION": "/var/lib/weblate/avatar-cache",
+          "TIMEOUT": 86400,
+          "OPTIONS": {"MAX_ENTRIES": 1000},
+        }
+      }
+
+
+      CELERY_TASK_ALWAYS_EAGER = False
+      CELERY_BROKER_URL = "redis+socket://${config.services.redis.servers.weblate.unixSocket}"
+      CELERY_RESULT_BACKEND = CELERY_BROKER_URL
+
+      VCS_BACKENDS = ("weblate.vcs.git.GitRepository",)
+
+    ''
+    + lib.optionalString cfg.smtp.enable ''
+      ADMINS = (("Weblate Admin", "${cfg.smtp.user}"),)
+
+      EMAIL_HOST = "${cfg.smtp.host}"
+      EMAIL_USE_TLS = True
+      EMAIL_HOST_USER = "${cfg.smtp.user}"
+      SERVER_EMAIL = "${cfg.smtp.user}"
+      DEFAULT_FROM_EMAIL = "${cfg.smtp.user}"
+      EMAIL_PORT = 587
+      with open("${cfg.smtp.passwordFile}") as f:
+        EMAIL_HOST_PASSWORD = f.read().rstrip("\n")
+
+    ''
+    + cfg.extraConfig;
+  settings_py =
+    pkgs.runCommand "weblate_settings.py"
+      {
+        inherit weblateConfig;
+        passAsFile = [ "weblateConfig" ];
+      }
+      ''
+        mkdir -p $out
+        cat \
+          ${finalPackage}/${python.sitePackages}/weblate/settings_example.py \
+          $weblateConfigPath \
+          > $out/settings.py
+      '';
+
+  environment = {
+    PYTHONPATH = "${settingsDir}:${pythonEnv}/${python.sitePackages}/";
+    DJANGO_SETTINGS_MODULE = "settings";
+    # We run Weblate through gunicorn, so we can't utilise the env var set in the wrapper.
+    inherit (finalPackage) GI_TYPELIB_PATH;
+  };
+
+  weblatePath = with pkgs; [
+    gitSVN
+
+    #optional
+    git-review
+    tesseract
+    licensee
+    mercurial
+  ];
+in
+{
+
+  options = {
+    services.weblate = {
+      enable = lib.mkEnableOption "Weblate service";
+
+      package = lib.mkPackageOption pkgs "weblate" { };
+
+      localDomain = lib.mkOption {
+        description = "The domain name serving your Weblate instance.";
+        example = "weblate.example.org";
+        type = lib.types.str;
+      };
+
+      djangoSecretKeyFile = lib.mkOption {
+        description = ''
+          Location of the Django secret key.
+
+          This should be a path pointing to a file with secure permissions (not /nix/store).
+
+          Can be generated with `weblate-generate-secret-key` which is available as the `weblate` user.
+        '';
+        type = lib.types.path;
+      };
+
+      extraConfig = lib.mkOption {
+        type = lib.types.lines;
+        default = "";
+        description = ''
+          Text to append to `settings.py` Weblate configuration file.
+        '';
+      };
+
+      smtp = {
+        enable = lib.mkEnableOption "Weblate SMTP support";
+        user = lib.mkOption {
+          description = "SMTP login name.";
+          example = "weblate@example.org";
+          type = lib.types.str;
+        };
+
+        host = lib.mkOption {
+          description = "SMTP host used when sending emails to users.";
+          type = lib.types.str;
+          example = "127.0.0.1";
+        };
+
+        passwordFile = lib.mkOption {
+          description = ''
+            Location of a file containing the SMTP password.
+
+            This should be a path pointing to a file with secure permissions (not /nix/store).
+          '';
+          type = lib.types.path;
+        };
+      };
+
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+
+    systemd.tmpfiles.rules = [ "L+ ${settingsDir} - - - - ${settings_py}" ];
+
+    services.nginx = {
+      enable = true;
+      virtualHosts."${cfg.localDomain}" = {
+
+        forceSSL = true;
+        enableACME = true;
+
+        locations = {
+          "= /favicon.ico".alias = "${finalPackage}/${python.sitePackages}/weblate/static/favicon.ico";
+          "/static/".alias = "${finalPackage.static}/static/";
+          "/static/CACHE/".alias = "${finalPackage.static}/compressor-cache/CACHE/";
+          "/media/".alias = "/var/lib/weblate/media/";
+          "/".proxyPass = "http://unix:///run/weblate.socket";
+        };
+
+      };
+    };
+
+    systemd.services.weblate-postgresql-setup = {
+      description = "Weblate PostgreSQL setup";
+      after = [ "postgresql.service" ];
+      serviceConfig = {
+        Type = "oneshot";
+        User = "postgres";
+        Group = "postgres";
+        ExecStart = ''
+          ${config.services.postgresql.package}/bin/psql weblate -c "CREATE EXTENSION IF NOT EXISTS pg_trgm"
+        '';
+      };
+    };
+
+    systemd.services.weblate-migrate = {
+      description = "Weblate migration";
+      after = [ "weblate-postgresql-setup.service" ];
+      requires = [ "weblate-postgresql-setup.service" ];
+      # We want this to be active on boot, not just on socket activation
+      wantedBy = [ "multi-user.target" ];
+      inherit environment;
+      path = weblatePath;
+      serviceConfig = {
+        Type = "oneshot";
+        StateDirectory = "weblate";
+        User = "weblate";
+        Group = "weblate";
+        ExecStart = "${finalPackage}/bin/weblate migrate --noinput";
+      };
+    };
+
+    systemd.services.weblate-celery = {
+      description = "Weblate Celery";
+      after = [
+        "network.target"
+        "redis.service"
+        "postgresql.service"
+      ];
+      # We want this to be active on boot, not just on socket activation
+      wantedBy = [ "multi-user.target" ];
+      environment = environment // {
+        CELERY_WORKER_RUNNING = "1";
+      };
+      path = weblatePath;
+      # Recommendations from:
+      # https://github.com/WeblateOrg/weblate/blob/main/weblate/examples/celery-weblate.service
+      serviceConfig =
+        let
+          # We have to push %n through systemd's replacement, therefore %%n.
+          pidFile = "/run/celery/weblate-%%n.pid";
+          nodes = "celery notify memory backup translate";
+          cmd = verb: ''
+            ${pythonEnv}/bin/celery multi ${verb} \
+              ${nodes} \
+              -A "weblate.utils" \
+              --pidfile=${pidFile} \
+              --logfile=/var/log/celery/weblate-%%n%%I.log \
+              --loglevel=DEBUG \
+              --beat:celery \
+              --queues:celery=celery \
+              --prefetch-multiplier:celery=4 \
+              --queues:notify=notify \
+              --prefetch-multiplier:notify=10 \
+              --queues:memory=memory \
+              --prefetch-multiplier:memory=10 \
+              --queues:translate=translate \
+              --prefetch-multiplier:translate=4 \
+              --concurrency:backup=1 \
+              --queues:backup=backup \
+              --prefetch-multiplier:backup=2
+          '';
+        in
+        {
+          Type = "forking";
+          User = "weblate";
+          Group = "weblate";
+          WorkingDirectory = "${finalPackage}/${python.sitePackages}/weblate/";
+          RuntimeDirectory = "celery";
+          RuntimeDirectoryPreserve = "restart";
+          LogsDirectory = "celery";
+          ExecStart = cmd "start";
+          ExecReload = cmd "restart";
+          ExecStop = ''
+            ${pythonEnv}/bin/celery multi stopwait \
+              ${nodes} \
+              --pidfile=${pidFile}
+          '';
+          Restart = "always";
+        };
+    };
+
+    systemd.services.weblate = {
+      description = "Weblate Gunicorn app";
+      after = [
+        "network.target"
+        "weblate-migrate.service"
+        "weblate-celery.service"
+      ];
+      requires = [
+        "weblate-migrate.service"
+        "weblate-celery.service"
+        "weblate.socket"
+      ];
+      inherit environment;
+      path = weblatePath;
+      serviceConfig = {
+        Type = "notify";
+        NotifyAccess = "all";
+        ExecStart =
+          let
+            gunicorn = python.pkgs.gunicorn.overridePythonAttrs (old: {
+              # Allows Gunicorn to set a meaningful process name
+              dependencies = (old.dependencies or [ ]) ++ old.optional-dependencies.setproctitle;
+            });
+          in
+          ''
+            ${gunicorn}/bin/gunicorn \
+              --name=weblate \
+              --bind='unix:///run/weblate.socket' \
+              weblate.wsgi
+          '';
+        ExecReload = "kill -s HUP $MAINPID";
+        KillMode = "mixed";
+        PrivateTmp = true;
+        WorkingDirectory = dataDir;
+        StateDirectory = "weblate";
+        RuntimeDirectory = "weblate";
+        User = "weblate";
+        Group = "weblate";
+      };
+    };
+
+    systemd.sockets.weblate = {
+      before = [ "nginx.service" ];
+      wantedBy = [ "sockets.target" ];
+      socketConfig = {
+        ListenStream = "/run/weblate.socket";
+        SocketUser = "weblate";
+        SocketGroup = "weblate";
+        SocketMode = "770";
+      };
+    };
+
+    services.redis.servers.weblate = {
+      enable = true;
+      user = "weblate";
+      unixSocket = "/run/redis-weblate/redis.sock";
+      unixSocketPerm = 770;
+    };
+
+    services.postgresql = {
+      enable = true;
+      ensureUsers = [
+        {
+          name = "weblate";
+          ensureDBOwnership = true;
+        }
+      ];
+      ensureDatabases = [ "weblate" ];
+    };
+
+    users.users.weblate = {
+      isSystemUser = true;
+      group = "weblate";
+      packages = [ finalPackage ] ++ weblatePath;
+    };
+
+    users.groups.weblate.members = [ config.services.nginx.user ];
+  };
+
+  meta.maintainers = with lib.maintainers; [ erictapen ];
+
+}
diff --git a/nixos/modules/services/web-apps/wordpress.nix b/nixos/modules/services/web-apps/wordpress.nix
index 0d49f2d92998c..37c39608edae1 100644
--- a/nixos/modules/services/web-apps/wordpress.nix
+++ b/nixos/modules/services/web-apps/wordpress.nix
@@ -70,7 +70,7 @@ let
         require_once(ABSPATH . 'wp-settings.php');
       ?>
     '';
-    checkPhase = "${pkgs.php81}/bin/php --syntax-check $target";
+    checkPhase = "${pkgs.php}/bin/php --syntax-check $target";
   };
 
   mkPhpValue = v: let
diff --git a/nixos/modules/services/web-apps/zabbix.nix b/nixos/modules/services/web-apps/zabbix.nix
index 2455e676e583a..3463148b30e0d 100644
--- a/nixos/modules/services/web-apps/zabbix.nix
+++ b/nixos/modules/services/web-apps/zabbix.nix
@@ -1,9 +1,31 @@
-{ config, lib, options, pkgs, ... }:
+{
+  config,
+  lib,
+  options,
+  pkgs,
+  ...
+}:
 
 let
 
-  inherit (lib) mkDefault mkEnableOption mkPackageOption mkForce mkIf mkMerge mkOption types;
-  inherit (lib) literalExpression mapAttrs optionalString versionAtLeast;
+  inherit (lib)
+    mkDefault
+    mkEnableOption
+    mkPackageOption
+    mkRenamedOptionModule
+    mkForce
+    mkIf
+    mkMerge
+    mkOption
+    types
+    ;
+  inherit (lib)
+    literalExpression
+    mapAttrs
+    optionalString
+    optionals
+    versionAtLeast
+    ;
 
   cfg = config.services.zabbixWeb;
   opt = options.services.zabbixWeb;
@@ -17,13 +39,25 @@ let
     <?php
     // Zabbix GUI configuration file.
     global $DB;
-    $DB['TYPE'] = '${ { mysql = "MYSQL"; pgsql = "POSTGRESQL"; oracle = "ORACLE"; }.${cfg.database.type} }';
+    $DB['TYPE'] = '${
+      {
+        mysql = "MYSQL";
+        pgsql = "POSTGRESQL";
+        oracle = "ORACLE";
+      }
+      .${cfg.database.type}
+    }';
     $DB['SERVER'] = '${cfg.database.host}';
     $DB['PORT'] = '${toString cfg.database.port}';
     $DB['DATABASE'] = '${cfg.database.name}';
     $DB['USER'] = '${cfg.database.user}';
     # NOTE: file_get_contents adds newline at the end of returned string
-    $DB['PASSWORD'] = ${if cfg.database.passwordFile != null then "trim(file_get_contents('${cfg.database.passwordFile}'), \"\\r\\n\")" else "''"};
+    $DB['PASSWORD'] = ${
+      if cfg.database.passwordFile != null then
+        "trim(file_get_contents('${cfg.database.passwordFile}'), \"\\r\\n\")"
+      else
+        "''"
+    };
     // Schema name. Used for IBM DB2 and PostgreSQL.
     $DB['SCHEMA'] = ''';
     $ZBX_SERVER = '${cfg.server.address}';
@@ -33,16 +67,33 @@ let
 
     ${cfg.extraConfig}
   '';
-
 in
 {
+  imports = [
+    (mkRenamedOptionModule
+      [
+        "services"
+        "zabbixWeb"
+        "virtualHost"
+      ]
+      [
+        "services"
+        "zabbixWeb"
+        "httpd"
+        "virtualHost"
+      ]
+    )
+  ];
   # interface
 
   options.services = {
     zabbixWeb = {
       enable = mkEnableOption "the Zabbix web interface";
 
-      package = mkPackageOption pkgs [ "zabbix" "web" ] { };
+      package = mkPackageOption pkgs [
+        "zabbix"
+        "web"
+      ] { };
 
       server = {
         port = mkOption {
@@ -60,7 +111,11 @@ in
 
       database = {
         type = mkOption {
-          type = types.enum [ "mysql" "pgsql" "oracle" ];
+          type = types.enum [
+            "mysql"
+            "pgsql"
+            "oracle"
+          ];
           example = "mysql";
           default = "pgsql";
           description = "Database engine to use.";
@@ -75,9 +130,12 @@ in
         port = mkOption {
           type = types.port;
           default =
-            if cfg.database.type == "mysql" then config.services.mysql.port
-            else if cfg.database.type == "pgsql" then config.services.postgresql.settings.port
-            else 1521;
+            if cfg.database.type == "mysql" then
+              config.services.mysql.port
+            else if cfg.database.type == "pgsql" then
+              config.services.postgresql.settings.port
+            else
+              1521;
           defaultText = literalExpression ''
             if config.${opt.database.type} == "mysql" then config.${options.services.mysql.port}
             else if config.${opt.database.type} == "pgsql" then config.services.postgresql.settings.port
@@ -116,7 +174,17 @@ in
         };
       };
 
-      virtualHost = mkOption {
+      frontend = mkOption {
+        type = types.enum [
+          "nginx"
+          "httpd"
+        ];
+        example = "nginx";
+        default = "httpd";
+        description = "Frontend server to use.";
+      };
+
+      httpd.virtualHost = mkOption {
         type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
         example = literalExpression ''
           {
@@ -126,14 +194,43 @@ in
             enableACME = true;
           }
         '';
+        default = { };
         description = ''
           Apache configuration can be done by adapting `services.httpd.virtualHosts.<name>`.
           See [](#opt-services.httpd.virtualHosts) for further information.
         '';
       };
 
+      hostname = mkOption {
+        type = types.str;
+        default = "zabbix.local";
+        description = "Hostname for either nginx or httpd.";
+      };
+
+      nginx.virtualHost = mkOption {
+        type = types.submodule (import ../web-servers/nginx/vhost-options.nix);
+        example = literalExpression ''
+          {
+            forceSSL = true;
+            sslCertificateKey = "/etc/ssl/zabbix.key";
+            sslCertificate = "/etc/ssl/zabbix.crt";
+          }
+        '';
+        default = { };
+        description = ''
+          Nginx configuration can be done by adapting `services.nginx.virtualHosts.<name>`.
+          See [](#opt-services.nginx.virtualHosts) for further information.
+        '';
+      };
+
       poolConfig = mkOption {
-        type = with types; attrsOf (oneOf [ str int bool ]);
+        type =
+          with types;
+          attrsOf (oneOf [
+            str
+            int
+            bool
+          ]);
         default = {
           "pm" = "dynamic";
           "pm.max_children" = 32;
@@ -154,7 +251,6 @@ in
           Additional configuration to be copied verbatim into {file}`zabbix.conf.php`.
         '';
       };
-
     };
   };
 
@@ -162,61 +258,96 @@ in
 
   config = mkIf cfg.enable {
 
-    services.zabbixWeb.extraConfig = optionalString ((versionAtLeast config.system.stateVersion "20.09") && (versionAtLeast cfg.package.version "5.0.0")) ''
-      $DB['DOUBLE_IEEE754'] = 'true';
-    '';
+    services.zabbixWeb.extraConfig =
+      optionalString
+        (
+          (versionAtLeast config.system.stateVersion "20.09") && (versionAtLeast cfg.package.version "5.0.0")
+        )
+        ''
+          $DB['DOUBLE_IEEE754'] = 'true';
+        '';
 
-    systemd.tmpfiles.rules = [
-      "d '${stateDir}' 0750 ${user} ${group} - -"
-      "d '${stateDir}/session' 0750 ${user} ${config.services.httpd.group} - -"
-    ];
+    systemd.tmpfiles.rules =
+      [ "d '${stateDir}' 0750 ${user} ${group} - -" ]
+      ++ optionals (cfg.frontend == "httpd") [
+        "d '${stateDir}/session' 0750 ${user} ${config.services.httpd.group} - -"
+      ]
+      ++ optionals (cfg.frontend == "nginx") [
+        "d '${stateDir}/session' 0750 ${user} ${config.services.nginx.group} - -"
+      ];
 
     services.phpfpm.pools.zabbix = {
       inherit user;
-      group = config.services.httpd.group;
-      phpOptions = ''
-        # https://www.zabbix.com/documentation/current/manual/installation/install
-        memory_limit = 128M
-        post_max_size = 16M
-        upload_max_filesize = 2M
-        max_execution_time = 300
-        max_input_time = 300
-        session.auto_start = 0
-        mbstring.func_overload = 0
-        always_populate_raw_post_data = -1
-        # https://bbs.archlinux.org/viewtopic.php?pid=1745214#p1745214
-        session.save_path = ${stateDir}/session
-      '' + optionalString (config.time.timeZone != null) ''
-        date.timezone = "${config.time.timeZone}"
-      '' + optionalString (cfg.database.type == "oracle") ''
-        extension=${pkgs.phpPackages.oci8}/lib/php/extensions/oci8.so
-      '';
+      group = config.services.${cfg.frontend}.group;
+      phpOptions =
+        ''
+          # https://www.zabbix.com/documentation/current/manual/installation/install
+          memory_limit = 128M
+          post_max_size = 16M
+          upload_max_filesize = 2M
+          max_execution_time = 300
+          max_input_time = 300
+          session.auto_start = 0
+          mbstring.func_overload = 0
+          always_populate_raw_post_data = -1
+          # https://bbs.archlinux.org/viewtopic.php?pid=1745214#p1745214
+          session.save_path = ${stateDir}/session
+        ''
+        + optionalString (config.time.timeZone != null) ''
+          date.timezone = "${config.time.timeZone}"
+        ''
+        + optionalString (cfg.database.type == "oracle") ''
+          extension=${pkgs.phpPackages.oci8}/lib/php/extensions/oci8.so
+        '';
       phpEnv.ZABBIX_CONFIG = "${zabbixConfig}";
       settings = {
-        "listen.owner" = config.services.httpd.user;
-        "listen.group" = config.services.httpd.group;
+        "listen.owner" =
+          if cfg.frontend == "httpd" then config.services.httpd.user else config.services.nginx.user;
+        "listen.group" =
+          if cfg.frontend == "httpd" then config.services.httpd.group else config.services.nginx.group;
       } // cfg.poolConfig;
     };
 
-    services.httpd = {
+    services.httpd = mkIf (cfg.frontend == "httpd") {
       enable = true;
-      adminAddr = mkDefault cfg.virtualHost.adminAddr;
+      adminAddr = mkDefault cfg.httpd.virtualHost.adminAddr;
       extraModules = [ "proxy_fcgi" ];
-      virtualHosts.${cfg.virtualHost.hostName} = mkMerge [ cfg.virtualHost {
-        documentRoot = mkForce "${cfg.package}/share/zabbix";
-        extraConfig = ''
-          <Directory "${cfg.package}/share/zabbix">
-            <FilesMatch "\.php$">
-              <If "-f %{REQUEST_FILENAME}">
-                SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
-              </If>
-            </FilesMatch>
-            AllowOverride all
-            Options -Indexes
-            DirectoryIndex index.php
-          </Directory>
-        '';
-      } ];
+      virtualHosts.${cfg.hostname} = mkMerge [
+        cfg.httpd.virtualHost
+        {
+          documentRoot = mkForce "${cfg.package}/share/zabbix";
+          extraConfig = ''
+            <Directory "${cfg.package}/share/zabbix">
+              <FilesMatch "\.php$">
+                <If "-f %{REQUEST_FILENAME}">
+                  SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
+                </If>
+              </FilesMatch>
+              AllowOverride all
+              Options -Indexes
+              DirectoryIndex index.php
+            </Directory>
+          '';
+        }
+      ];
+    };
+
+    services.nginx = mkIf (cfg.frontend == "nginx") {
+      enable = true;
+      virtualHosts.${cfg.hostname} = mkMerge [
+        cfg.nginx.virtualHost
+        {
+          root = mkForce "${cfg.package}/share/zabbix";
+          locations."/" = {
+            index = "index.html index.htm index.php";
+            tryFiles = "$uri $uri/ =404";
+          };
+          locations."~ \.php$".extraConfig = ''
+            fastcgi_pass  unix:${fpm.socket};
+            fastcgi_index index.php;
+          '';
+        }
+      ];
     };
 
     users.users.${user} = mapAttrs (name: mkDefault) {
@@ -225,9 +356,6 @@ in
       inherit group;
     };
 
-    users.groups.${group} = mapAttrs (name: mkDefault) {
-      gid = config.ids.gids.zabbix;
-    };
-
+    users.groups.${group} = mapAttrs (name: mkDefault) { gid = config.ids.gids.zabbix; };
   };
 }
diff --git a/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixos/modules/services/web-servers/apache-httpd/default.nix
index 5dd28a1db00ef..46cb099595794 100644
--- a/nixos/modules/services/web-servers/apache-httpd/default.nix
+++ b/nixos/modules/services/web-servers/apache-httpd/default.nix
@@ -538,25 +538,13 @@ in
         '';
       };
 
-      enableMellon = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Whether to enable the mod_auth_mellon module.";
-      };
+      enableMellon = mkEnableOption "the mod_auth_mellon module";
 
-      enablePHP = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Whether to enable the PHP module.";
-      };
+      enablePHP = mkEnableOption "the PHP module";
 
       phpPackage = mkPackageOption pkgs "php" { };
 
-      enablePerl = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Whether to enable the Perl module (mod_perl).";
-      };
+      enablePerl = mkEnableOption "the Perl module (mod_perl)";
 
       phpOptions = mkOption {
         type = types.lines;
diff --git a/nixos/modules/services/web-servers/caddy/default.nix b/nixos/modules/services/web-servers/caddy/default.nix
index 064a0c71b586b..59c0ab13de19b 100644
--- a/nixos/modules/services/web-servers/caddy/default.nix
+++ b/nixos/modules/services/web-servers/caddy/default.nix
@@ -84,12 +84,12 @@ in
       default = "caddy";
       type = types.str;
       description = ''
-        Group account under which caddy runs.
+        Group under which caddy runs.
 
         ::: {.note}
-        If left as the default value this user will automatically be created
+        If left as the default value this group will automatically be created
         on system activation, otherwise you are responsible for
-        ensuring the user exists before the Caddy service starts.
+        ensuring the group exists before the Caddy service starts.
         :::
       '';
     };
@@ -242,7 +242,7 @@ in
             serverAliases = [ "www.hydra.example.com" ];
             extraConfig = '''
               encode gzip
-              root /srv/http
+              root * /srv/http
             ''';
           };
         };
diff --git a/nixos/modules/services/web-servers/fcgiwrap.nix b/nixos/modules/services/web-servers/fcgiwrap.nix
index 29ddd39942c60..36a327b9ab9f9 100644
--- a/nixos/modules/services/web-servers/fcgiwrap.nix
+++ b/nixos/modules/services/web-servers/fcgiwrap.nix
@@ -3,12 +3,26 @@
 with lib;
 
 let
-  forEachInstance = f: flip mapAttrs' config.services.fcgiwrap (name: cfg:
-    nameValuePair "fcgiwrap-${name}" (f cfg)
+  forEachInstance = f: flip mapAttrs' config.services.fcgiwrap.instances (
+    name: cfg: nameValuePair "fcgiwrap-${name}" (f cfg)
   );
 
 in {
-  options.services.fcgiwrap = mkOption {
+  imports = forEach [
+    "enable"
+    "user"
+    "group"
+    "socketType"
+    "socketAddress"
+    "preforkProcesses"
+  ] (attr: mkRemovedOptionModule [ "services" "fcgiwrap" attr ] ''
+      The global shared fcgiwrap instance is no longer supported due to
+      security issues.
+      Isolated instances should instead be configured through
+      `services.fcgiwrap.instances.*'.
+  '');
+
+  options.services.fcgiwrap.instances = mkOption {
     description = "Configuration for fcgiwrap instances.";
     default = { };
     type = types.attrsOf (types.submodule ({ config, ... }: { options = {
@@ -54,7 +68,6 @@ in {
         default = null;
         description = ''
           User to be set as owner of the UNIX socket.
-          Defaults to the process running user.
         '';
       };
 
@@ -63,7 +76,6 @@ in {
         default = null;
         description = ''
           Group to be set as owner of the UNIX socket.
-          Defaults to the process running group.
         '';
       };
 
@@ -84,6 +96,14 @@ in {
   config = {
     assertions = concatLists (mapAttrsToList (name: cfg: [
       {
+        assertion = cfg.socket.type == "unix" -> cfg.socket.user != null;
+        message = "Socket owner is required for the UNIX socket type.";
+      }
+      {
+        assertion = cfg.socket.type == "unix" -> cfg.socket.group != null;
+        message = "Socket owner is required for the UNIX socket type.";
+      }
+      {
         assertion = cfg.socket.user != null -> cfg.socket.type == "unix";
         message = "Socket owner can only be set for the UNIX socket type.";
       }
@@ -95,7 +115,7 @@ in {
         assertion = cfg.socket.mode != null -> cfg.socket.type == "unix";
         message = "Socket mode can only be set for the UNIX socket type.";
       }
-    ]) config.services.fcgiwrap);
+    ]) config.services.fcgiwrap.instances);
 
     systemd.services = forEachInstance (cfg: {
       after = [ "nss-user-lookup.target" ];
diff --git a/nixos/modules/services/web-servers/hydron.nix b/nixos/modules/services/web-servers/hydron.nix
deleted file mode 100644
index 68c0859fc3322..0000000000000
--- a/nixos/modules/services/web-servers/hydron.nix
+++ /dev/null
@@ -1,164 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-let
-  cfg = config.services.hydron;
-in with lib; {
-  options.services.hydron = {
-    enable = mkEnableOption "hydron";
-
-    dataDir = mkOption {
-      type = types.path;
-      default = "/var/lib/hydron";
-      example = "/home/okina/hydron";
-      description = "Location where hydron runs and stores data.";
-    };
-
-    interval = mkOption {
-      type = types.str;
-      default = "weekly";
-      example = "06:00";
-      description = ''
-        How often we run hydron import and possibly fetch tags. Runs by default every week.
-
-        The format is described in
-        {manpage}`systemd.time(7)`.
-      '';
-    };
-
-    password = mkOption {
-      type = types.str;
-      default = "hydron";
-      example = "dumbpass";
-      description = "Password for the hydron database.";
-    };
-
-    passwordFile = mkOption {
-      type = types.path;
-      default = "/run/keys/hydron-password-file";
-      example = "/home/okina/hydron/keys/pass";
-      description = "Password file for the hydron database.";
-    };
-
-    postgresArgs = mkOption {
-      type = types.str;
-      description = "Postgresql connection arguments.";
-      example = ''
-        {
-          "driver": "postgres",
-          "connection": "user=hydron password=dumbpass dbname=hydron sslmode=disable"
-        }
-      '';
-    };
-
-    postgresArgsFile = mkOption {
-      type = types.path;
-      default = "/run/keys/hydron-postgres-args";
-      example = "/home/okina/hydron/keys/postgres";
-      description = "Postgresql connection arguments file.";
-    };
-
-    listenAddress = mkOption {
-      type = types.nullOr types.str;
-      default = null;
-      example = "127.0.0.1:8010";
-      description = "Listen on a specific IP address and port.";
-    };
-
-    importPaths = mkOption {
-      type = types.listOf types.path;
-      default = [];
-      example = [ "/home/okina/Pictures" ];
-      description = "Paths that hydron will recursively import.";
-    };
-
-    fetchTags = mkOption {
-      type = types.bool;
-      default = true;
-      description = "Fetch tags for imported images and webm from gelbooru.";
-    };
-  };
-
-  config = mkIf cfg.enable {
-    services.hydron.passwordFile = mkDefault (pkgs.writeText "hydron-password-file" cfg.password);
-    services.hydron.postgresArgsFile = mkDefault (pkgs.writeText "hydron-postgres-args" cfg.postgresArgs);
-    services.hydron.postgresArgs = mkDefault ''
-      {
-        "driver": "postgres",
-        "connection": "user=hydron password=${cfg.password} host=/run/postgresql dbname=hydron sslmode=disable"
-      }
-    '';
-
-    services.postgresql = {
-      enable = true;
-      ensureDatabases = [ "hydron" ];
-      ensureUsers = [
-        { name = "hydron";
-          ensureDBOwnership = true;
-        }
-      ];
-    };
-
-    systemd.tmpfiles.rules = [
-      "d '${cfg.dataDir}' 0750 hydron hydron - -"
-      "d '${cfg.dataDir}/.hydron' - hydron hydron - -"
-      "d '${cfg.dataDir}/images' - hydron hydron - -"
-      "Z '${cfg.dataDir}' - hydron hydron - -"
-
-      "L+ '${cfg.dataDir}/.hydron/db_conf.json' - - - - ${cfg.postgresArgsFile}"
-    ];
-
-    systemd.services.hydron = {
-      description = "hydron";
-      after = [ "network.target" "postgresql.service" ];
-      wantedBy = [ "multi-user.target" ];
-
-      serviceConfig = {
-        User = "hydron";
-        Group = "hydron";
-        ExecStart = "${pkgs.hydron}/bin/hydron serve"
-        + optionalString (cfg.listenAddress != null) " -a ${cfg.listenAddress}";
-      };
-    };
-
-    systemd.services.hydron-fetch = {
-      description = "Import paths into hydron and possibly fetch tags";
-
-      serviceConfig = {
-        Type = "oneshot";
-        User = "hydron";
-        Group = "hydron";
-        ExecStart = "${pkgs.hydron}/bin/hydron import "
-        + optionalString cfg.fetchTags "-f "
-        + (escapeShellArg cfg.dataDir) + "/images " + (escapeShellArgs cfg.importPaths);
-      };
-    };
-
-    systemd.timers.hydron-fetch = {
-      description = "Automatically import paths into hydron and possibly fetch tags";
-      after = [ "network.target" "hydron.service" ];
-      wantedBy = [ "timers.target" ];
-
-      timerConfig = {
-        Persistent = true;
-        OnCalendar = cfg.interval;
-      };
-    };
-
-    users = {
-      groups.hydron.gid = config.ids.gids.hydron;
-
-      users.hydron = {
-        description = "hydron server service user";
-        home = cfg.dataDir;
-        group = "hydron";
-        uid = config.ids.uids.hydron;
-      };
-    };
-  };
-
-  imports = [
-    (mkRenamedOptionModule [ "services" "hydron" "baseDir" ] [ "services" "hydron" "dataDir" ])
-  ];
-
-  meta.maintainers = with maintainers; [ Madouura ];
-}
diff --git a/nixos/modules/services/web-servers/nginx/gitweb.nix b/nixos/modules/services/web-servers/nginx/gitweb.nix
index 9242c1adbde16..81f794bf9b3f8 100644
--- a/nixos/modules/services/web-servers/nginx/gitweb.nix
+++ b/nixos/modules/services/web-servers/nginx/gitweb.nix
@@ -89,6 +89,6 @@ in
 
   };
 
-  meta.maintainers = with maintainers; [ ];
+  meta.maintainers = [ ];
 
 }
diff --git a/nixos/modules/services/x11/desktop-managers/budgie.nix b/nixos/modules/services/x11/desktop-managers/budgie.nix
index 7f43a939970b8..987f114ec5e18 100644
--- a/nixos/modules/services/x11/desktop-managers/budgie.nix
+++ b/nixos/modules/services/x11/desktop-managers/budgie.nix
@@ -8,7 +8,7 @@ let
   nixos-background-light = pkgs.nixos-artwork.wallpapers.nineish;
   nixos-background-dark = pkgs.nixos-artwork.wallpapers.nineish-dark-gray;
 
-  nixos-gsettings-overrides = pkgs.budgie.budgie-gsettings-overrides.override {
+  nixos-gsettings-overrides = pkgs.budgie-gsettings-overrides.override {
     inherit (cfg) extraGSettingsOverrides extraGSettingsOverridePackages;
     inherit nixos-background-dark nixos-background-light;
   };
@@ -40,7 +40,7 @@ let
     destination = "/share/gnome-background-properties/nixos.xml";
   };
 
-  budgie-control-center = pkgs.budgie.budgie-control-center.override {
+  budgie-control-center' = pkgs.budgie-control-center.override {
     enableSshSocket = config.services.openssh.startWhenNeeded;
   };
 
@@ -80,7 +80,7 @@ in {
         description = "Extra plugins for the Budgie desktop";
         type = types.listOf types.package;
         default = [];
-        example = literalExpression "[ pkgs.budgiePlugins.budgie-analogue-clock-applet ]";
+        example = literalExpression "[ pkgs.budgie-analogue-clock-applet ]";
       };
     };
 
@@ -94,7 +94,7 @@ in {
 
   config = mkIf cfg.enable {
     services.displayManager.sessionPackages = with pkgs; [
-      budgie.budgie-desktop
+      budgie-desktop
     ];
 
     services.xserver.displayManager.lightdm.greeters.slick = {
@@ -104,7 +104,7 @@ in {
       cursorTheme = mkDefault { name = "Qogir"; package = pkgs.qogir-icon-theme; };
     };
 
-    services.xserver.desktopManager.budgie.sessionPath = [ pkgs.budgie.budgie-desktop-view ];
+    services.xserver.desktopManager.budgie.sessionPath = [ pkgs.budgie-desktop-view ];
 
     environment.extraInit = ''
       ${concatMapStrings (p: ''
@@ -121,12 +121,12 @@ in {
     environment.systemPackages = with pkgs;
       [
         # Budgie Desktop.
-        budgie.budgie-backgrounds
-        budgie-control-center
-        (budgie.budgie-desktop-with-plugins.override { plugins = cfg.extraPlugins; })
-        budgie.budgie-desktop-view
-        budgie.budgie-screensaver
-        budgie.budgie-session
+        budgie-backgrounds
+        budgie-control-center'
+        (budgie-desktop-with-plugins.override { plugins = cfg.extraPlugins; })
+        budgie-desktop-view
+        budgie-screensaver
+        budgie-session
 
         # Required by Budgie Menu.
         gnome-menus
@@ -142,7 +142,7 @@ in {
       ]
       ++ lib.optional config.networking.networkmanager.enable pkgs.networkmanagerapplet
       ++ (utils.removePackagesByName [
-          cinnamon.nemo
+          nemo
           mate.eom
           mate.pluma
           mate.atril
@@ -210,7 +210,7 @@ in {
     xdg.portal.extraPortals = with pkgs; [
       xdg-desktop-portal-gtk # provides a XDG Portals implementation.
     ];
-    xdg.portal.configPackages = mkDefault [ pkgs.budgie.budgie-desktop ];
+    xdg.portal.configPackages = mkDefault [ pkgs.budgie-desktop ];
 
     services.geoclue2.enable = mkDefault true; # for BCC's Privacy > Location Services panel.
     services.upower.enable = config.powerManagement.enable; # for Budgie's Status Indicator and BCC's Power panel.
@@ -243,12 +243,12 @@ in {
 
     # Register packages for DBus.
     services.dbus.packages = [
-      budgie-control-center
+      budgie-control-center'
     ];
 
     # Register packages for udev.
     services.udev.packages = with pkgs; [
-      budgie.magpie
+      magpie
     ];
 
     # Shell integration for MATE Terminal.
diff --git a/nixos/modules/services/x11/desktop-managers/cinnamon.nix b/nixos/modules/services/x11/desktop-managers/cinnamon.nix
index 0dc21862d8348..70fa4f0f3a5a4 100644
--- a/nixos/modules/services/x11/desktop-managers/cinnamon.nix
+++ b/nixos/modules/services/x11/desktop-managers/cinnamon.nix
@@ -7,7 +7,7 @@ let
   cfg = config.services.xserver.desktopManager.cinnamon;
   serviceCfg = config.services.cinnamon;
 
-  nixos-gsettings-overrides = pkgs.cinnamon.cinnamon-gsettings-overrides.override {
+  nixos-gsettings-overrides = pkgs.cinnamon-gsettings-overrides.override {
     extraGSettingsOverridePackages = cfg.extraGSettingsOverridePackages;
     extraGSettingsOverrides = cfg.extraGSettingsOverrides;
   };
@@ -51,7 +51,7 @@ in
 
     environment.cinnamon.excludePackages = mkOption {
       default = [];
-      example = literalExpression "[ pkgs.cinnamon.blueberry ]";
+      example = literalExpression "[ pkgs.blueman ]";
       type = types.listOf types.package;
       description = "Which packages cinnamon should exclude from the default environment";
     };
@@ -60,23 +60,23 @@ in
 
   config = mkMerge [
     (mkIf cfg.enable {
-      services.displayManager.sessionPackages = [ pkgs.cinnamon.cinnamon-common ];
+      services.displayManager.sessionPackages = [ pkgs.cinnamon-common ];
 
       services.xserver.displayManager.lightdm.greeters.slick = {
         enable = mkDefault true;
 
         # Taken from mint-artwork.gschema.override
-        theme = mkIf (notExcluded pkgs.cinnamon.mint-themes) {
+        theme = mkIf (notExcluded pkgs.mint-themes) {
           name = mkDefault "Mint-Y-Aqua";
-          package = mkDefault pkgs.cinnamon.mint-themes;
+          package = mkDefault pkgs.mint-themes;
         };
-        iconTheme = mkIf (notExcluded pkgs.cinnamon.mint-y-icons) {
+        iconTheme = mkIf (notExcluded pkgs.mint-y-icons) {
           name = mkDefault "Mint-Y-Sand";
-          package = mkDefault pkgs.cinnamon.mint-y-icons;
+          package = mkDefault pkgs.mint-y-icons;
         };
-        cursorTheme = mkIf (notExcluded pkgs.cinnamon.mint-cursor-themes) {
+        cursorTheme = mkIf (notExcluded pkgs.mint-cursor-themes) {
           name = mkDefault "Bibata-Modern-Classic";
-          package = mkDefault pkgs.cinnamon.mint-cursor-themes;
+          package = mkDefault pkgs.mint-cursor-themes;
         };
       };
 
@@ -101,7 +101,7 @@ in
       security.polkit.enable = true;
       services.accounts-daemon.enable = true;
       services.system-config-printer.enable = (mkIf config.services.printing.enable (mkDefault true));
-      services.dbus.packages = with pkgs.cinnamon; [
+      services.dbus.packages = with pkgs; [
         cinnamon-common
         cinnamon-screensaver
         nemo-with-extensions
@@ -134,7 +134,7 @@ in
         cinnamon-screensaver = {};
       };
 
-      environment.systemPackages = with pkgs.cinnamon // pkgs; ([
+      environment.systemPackages = with pkgs; ([
         desktop-file-utils
 
         # common-files
@@ -200,7 +200,7 @@ in
         })
       ];
 
-      xdg.portal.configPackages = mkDefault [ pkgs.cinnamon.cinnamon-common ];
+      xdg.portal.configPackages = mkDefault [ pkgs.cinnamon-common ];
 
       # Override GSettings schemas
       environment.sessionVariables.NIX_GSETTINGS_OVERRIDES_DIR = "${nixos-gsettings-overrides}/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas";
@@ -224,7 +224,7 @@ in
       # Default Fonts
       fonts.packages = with pkgs; [
         dejavu_fonts # Default monospace font in LMDE 6+
-        ubuntu_font_family # required for default theme
+        ubuntu-classic # required for default theme
       ];
     })
 
@@ -233,7 +233,7 @@ in
       programs.gnome-terminal.enable = mkDefault (notExcluded pkgs.gnome-terminal);
       programs.file-roller.enable = mkDefault (notExcluded pkgs.file-roller);
 
-      environment.systemPackages = with pkgs // pkgs.cinnamon; utils.removePackagesByName [
+      environment.systemPackages = with pkgs; utils.removePackagesByName [
         # cinnamon team apps
         bulky
         warpinator
diff --git a/nixos/modules/services/x11/desktop-managers/enlightenment.nix b/nixos/modules/services/x11/desktop-managers/enlightenment.nix
index 0a341ba133d39..44c0dbc70fea8 100644
--- a/nixos/modules/services/x11/desktop-managers/enlightenment.nix
+++ b/nixos/modules/services/x11/desktop-managers/enlightenment.nix
@@ -92,7 +92,7 @@ in
 
     environment.etc."X11/xkb".source = xcfg.xkb.dir;
 
-    fonts.packages = [ pkgs.dejavu_fonts pkgs.ubuntu_font_family ];
+    fonts.packages = [ pkgs.dejavu_fonts ];
 
     services.udisks2.enable = true;
     services.upower.enable = config.powerManagement.enable;
diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix
index 53d3b91bfa17c..0092a1f3693b9 100644
--- a/nixos/modules/services/x11/desktop-managers/plasma5.nix
+++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix
@@ -374,6 +374,8 @@ in
         };
       };
 
+      xdg.icons.enable = true;
+
       xdg.portal.enable = true;
       xdg.portal.extraPortals = [ pkgs.plasma5Packages.xdg-desktop-portal-kde ];
       xdg.portal.configPackages = mkDefault [ pkgs.plasma5Packages.xdg-desktop-portal-kde ];
diff --git a/nixos/modules/services/x11/display-managers/gdm.nix b/nixos/modules/services/x11/display-managers/gdm.nix
index 1a39b365db5f3..6a915a3f2f095 100644
--- a/nixos/modules/services/x11/display-managers/gdm.nix
+++ b/nixos/modules/services/x11/display-managers/gdm.nix
@@ -310,8 +310,11 @@ in
 
       gdm-autologin.text = ''
         auth      requisite     pam_nologin.so
-
         auth      required      pam_succeed_if.so uid >= 1000 quiet
+        ${lib.optionalString pamCfg.login.enableGnomeKeyring ''
+          auth       [success=ok default=1]      ${pkgs.gnome.gdm}/lib/security/pam_gdm.so
+          auth       optional                    ${pkgs.gnome-keyring}/lib/security/pam_gnome_keyring.so
+        ''}
         auth      required      pam_permit.so
 
         account   sufficient    pam_unix.so
diff --git a/nixos/modules/services/x11/display-managers/lightdm-greeters/slick.nix b/nixos/modules/services/x11/display-managers/lightdm-greeters/slick.nix
index d20b26491aee1..4758b8d94eda2 100644
--- a/nixos/modules/services/x11/display-managers/lightdm-greeters/slick.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm-greeters/slick.nix
@@ -71,8 +71,8 @@ in
       font = {
         package = mkOption {
           type = types.package;
-          default = pkgs.ubuntu_font_family;
-          defaultText = literalExpression "pkgs.ubuntu_font_family";
+          default = pkgs.ubuntu-classic;
+          defaultText = literalExpression "pkgs.ubuntu-classic";
           description = ''
             The package path that contains the font given in the name option.
           '';
diff --git a/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix
index 57e83399eded6..5cec0ee0f1d51 100644
--- a/nixos/modules/services/x11/xserver.nix
+++ b/nixos/modules/services/x11/xserver.nix
@@ -648,7 +648,8 @@ in
                     || dmConf.xpra.enable
                     || dmConf.sx.enable
                     || dmConf.startx.enable
-                    || config.services.greetd.enable);
+                    || config.services.greetd.enable
+                    || config.services.displayManager.ly.enable);
       in mkIf (default) (mkDefault true);
 
     services.xserver.videoDrivers = mkIf (cfg.videoDriver != null) [ cfg.videoDriver ];