about summary refs log tree commit diff
path: root/nixos/modules/services/misc
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/services/misc')
-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.nix44
-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.nix225
-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/renovate.nix1
-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.nix17
28 files changed, 1260 insertions, 323 deletions
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 7b96a182f0d94..9fd6014f2c71c 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -9,10 +9,12 @@ 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
-                        pkgs.postgresql_13;
+                        pkgs.postgresql_14;
 
   gitlabSocket = "${cfg.statePath}/tmp/sockets/gitlab.socket";
   gitalySocket = "${cfg.statePath}/tmp/sockets/gitaly.socket";
@@ -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";
@@ -1119,12 +1120,12 @@ in {
         message = "services.gitlab.secrets.jwsFile must be set!";
       }
       {
-        assertion = versionAtLeast postgresqlPackage.version "13.6.0";
-        message = "PostgreSQL >=13.6 is required to run GitLab 16. Follow the instructions in the manual section for upgrading PostgreSQL here: https://nixos.org/manual/nixos/stable/index.html#module-services-postgres-upgrading";
+        assertion = versionAtLeast postgresqlPackage.version "14.9";
+        message = "PostgreSQL >= 14.9 is required to run GitLab 17. Follow the instructions in the manual section for upgrading PostgreSQL here: https://nixos.org/manual/nixos/stable/index.html#module-services-postgres-upgrading";
       }
     ];
 
-    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.";
@@ -1282,6 +1283,7 @@ in {
       "d ${gitlabConfig.production.shared.path}/registry 0750 ${cfg.user} ${cfg.group} -"
       "d ${gitlabConfig.production.shared.path}/terraform_state 0750 ${cfg.user} ${cfg.group} -"
       "d ${gitlabConfig.production.shared.path}/ci_secure_files 0750 ${cfg.user} ${cfg.group} -"
+      "d ${gitlabConfig.production.shared.path}/external-diffs 0750 ${cfg.user} ${cfg.group} -"
       "L+ /run/gitlab/config - - - - ${cfg.statePath}/config"
       "L+ /run/gitlab/log - - - - ${cfg.statePath}/log"
       "L+ /run/gitlab/tmp - - - - ${cfg.statePath}/tmp"
@@ -1294,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;
@@ -1337,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
@@ -1457,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
@@ -1472,7 +1472,7 @@ in {
         gzip
 
         procps # Sidekiq MemoryKiller
-      ];
+      ]);
       serviceConfig = {
         Type = "simple";
         User = cfg.user;
@@ -1499,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;
@@ -1581,7 +1580,7 @@ in {
       after = [ "network.target" ];
       wantedBy = [ "gitlab.target" ];
       partOf = [ "gitlab.target" ];
-      path = with pkgs; [
+      path = [ git ] ++ (with pkgs; [
         remarshal
         exiftool
         git
@@ -1589,7 +1588,7 @@ in {
         gzip
         openssh
         cfg.packages.gitlab-workhorse
-      ];
+      ]);
       serviceConfig = {
         Type = "simple";
         User = cfg.user;
@@ -1657,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 1467c3f93bc85..f8dbfe9c5692d 100644
--- a/nixos/modules/services/misc/ollama.nix
+++ b/nixos/modules/services/misc/ollama.nix
@@ -1,78 +1,91 @@
-{ config, lib, pkgs, ... }:
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
 let
-  inherit (lib) types;
+  inherit (lib) literalExpression types mkBefore;
 
   cfg = config.services.ollama;
-  ollamaPackage = cfg.package.override {
-    inherit (cfg) acceleration;
-    linuxPackages = config.boot.kernelPackages // {
-      nvidia_x11 = config.hardware.nvidia.package;
-    };
-  };
+  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.
         '';
@@ -86,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 = ''
@@ -132,6 +151,14 @@ in
           Since `ollama run` is mostly a shell around the ollama server, this is usually sufficient.
         '';
       };
+      loadModels = lib.mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        description = ''
+          The models to download as soon as the service starts.
+          Search for models of your choice from: https://ollama.com/library
+        '';
+      };
       openFirewall = lib.mkOption {
         type = types.bool;
         default = false;
@@ -144,23 +171,98 @@ 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}
+        for model in ${lib.escapeShellArgs cfg.loadModels}
+        do
+          ${lib.escapeShellArg (lib.getExe ollamaPackage)} pull "$model"
+        done
+      '';
     };
 
     networking.firewall = lib.mkIf cfg.openFirewall { allowedTCPPorts = [ cfg.port ]; };
@@ -168,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/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/renovate.nix b/nixos/modules/services/misc/renovate.nix
index 25a719c91cbd8..9062b7424b681 100644
--- a/nixos/modules/services/misc/renovate.nix
+++ b/nixos/modules/services/misc/renovate.nix
@@ -128,6 +128,7 @@ in
         RestrictAddressFamilies = [
           "AF_INET"
           "AF_INET6"
+          "AF_UNIX"
         ];
         RestrictNamespaces = true;
         RestrictRealtime = true;
diff --git a/nixos/modules/services/misc/rkvm.nix b/nixos/modules/services/misc/rkvm.nix
index 9d41669e00f61..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; [ ckie ];
+  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 d09cd87febfff..5b0b1448f6856 100644
--- a/nixos/modules/services/misc/zoneminder.nix
+++ b/nixos/modules/services/misc/zoneminder.nix
@@ -202,10 +202,11 @@ in {
     ];
 
     services = {
-      fcgiwrap = lib.mkIf useNginx {
-        enable = true;
-        preforkProcesses = cfg.cameras;
-        inherit user group;
+      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 {
@@ -225,9 +226,7 @@ in {
             default = true;
             root = "${pkg}/share/zoneminder/www";
             listen = [ { addr = "0.0.0.0"; inherit (cfg) port; } ];
-            extraConfig = let
-              fcgi = config.services.fcgiwrap;
-            in ''
+            extraConfig = ''
               index index.php;
 
               location / {
@@ -257,7 +256,7 @@ in {
                   fastcgi_param HTTP_PROXY "";
                   fastcgi_intercept_errors on;
 
-                  fastcgi_pass ${fcgi.socketType}:${fcgi.socketAddress};
+                  fastcgi_pass unix:${config.services.fcgiwrap.instances.zoneminder.socket.address};
                 }
 
                 location /cache/ {
@@ -374,5 +373,5 @@ in {
     };
   };
 
-  meta.maintainers = with lib.maintainers; [ ];
+  meta.maintainers = [ ];
 }