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/backup/restic.nix7
-rw-r--r--nixos/modules/services/cluster/hadoop/hbase.nix228
-rw-r--r--nixos/modules/services/cluster/kubernetes/addons/dns.nix11
-rw-r--r--nixos/modules/services/cluster/kubernetes/pki.nix2
-rw-r--r--nixos/modules/services/continuous-integration/woodpecker/agents.nix144
-rw-r--r--nixos/modules/services/continuous-integration/woodpecker/server.nix98
-rw-r--r--nixos/modules/services/databases/dgraph.nix2
-rw-r--r--nixos/modules/services/desktops/pipewire/daemon/client-rt.conf.json54
-rw-r--r--nixos/modules/services/desktops/pipewire/daemon/client.conf.json31
-rw-r--r--nixos/modules/services/desktops/pipewire/daemon/filter-chain.conf.json28
-rw-r--r--nixos/modules/services/desktops/pipewire/daemon/jack.conf.json75
-rw-r--r--nixos/modules/services/desktops/pipewire/daemon/minimal.conf.json120
-rw-r--r--nixos/modules/services/desktops/pipewire/daemon/pipewire-aes67.conf.json38
-rw-r--r--nixos/modules/services/desktops/pipewire/daemon/pipewire-avb.conf.json38
-rw-r--r--nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json106
-rw-r--r--nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json110
-rw-r--r--nixos/modules/services/desktops/pipewire/media-session/alsa-monitor.conf.json34
-rw-r--r--nixos/modules/services/desktops/pipewire/media-session/bluez-monitor.conf.json36
-rw-r--r--nixos/modules/services/desktops/pipewire/media-session/media-session.conf.json68
-rw-r--r--nixos/modules/services/desktops/pipewire/media-session/v4l2-monitor.conf.json30
-rw-r--r--nixos/modules/services/desktops/pipewire/pipewire-media-session.nix141
-rw-r--r--nixos/modules/services/desktops/pipewire/pipewire.nix111
-rw-r--r--nixos/modules/services/desktops/pipewire/wireplumber.nix4
-rw-r--r--nixos/modules/services/hardware/kanata.nix4
-rw-r--r--nixos/modules/services/hardware/keyd.nix112
-rw-r--r--nixos/modules/services/hardware/supergfxd.nix1
-rw-r--r--nixos/modules/services/hardware/undervolt.nix4
-rw-r--r--nixos/modules/services/home-automation/home-assistant.nix2
-rw-r--r--nixos/modules/services/logging/logrotate.nix2
-rw-r--r--nixos/modules/services/mail/roundcube.nix2
-rw-r--r--nixos/modules/services/matrix/synapse.md5
-rw-r--r--nixos/modules/services/matrix/synapse.nix4
-rw-r--r--nixos/modules/services/misc/gitea.nix2
-rw-r--r--nixos/modules/services/misc/gitlab.nix2
-rw-r--r--nixos/modules/services/misc/portunus.nix2
-rw-r--r--nixos/modules/services/misc/sssd.nix5
-rw-r--r--nixos/modules/services/misc/zoneminder.nix12
-rw-r--r--nixos/modules/services/monitoring/grafana.nix2
-rw-r--r--nixos/modules/services/monitoring/prometheus/alertmanager-irc-relay.nix107
-rw-r--r--nixos/modules/services/monitoring/prometheus/default.nix2
-rw-r--r--nixos/modules/services/network-filesystems/kubo.nix4
-rw-r--r--nixos/modules/services/networking/avahi-daemon.nix36
-rw-r--r--nixos/modules/services/networking/firewall-nftables.nix16
-rw-r--r--nixos/modules/services/networking/headscale.nix9
-rw-r--r--nixos/modules/services/networking/jicofo.nix48
-rw-r--r--nixos/modules/services/networking/multipath.nix8
-rw-r--r--nixos/modules/services/networking/peroxide.nix131
-rw-r--r--nixos/modules/services/networking/radicale.nix6
-rw-r--r--nixos/modules/services/networking/ssh/sshd.nix2
-rw-r--r--nixos/modules/services/networking/yggdrasil.nix200
-rw-r--r--nixos/modules/services/search/solr.nix110
-rw-r--r--nixos/modules/services/security/authelia.nix401
-rw-r--r--nixos/modules/services/security/fail2ban.nix12
-rw-r--r--nixos/modules/services/system/cachix-watch-store.nix2
-rw-r--r--nixos/modules/services/system/self-deploy.nix2
-rw-r--r--nixos/modules/services/web-apps/baget.nix170
-rw-r--r--nixos/modules/services/web-apps/dolibarr.nix2
-rw-r--r--nixos/modules/services/web-apps/jitsi-meet.nix9
-rw-r--r--nixos/modules/services/web-apps/limesurvey.nix33
-rw-r--r--nixos/modules/services/web-apps/mattermost.nix17
-rw-r--r--nixos/modules/services/web-apps/nextcloud.md2
-rw-r--r--nixos/modules/services/web-apps/nextcloud.nix74
-rw-r--r--nixos/modules/services/web-apps/writefreely.nix11
-rw-r--r--nixos/modules/services/web-servers/garage.nix2
-rw-r--r--nixos/modules/services/web-servers/minio.nix79
-rw-r--r--nixos/modules/services/web-servers/nginx/default.nix23
-rw-r--r--nixos/modules/services/x11/desktop-managers/plasma5.nix157
-rw-r--r--nixos/modules/services/x11/display-managers/gdm.nix2
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm.nix2
-rw-r--r--nixos/modules/services/x11/gdk-pixbuf.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/qtile.nix46
-rw-r--r--nixos/modules/services/x11/xserver.nix40
72 files changed, 1711 insertions, 1733 deletions
diff --git a/nixos/modules/services/backup/restic.nix b/nixos/modules/services/backup/restic.nix
index bc24e13aa050e..ca796cf7797e6 100644
--- a/nixos/modules/services/backup/restic.nix
+++ b/nixos/modules/services/backup/restic.nix
@@ -303,8 +303,8 @@ in
               then if (backup.paths != null) then concatStringsSep " " backup.paths else ""
               else "--files-from ${filesFromTmpFile}";
             pruneCmd = optionals (builtins.length backup.pruneOpts > 0) [
-              (resticCmd + " forget --prune --cache-dir=%C/restic-backups-${name} " + (concatStringsSep " " backup.pruneOpts))
-              (resticCmd + " check --cache-dir=%C/restic-backups-${name} " + (concatStringsSep " " backup.checkOpts))
+              (resticCmd + " forget --prune " + (concatStringsSep " " backup.pruneOpts))
+              (resticCmd + " check " + (concatStringsSep " " backup.checkOpts))
             ];
             # Helper functions for rclone remotes
             rcloneRemoteName = builtins.elemAt (splitString ":" backup.repository) 1;
@@ -314,6 +314,7 @@ in
           in
           nameValuePair "restic-backups-${name}" ({
             environment = {
+              RESTIC_CACHE_DIR = "%C/restic-backups-${name}";
               RESTIC_PASSWORD_FILE = backup.passwordFile;
               RESTIC_REPOSITORY = backup.repository;
               RESTIC_REPOSITORY_FILE = backup.repositoryFile;
@@ -332,7 +333,7 @@ in
             restartIfChanged = false;
             serviceConfig = {
               Type = "oneshot";
-              ExecStart = (optionals (backupPaths != "") [ "${resticCmd} backup --cache-dir=%C/restic-backups-${name} ${concatStringsSep " " (backup.extraBackupArgs ++ excludeFlags)} ${backupPaths}" ])
+              ExecStart = (optionals (backupPaths != "") [ "${resticCmd} backup ${concatStringsSep " " (backup.extraBackupArgs ++ excludeFlags)} ${backupPaths}" ])
                 ++ pruneCmd;
               User = backup.user;
               RuntimeDirectory = "restic-backups-${name}";
diff --git a/nixos/modules/services/cluster/hadoop/hbase.nix b/nixos/modules/services/cluster/hadoop/hbase.nix
index 97951ebfe3343..a39da2a84ecad 100644
--- a/nixos/modules/services/cluster/hadoop/hbase.nix
+++ b/nixos/modules/services/cluster/hadoop/hbase.nix
@@ -5,11 +5,95 @@ let
   cfg = config.services.hadoop;
   hadoopConf = "${import ./conf.nix { inherit cfg pkgs lib; }}/";
   mkIfNotNull = x: mkIf (x != null) x;
+  # generic hbase role options
+  hbaseRoleOption = name: extraOpts: {
+    enable = mkEnableOption (mdDoc "HBase ${name}");
+
+    openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = mdDoc "Open firewall ports for HBase ${name}.";
+    };
+
+    restartIfChanged = mkOption {
+      type = types.bool;
+      default = false;
+      description = mdDoc "Restart ${name} con config change.";
+    };
+
+    extraFlags = mkOption {
+      type = with types; listOf str;
+      default = [];
+      example = literalExpression ''[ "--backup" ]'';
+      description = mdDoc "Extra flags for the ${name} service.";
+    };
+
+    environment = mkOption {
+      type = with types; attrsOf str;
+      default = {};
+      example = literalExpression ''
+        {
+          HBASE_MASTER_OPTS = "-Dcom.sun.management.jmxremote.ssl=true";
+        }
+      '';
+      description = mdDoc "Environment variables passed to ${name}.";
+    };
+  } // extraOpts;
+  # generic hbase role configs
+  hbaseRoleConfig = name: ports: (mkIf cfg.hbase."${name}".enable {
+    services.hadoop.gatewayRole = {
+      enable = true;
+      enableHbaseCli = mkDefault true;
+    };
+
+    systemd.services."hbase-${toLower name}" = {
+      description = "HBase ${name}";
+      wantedBy = [ "multi-user.target" ];
+      path = with cfg; [ hbase.package ] ++ optional
+        (with cfg.hbase.master; enable && initHDFS) package;
+      preStart = mkIf (with cfg.hbase.master; enable && initHDFS)
+        (concatStringsSep "\n" (
+          map (x: "HADOOP_USER_NAME=hdfs hdfs --config /etc/hadoop-conf ${x}")[
+            "dfsadmin -safemode wait"
+            "dfs -mkdir -p ${cfg.hbase.rootdir}"
+            "dfs -chown hbase ${cfg.hbase.rootdir}"
+          ]
+        ));
+
+      inherit (cfg.hbase."${name}") environment;
+      script = concatStringsSep " " (
+        [
+          "hbase --config /etc/hadoop-conf/"
+          "${toLower name} start"
+        ]
+        ++ cfg.hbase."${name}".extraFlags
+        ++ map (x: "--${toLower x} ${toString cfg.hbase.${name}.${x}}")
+          (filter (x: hasAttr x cfg.hbase.${name}) ["port" "infoPort"])
+      );
+
+      serviceConfig = {
+        User = "hbase";
+        SyslogIdentifier = "hbase-${toLower name}";
+        Restart = "always";
+      };
+    };
+
+    services.hadoop.hbaseSiteInternal."hbase.rootdir" = cfg.hbase.rootdir;
+
+    networking = {
+      firewall.allowedTCPPorts = mkIf cfg.hbase."${name}".openFirewall ports;
+      hosts = mkIf (with cfg.hbase.regionServer; enable && overrideHosts) {
+        "127.0.0.2" = mkForce [ ];
+        "::1" = mkForce [ ];
+      };
+    };
+
+  });
 in
 {
   options.services.hadoop = {
 
-    gatewayRole.enableHbaseCli = mkEnableOption (lib.mdDoc "HBase CLI tools");
+    gatewayRole.enableHbaseCli = mkEnableOption (mdDoc "HBase CLI tools");
 
     hbaseSiteDefault = mkOption {
       default = {
@@ -21,7 +105,7 @@ in
         "hbase.cluster.distributed" = "true";
       };
       type = types.attrsOf types.anything;
-      description = lib.mdDoc ''
+      description = mdDoc ''
         Default options for hbase-site.xml
       '';
     };
@@ -29,8 +113,12 @@ in
       default = {};
       type = with types; attrsOf anything;
       example = literalExpression ''
+        {
+          "hbase.hregion.max.filesize" = 20*1024*1024*1024;
+          "hbase.table.normalization.enabled" = "true";
+        }
       '';
-      description = lib.mdDoc ''
+      description = mdDoc ''
         Additional options and overrides for hbase-site.xml
         <https://github.com/apache/hbase/blob/rel/2.4.11/hbase-common/src/main/resources/hbase-default.xml>
       '';
@@ -39,7 +127,7 @@ in
       default = {};
       type = with types; attrsOf anything;
       internal = true;
-      description = lib.mdDoc ''
+      description = mdDoc ''
         Internal option to add configs to hbase-site.xml based on module options
       '';
     };
@@ -50,11 +138,11 @@ in
         type = types.package;
         default = pkgs.hbase;
         defaultText = literalExpression "pkgs.hbase";
-        description = lib.mdDoc "HBase package";
+        description = mdDoc "HBase package";
       };
 
       rootdir = mkOption {
-        description = lib.mdDoc ''
+        description = mdDoc ''
           This option will set "hbase.rootdir" in hbase-site.xml and determine
           the directory shared by region servers and into which HBase persists.
           The URL should be 'fully-qualified' to include the filesystem scheme.
@@ -68,7 +156,7 @@ in
         default = "/hbase";
       };
       zookeeperQuorum = mkOption {
-        description = lib.mdDoc ''
+        description = mdDoc ''
           This option will set "hbase.zookeeper.quorum" in hbase-site.xml.
           Comma separated list of servers in the ZooKeeper ensemble.
         '';
@@ -76,107 +164,36 @@ in
         example = "zk1.internal,zk2.internal,zk3.internal";
         default = null;
       };
-      master = {
-        enable = mkEnableOption (lib.mdDoc "HBase Master");
-        initHDFS = mkEnableOption (lib.mdDoc "initialization of the hbase directory on HDFS");
-
-        openFirewall = mkOption {
-          type = types.bool;
-          default = false;
-          description = lib.mdDoc ''
-            Open firewall ports for HBase master.
-          '';
+    } // (let
+      ports = port: infoPort: {
+        port = mkOption {
+          type = types.int;
+          default = port;
+          description = mdDoc "RPC port";
         };
-      };
-      regionServer = {
-        enable = mkEnableOption (lib.mdDoc "HBase RegionServer");
-
-        overrideHosts = mkOption {
-          type = types.bool;
-          default = true;
-          description = lib.mdDoc ''
-            Remove /etc/hosts entries for "127.0.0.2" and "::1" defined in nixos/modules/config/networking.nix
-            Regionservers must be able to resolve their hostnames to their IP addresses, through PTR records
-            or /etc/hosts entries.
-
-          '';
-        };
-
-        openFirewall = mkOption {
-          type = types.bool;
-          default = false;
-          description = lib.mdDoc ''
-            Open firewall ports for HBase master.
-          '';
+        infoPort = mkOption {
+          type = types.int;
+          default = infoPort;
+          description = mdDoc "web UI port";
         };
       };
-    };
-  };
-
-  config = mkMerge [
-    (mkIf cfg.hbase.master.enable {
-      services.hadoop.gatewayRole = {
-        enable = true;
-        enableHbaseCli = mkDefault true;
-      };
-
-      systemd.services.hbase-master = {
-        description = "HBase master";
-        wantedBy = [ "multi-user.target" ];
-
-        preStart = mkIf cfg.hbase.master.initHDFS ''
-          HADOOP_USER_NAME=hdfs ${cfg.package}/bin/hdfs --config ${hadoopConf} dfsadmin -safemode wait
-          HADOOP_USER_NAME=hdfs ${cfg.package}/bin/hdfs --config ${hadoopConf} dfs -mkdir -p ${cfg.hbase.rootdir}
-          HADOOP_USER_NAME=hdfs ${cfg.package}/bin/hdfs --config ${hadoopConf} dfs -chown hbase ${cfg.hbase.rootdir}
+    in mapAttrs hbaseRoleOption {
+      master.initHDFS = mkEnableOption (mdDoc "initialization of the hbase directory on HDFS");
+      regionServer.overrideHosts = mkOption {
+        type = types.bool;
+        default = true;
+        description = mdDoc ''
+          Remove /etc/hosts entries for "127.0.0.2" and "::1" defined in nixos/modules/config/networking.nix
+          Regionservers must be able to resolve their hostnames to their IP addresses, through PTR records
+          or /etc/hosts entries.
         '';
-
-        serviceConfig = {
-          User = "hbase";
-          SyslogIdentifier = "hbase-master";
-          ExecStart = "${cfg.hbase.package}/bin/hbase --config ${hadoopConf} " +
-                      "master start";
-          Restart = "always";
-        };
-      };
-
-      services.hadoop.hbaseSiteInternal."hbase.rootdir" = cfg.hbase.rootdir;
-
-      networking.firewall.allowedTCPPorts = mkIf cfg.hbase.master.openFirewall [
-        16000 16010
-      ];
-
-    })
-
-    (mkIf cfg.hbase.regionServer.enable {
-      services.hadoop.gatewayRole = {
-        enable = true;
-        enableHbaseCli = mkDefault true;
-      };
-
-      systemd.services.hbase-regionserver = {
-        description = "HBase RegionServer";
-        wantedBy = [ "multi-user.target" ];
-        serviceConfig = {
-          User = "hbase";
-          SyslogIdentifier = "hbase-regionserver";
-          ExecStart = "${cfg.hbase.package}/bin/hbase --config /etc/hadoop-conf/ " +
-                      "regionserver start";
-          Restart = "always";
-        };
       };
+      thrift = ports 9090 9095;
+      rest = ports 8080 8085;
+    });
+  };
 
-      services.hadoop.hbaseSiteInternal."hbase.rootdir" = cfg.hbase.rootdir;
-
-      networking = {
-        firewall.allowedTCPPorts = mkIf cfg.hbase.regionServer.openFirewall [
-          16020 16030
-        ];
-        hosts = mkIf cfg.hbase.regionServer.overrideHosts {
-          "127.0.0.2" = mkForce [ ];
-          "::1" = mkForce [ ];
-        };
-      };
-    })
+  config = mkMerge ([
 
     (mkIf cfg.gatewayRole.enable {
 
@@ -192,5 +209,10 @@ in
         isSystemUser = true;
       };
     })
-  ];
+  ] ++ (mapAttrsToList hbaseRoleConfig {
+    master = [ 16000 16010 ];
+    regionServer = [ 16020 16030 ];
+    thrift = with cfg.hbase.thrift; [ port infoPort ];
+    rest = with cfg.hbase.rest; [ port infoPort ];
+  }));
 }
diff --git a/nixos/modules/services/cluster/kubernetes/addons/dns.nix b/nixos/modules/services/cluster/kubernetes/addons/dns.nix
index 3d41b5f008537..1c00329e6ccff 100644
--- a/nixos/modules/services/cluster/kubernetes/addons/dns.nix
+++ b/nixos/modules/services/cluster/kubernetes/addons/dns.nix
@@ -3,7 +3,7 @@
 with lib;
 
 let
-  version = "1.7.1";
+  version = "1.10.1";
   cfg = config.services.kubernetes.addons.dns;
   ports = {
     dns = 10053;
@@ -59,9 +59,9 @@ in {
       type = types.attrs;
       default = {
         imageName = "coredns/coredns";
-        imageDigest = "sha256:4a6e0769130686518325b21b0c1d0688b54e7c79244d48e1b15634e98e40c6ef";
+        imageDigest = "sha256:a0ead06651cf580044aeb0a0feba63591858fb2e43ade8c9dea45a6a89ae7e5e";
         finalImageTag = version;
-        sha256 = "02r440xcdsgi137k5lmmvp0z5w5fmk8g9mysq5pnysq1wl8sj6mw";
+        sha256 = "0wg696920smmal7552a2zdhfncndn5kfammfa8bk8l7dz9bhk0y1";
       };
     };
 
@@ -136,6 +136,11 @@ in {
             resources = [ "nodes" ];
             verbs = [ "get" ];
           }
+          {
+            apiGroups = [ "discovery.k8s.io" ];
+            resources = [ "endpointslices" ];
+            verbs = [ "list" "watch" ];
+          }
         ];
       };
 
diff --git a/nixos/modules/services/cluster/kubernetes/pki.nix b/nixos/modules/services/cluster/kubernetes/pki.nix
index 26fe0f5e9e097..38682701ea151 100644
--- a/nixos/modules/services/cluster/kubernetes/pki.nix
+++ b/nixos/modules/services/cluster/kubernetes/pki.nix
@@ -270,7 +270,7 @@ in
           '';
         })]);
 
-      environment.etc.${cfg.etcClusterAdminKubeconfig}.source = mkIf (!isNull cfg.etcClusterAdminKubeconfig)
+      environment.etc.${cfg.etcClusterAdminKubeconfig}.source = mkIf (cfg.etcClusterAdminKubeconfig != null)
         clusterAdminKubeconfig;
 
       environment.systemPackages = mkIf (top.kubelet.enable || top.proxy.enable) [
diff --git a/nixos/modules/services/continuous-integration/woodpecker/agents.nix b/nixos/modules/services/continuous-integration/woodpecker/agents.nix
new file mode 100644
index 0000000000000..caf6c85093424
--- /dev/null
+++ b/nixos/modules/services/continuous-integration/woodpecker/agents.nix
@@ -0,0 +1,144 @@
+{ config
+, lib
+, pkgs
+, ...
+}:
+
+let
+  cfg = config.services.woodpecker-agents;
+
+  agentModule = lib.types.submodule {
+    options = {
+      enable = lib.mkEnableOption (lib.mdDoc "this Woodpecker-Agent. Agents execute tasks generated by a Server, every install will need one server and at least one agent");
+
+      package = lib.mkPackageOptionMD pkgs "woodpecker-agent" { };
+
+      environment = lib.mkOption {
+        default = { };
+        type = lib.types.attrsOf lib.types.str;
+        example = lib.literalExpression ''
+          {
+            WOODPECKER_SERVER = "localhost:9000";
+            WOODPECKER_BACKEND = "docker";
+            DOCKER_HOST = "unix:///run/podman/podman.sock";
+          }
+        '';
+        description = lib.mdDoc "woodpecker-agent config envrionment variables, for other options read the [documentation](https://woodpecker-ci.org/docs/administration/agent-config)";
+      };
+
+      extraGroups = lib.mkOption {
+        type = lib.types.listOf lib.types.str;
+        default = [ ];
+        example = [ "podman" ];
+        description = lib.mdDoc ''
+          Additional groups for the systemd service.
+        '';
+      };
+
+      environmentFile = lib.mkOption {
+        type = lib.types.listOf lib.types.path;
+        default = [ ];
+        example = [ "/var/secrets/woodpecker-agent.env" ];
+        description = lib.mdDoc ''
+          File to load environment variables
+          from. This is helpful for specifying secrets.
+          Example content of environmentFile:
+          ```
+          WOODPECKER_AGENT_SECRET=your-shared-secret-goes-here
+          ```
+        '';
+      };
+    };
+  };
+
+  mkAgentService = name: agentCfg: {
+    name = "woodpecker-agent-${name}";
+    value = {
+      description = "Woodpecker-Agent Service - ${name}";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" ];
+      wants = [ "network-online.target" ];
+      serviceConfig = {
+        DynamicUser = true;
+        SupplementaryGroups = agentCfg.extraGroups;
+        EnvironmentFile = agentCfg.environmentFile;
+        ExecStart = lib.getExe agentCfg.package;
+        Restart = "on-failure";
+        RestartSec = 15;
+        CapabilityBoundingSet = "";
+        NoNewPrivileges = true;
+        ProtectSystem = "strict";
+        PrivateTmp = true;
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProtectHostname = true;
+        ProtectClock = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectKernelLogs = true;
+        ProtectControlGroups = true;
+        RestrictAddressFamilies = [ "AF_UNIX AF_INET AF_INET6" ];
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        PrivateMounts = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = "~@clock @privileged @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @reboot @setuid @swap";
+        BindReadOnlyPaths = [
+          "-/etc/resolv.conf"
+          "-/etc/nsswitch.conf"
+          "-/etc/ssl/certs"
+          "-/etc/static/ssl/certs"
+          "-/etc/hosts"
+          "-/etc/localtime"
+        ];
+      };
+      inherit (agentCfg) environment;
+    };
+  };
+in
+{
+  meta.maintainers = with lib.maintainers; [ janik ambroisie ];
+
+  options = {
+    services.woodpecker-agents = {
+      agents = lib.mkOption {
+        default = { };
+        type = lib.types.attrsOf agentModule;
+        example = {
+          docker = {
+            environment = {
+              WOODPECKER_SERVER = "localhost:9000";
+              WOODPECKER_BACKEND = "docker";
+              DOCKER_HOST = "unix:///run/podman/podman.sock";
+            };
+
+            extraGroups = [ "docker" ];
+
+            environmentFile = "/run/secrets/woodpecker/agent-secret.txt";
+          };
+
+          exec = {
+            environment = {
+              WOODPECKER_SERVER = "localhost:9000";
+              WOODPECKER_BACKEND = "exec";
+            };
+
+            environmentFile = "/run/secrets/woodpecker/agent-secret.txt";
+          };
+        };
+        description = lib.mdDoc "woodpecker-agents configurations";
+      };
+    };
+  };
+
+  config = {
+    systemd.services =
+      let
+        mkServices = lib.mapAttrs' mkAgentService;
+        enabledAgents = lib.filterAttrs (_: agent: agent.enable) cfg.agents;
+      in
+      mkServices enabledAgents;
+  };
+}
diff --git a/nixos/modules/services/continuous-integration/woodpecker/server.nix b/nixos/modules/services/continuous-integration/woodpecker/server.nix
new file mode 100644
index 0000000000000..be7786da8505f
--- /dev/null
+++ b/nixos/modules/services/continuous-integration/woodpecker/server.nix
@@ -0,0 +1,98 @@
+{ config
+, lib
+, pkgs
+, ...
+}:
+
+let
+  cfg = config.services.woodpecker-server;
+in
+{
+  meta.maintainers = with lib.maintainers; [ janik ambroisie ];
+
+
+  options = {
+    services.woodpecker-server = {
+      enable = lib.mkEnableOption (lib.mdDoc "the Woodpecker-Server, a CI/CD application for automatic builds, deployments and tests");
+      package = lib.mkPackageOptionMD pkgs "woodpecker-server" { };
+      environment = lib.mkOption {
+        default = { };
+        type = lib.types.attrsOf lib.types.str;
+        example = lib.literalExpression
+          ''
+            {
+              WOODPECKER_HOST = "https://woodpecker.example.com";
+              WOODPECKER_OPEN = "true";
+              WOODPECKER_GITEA = "true";
+              WOODPECKER_GITEA_CLIENT = "ffffffff-ffff-ffff-ffff-ffffffffffff";
+              WOODPECKER_GITEA_URL = "https://git.example.com";
+            }
+          '';
+        description = lib.mdDoc "woodpecker-server config envrionment variables, for other options read the [documentation](https://woodpecker-ci.org/docs/administration/server-config)";
+      };
+      environmentFile = lib.mkOption {
+        type = lib.types.nullOr lib.types.path;
+        default = null;
+        example = "/root/woodpecker-server.env";
+        description = lib.mdDoc ''
+          File to load environment variables
+          from. This is helpful for specifying secrets.
+          Example content of environmentFile:
+          ```
+          WOODPECKER_AGENT_SECRET=your-shared-secret-goes-here
+          WOODPECKER_GITEA_SECRET=gto_**************************************
+          ```
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services = {
+      woodpecker-server = {
+        description = "Woodpecker-Server Service";
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network-online.target" ];
+        wants = [ "network-online.target" ];
+        serviceConfig = {
+          DynamicUser = true;
+          WorkingDirectory = "%S/woodpecker-server";
+          StateDirectory = "woodpecker-server";
+          StateDirectoryMode = "0700";
+          UMask = "0007";
+          ConfigurationDirectory = "woodpecker-server";
+          EnvironmentFile = lib.optional (cfg.environmentFile != null) cfg.environmentFile;
+          ExecStart = "${cfg.package}/bin/woodpecker-server";
+          Restart = "on-failure";
+          RestartSec = 15;
+          CapabilityBoundingSet = "";
+          # Security
+          NoNewPrivileges = true;
+          # Sandboxing
+          ProtectSystem = "strict";
+          ProtectHome = true;
+          PrivateTmp = true;
+          PrivateDevices = true;
+          PrivateUsers = true;
+          ProtectHostname = true;
+          ProtectClock = true;
+          ProtectKernelTunables = true;
+          ProtectKernelModules = true;
+          ProtectKernelLogs = true;
+          ProtectControlGroups = true;
+          RestrictAddressFamilies = [ "AF_UNIX AF_INET AF_INET6" ];
+          LockPersonality = true;
+          MemoryDenyWriteExecute = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+          PrivateMounts = true;
+          # System Call Filtering
+          SystemCallArchitectures = "native";
+          SystemCallFilter = "~@clock @privileged @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @reboot @setuid @swap";
+        };
+        inherit (cfg) environment;
+      };
+    };
+  };
+}
+
diff --git a/nixos/modules/services/databases/dgraph.nix b/nixos/modules/services/databases/dgraph.nix
index 887164fa5b943..7f005a9971a69 100644
--- a/nixos/modules/services/databases/dgraph.nix
+++ b/nixos/modules/services/databases/dgraph.nix
@@ -12,7 +12,7 @@ let
   ''
     mkdir -p $out/bin
     makeWrapper ${cfg.package}/bin/dgraph $out/bin/dgraph \
-      --set PATH '${lib.makeBinPath [ pkgs.nodejs ]}:$PATH' \
+      --prefix PATH : "${lib.makeBinPath [ pkgs.nodejs ]}" \
   '';
   securityOptions = {
       NoNewPrivileges = true;
diff --git a/nixos/modules/services/desktops/pipewire/daemon/client-rt.conf.json b/nixos/modules/services/desktops/pipewire/daemon/client-rt.conf.json
deleted file mode 100644
index c204606193af5..0000000000000
--- a/nixos/modules/services/desktops/pipewire/daemon/client-rt.conf.json
+++ /dev/null
@@ -1,54 +0,0 @@
-{
-  "context.properties": {
-    "log.level": 0
-  },
-  "context.spa-libs": {
-    "audio.convert.*": "audioconvert/libspa-audioconvert",
-    "support.*": "support/libspa-support"
-  },
-  "context.modules": [
-    {
-      "name": "libpipewire-module-rt",
-      "args": {},
-      "flags": [
-        "ifexists",
-        "nofail"
-      ]
-    },
-    {
-      "name": "libpipewire-module-protocol-native"
-    },
-    {
-      "name": "libpipewire-module-client-node"
-    },
-    {
-      "name": "libpipewire-module-client-device"
-    },
-    {
-      "name": "libpipewire-module-adapter"
-    },
-    {
-      "name": "libpipewire-module-metadata"
-    },
-    {
-      "name": "libpipewire-module-session-manager"
-    }
-  ],
-  "filter.properties": {},
-  "stream.properties": {},
-  "alsa.properties": {},
-  "alsa.rules": [
-    {
-      "matches": [
-        {
-          "application.process.binary": "resolve"
-        }
-      ],
-      "actions": {
-        "update-props": {
-          "alsa.buffer-bytes": 131072
-        }
-      }
-    }
-  ]
-}
diff --git a/nixos/modules/services/desktops/pipewire/daemon/client.conf.json b/nixos/modules/services/desktops/pipewire/daemon/client.conf.json
deleted file mode 100644
index 71294a0e78a2d..0000000000000
--- a/nixos/modules/services/desktops/pipewire/daemon/client.conf.json
+++ /dev/null
@@ -1,31 +0,0 @@
-{
-  "context.properties": {
-    "log.level": 0
-  },
-  "context.spa-libs": {
-    "audio.convert.*": "audioconvert/libspa-audioconvert",
-    "support.*": "support/libspa-support"
-  },
-  "context.modules": [
-    {
-      "name": "libpipewire-module-protocol-native"
-    },
-    {
-      "name": "libpipewire-module-client-node"
-    },
-    {
-      "name": "libpipewire-module-client-device"
-    },
-    {
-      "name": "libpipewire-module-adapter"
-    },
-    {
-      "name": "libpipewire-module-metadata"
-    },
-    {
-      "name": "libpipewire-module-session-manager"
-    }
-  ],
-  "filter.properties": {},
-  "stream.properties": {}
-}
diff --git a/nixos/modules/services/desktops/pipewire/daemon/filter-chain.conf.json b/nixos/modules/services/desktops/pipewire/daemon/filter-chain.conf.json
deleted file mode 100644
index 689fca88359ba..0000000000000
--- a/nixos/modules/services/desktops/pipewire/daemon/filter-chain.conf.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
-  "context.properties": {
-    "log.level": 0
-  },
-  "context.spa-libs": {
-    "audio.convert.*": "audioconvert/libspa-audioconvert",
-    "support.*": "support/libspa-support"
-  },
-  "context.modules": [
-    {
-      "name": "libpipewire-module-rt",
-      "args": {},
-      "flags": [
-        "ifexists",
-        "nofail"
-      ]
-    },
-    {
-      "name": "libpipewire-module-protocol-native"
-    },
-    {
-      "name": "libpipewire-module-client-node"
-    },
-    {
-      "name": "libpipewire-module-adapter"
-    }
-  ]
-}
diff --git a/nixos/modules/services/desktops/pipewire/daemon/jack.conf.json b/nixos/modules/services/desktops/pipewire/daemon/jack.conf.json
deleted file mode 100644
index f2e396dd28d76..0000000000000
--- a/nixos/modules/services/desktops/pipewire/daemon/jack.conf.json
+++ /dev/null
@@ -1,75 +0,0 @@
-{
-  "context.properties": {
-    "log.level": 0
-  },
-  "context.spa-libs": {
-    "support.*": "support/libspa-support"
-  },
-  "context.modules": [
-    {
-      "name": "libpipewire-module-rt",
-      "args": {},
-      "flags": [
-        "ifexists",
-        "nofail"
-      ]
-    },
-    {
-      "name": "libpipewire-module-protocol-native"
-    },
-    {
-      "name": "libpipewire-module-client-node"
-    },
-    {
-      "name": "libpipewire-module-metadata"
-    }
-  ],
-  "jack.properties": {},
-  "jack.rules": [
-    {
-      "matches": [
-        {}
-      ],
-      "actions": {
-        "update-props": {}
-      }
-    },
-    {
-      "matches": [
-        {
-          "application.process.binary": "jack_bufsize"
-        }
-      ],
-      "actions": {
-        "update-props": {
-          "jack.global-buffer-size": true
-        }
-      }
-    },
-    {
-      "matches": [
-        {
-          "application.process.binary": "qsynth"
-        }
-      ],
-      "actions": {
-        "update-props": {
-          "node.pause-on-idle": false,
-          "node.passive": true
-        }
-      }
-    },
-    {
-      "matches": [
-        {
-          "client.name": "Mixxx"
-        }
-      ],
-      "actions": {
-        "update-props": {
-          "jack.merge-monitor": false
-        }
-      }
-    }
-  ]
-}
diff --git a/nixos/modules/services/desktops/pipewire/daemon/minimal.conf.json b/nixos/modules/services/desktops/pipewire/daemon/minimal.conf.json
deleted file mode 100644
index 0f1ebe5749c67..0000000000000
--- a/nixos/modules/services/desktops/pipewire/daemon/minimal.conf.json
+++ /dev/null
@@ -1,120 +0,0 @@
-{
-  "context.properties": {
-    "link.max-buffers": 16,
-    "core.daemon": true,
-    "core.name": "pipewire-0",
-    "settings.check-quantum": true,
-    "settings.check-rate": true,
-    "vm.overrides": {
-      "default.clock.min-quantum": 1024
-    }
-  },
-  "context.spa-libs": {
-    "audio.convert.*": "audioconvert/libspa-audioconvert",
-    "api.alsa.*": "alsa/libspa-alsa",
-    "support.*": "support/libspa-support"
-  },
-  "context.modules": [
-    {
-      "name": "libpipewire-module-rt",
-      "args": {
-        "nice.level": -11
-      },
-      "flags": [
-        "ifexists",
-        "nofail"
-      ]
-    },
-    {
-      "name": "libpipewire-module-protocol-native"
-    },
-    {
-      "name": "libpipewire-module-profiler"
-    },
-    {
-      "name": "libpipewire-module-metadata"
-    },
-    {
-      "name": "libpipewire-module-spa-node-factory"
-    },
-    {
-      "name": "libpipewire-module-client-node"
-    },
-    {
-      "name": "libpipewire-module-access",
-      "args": {}
-    },
-    {
-      "name": "libpipewire-module-adapter"
-    },
-    {
-      "name": "libpipewire-module-link-factory"
-    }
-  ],
-  "context.objects": [
-    {
-      "factory": "metadata",
-      "args": {
-        "metadata.name": "default"
-      }
-    },
-    {
-      "factory": "spa-node-factory",
-      "args": {
-        "factory.name": "support.node.driver",
-        "node.name": "Dummy-Driver",
-        "node.group": "pipewire.dummy",
-        "priority.driver": 20000
-      }
-    },
-    {
-      "factory": "spa-node-factory",
-      "args": {
-        "factory.name": "support.node.driver",
-        "node.name": "Freewheel-Driver",
-        "priority.driver": 19000,
-        "node.group": "pipewire.freewheel",
-        "node.freewheel": true
-      }
-    },
-    {
-      "factory": "adapter",
-      "args": {
-        "factory.name": "api.alsa.pcm.source",
-        "node.name": "system",
-        "node.description": "system",
-        "media.class": "Audio/Source",
-        "api.alsa.path": "hw:0",
-        "node.suspend-on-idle": true,
-        "resample.disable": true,
-        "channelmix.disable": true,
-        "adapter.auto-port-config": {
-          "mode": "dsp",
-          "monitor": false,
-          "control": false,
-          "position": "unknown"
-        }
-      }
-    },
-    {
-      "factory": "adapter",
-      "args": {
-        "factory.name": "api.alsa.pcm.sink",
-        "node.name": "system",
-        "node.description": "system",
-        "media.class": "Audio/Sink",
-        "api.alsa.path": "hw:0",
-        "node.suspend-on-idle": true,
-        "resample.disable": true,
-        "channelmix.disable": true,
-        "adapter.auto-port-config": {
-          "mode": "dsp",
-          "monitor": false,
-          "control": false,
-          "position": "unknown"
-        }
-      }
-    }
-  ],
-  "context.exec": []
-}
diff --git a/nixos/modules/services/desktops/pipewire/daemon/pipewire-aes67.conf.json b/nixos/modules/services/desktops/pipewire/daemon/pipewire-aes67.conf.json
deleted file mode 100644
index aaffa93ca964c..0000000000000
--- a/nixos/modules/services/desktops/pipewire/daemon/pipewire-aes67.conf.json
+++ /dev/null
@@ -1,38 +0,0 @@
-{
-  "context.properties": {},
-  "context.modules": [
-    {
-      "name": "libpipewire-module-rt",
-      "args": {
-        "nice.level": -11
-      },
-      "flags": [
-        "ifexists",
-        "nofail"
-      ]
-    },
-    {
-      "name": "libpipewire-module-protocol-native"
-    },
-    {
-      "name": "libpipewire-module-client-node"
-    },
-    {
-      "name": "libpipewire-module-adapter"
-    },
-    {
-      "name": "libpipewire-module-rtp-source",
-      "args": {
-        "sap.ip": "239.255.255.255",
-        "sap.port": 9875,
-        "sess.latency.msec": 10,
-        "local.ifname": "eth0",
-        "stream.props": {
-          "media.class": "Audio/Source",
-          "node.virtual": false,
-          "device.api": "aes67"
-        }
-      }
-    }
-  ]
-}
diff --git a/nixos/modules/services/desktops/pipewire/daemon/pipewire-avb.conf.json b/nixos/modules/services/desktops/pipewire/daemon/pipewire-avb.conf.json
deleted file mode 100644
index 4f669895d87b6..0000000000000
--- a/nixos/modules/services/desktops/pipewire/daemon/pipewire-avb.conf.json
+++ /dev/null
@@ -1,38 +0,0 @@
-{
-  "context.properties": {},
-  "context.spa-libs": {
-    "audio.convert.*": "audioconvert/libspa-audioconvert",
-    "support.*": "support/libspa-support"
-  },
-  "context.modules": [
-    {
-      "name": "libpipewire-module-rt",
-      "args": {
-        "nice.level": -11
-      },
-      "flags": [
-        "ifexists",
-        "nofail"
-      ]
-    },
-    {
-      "name": "libpipewire-module-protocol-native"
-    },
-    {
-      "name": "libpipewire-module-client-node"
-    },
-    {
-      "name": "libpipewire-module-adapter"
-    },
-    {
-      "name": "libpipewire-module-avb",
-      "args": {}
-    }
-  ],
-  "context.exec": [],
-  "stream.properties": {},
-  "avb.properties": {
-    "ifname": "enp3s0",
-    "vm.overrides": {}
-  }
-}
diff --git a/nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json b/nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json
deleted file mode 100644
index b1a864853325c..0000000000000
--- a/nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json
+++ /dev/null
@@ -1,106 +0,0 @@
-{
-  "context.properties": {},
-  "context.spa-libs": {
-    "audio.convert.*": "audioconvert/libspa-audioconvert",
-    "support.*": "support/libspa-support"
-  },
-  "context.modules": [
-    {
-      "name": "libpipewire-module-rt",
-      "args": {
-        "nice.level": -11
-      },
-      "flags": [
-        "ifexists",
-        "nofail"
-      ]
-    },
-    {
-      "name": "libpipewire-module-protocol-native"
-    },
-    {
-      "name": "libpipewire-module-client-node"
-    },
-    {
-      "name": "libpipewire-module-adapter"
-    },
-    {
-      "name": "libpipewire-module-metadata"
-    },
-    {
-      "name": "libpipewire-module-protocol-pulse",
-      "args": {}
-    }
-  ],
-  "context.exec": [],
-  "pulse.cmd": [
-    {
-      "cmd": "load-module",
-      "args": "module-always-sink",
-      "flags": []
-    }
-  ],
-  "stream.properties": {},
-  "pulse.properties": {
-    "server.address": [
-      "unix:native"
-    ],
-    "vm.overrides": {
-      "pulse.min.quantum": "1024/48000"
-    }
-  },
-  "pulse.rules": [
-    {
-      "matches": [
-        {}
-      ],
-      "actions": {
-        "update-props": {}
-      }
-    },
-    {
-      "matches": [
-        {
-          "application.process.binary": "teams"
-        },
-        {
-          "application.process.binary": "teams-insiders"
-        },
-        {
-          "application.process.binary": "skypeforlinux"
-        }
-      ],
-      "actions": {
-        "quirks": [
-          "force-s16-info"
-        ]
-      }
-    },
-    {
-      "matches": [
-        {
-          "application.process.binary": "firefox"
-        }
-      ],
-      "actions": {
-        "quirks": [
-          "remove-capture-dont-move"
-        ]
-      }
-    },
-    {
-      "matches": [
-        {
-          "application.name": "~speech-dispatcher.*"
-        }
-      ],
-      "actions": {
-        "update-props": {
-          "pulse.min.req": "512/48000",
-          "pulse.min.quantum": "512/48000",
-          "pulse.idle.timeout": 5
-        }
-      }
-    }
-  ]
-}
diff --git a/nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json b/nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json
deleted file mode 100644
index a47abe2213d94..0000000000000
--- a/nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json
+++ /dev/null
@@ -1,110 +0,0 @@
-{
-  "context.properties": {
-    "link.max-buffers": 16,
-    "core.daemon": true,
-    "core.name": "pipewire-0",
-    "vm.overrides": {
-      "default.clock.min-quantum": 1024
-    },
-    "module.x11.bell": true
-  },
-  "context.spa-libs": {
-    "audio.convert.*": "audioconvert/libspa-audioconvert",
-    "avb.*": "avb/libspa-avb",
-    "api.alsa.*": "alsa/libspa-alsa",
-    "api.v4l2.*": "v4l2/libspa-v4l2",
-    "api.libcamera.*": "libcamera/libspa-libcamera",
-    "api.bluez5.*": "bluez5/libspa-bluez5",
-    "api.vulkan.*": "vulkan/libspa-vulkan",
-    "api.jack.*": "jack/libspa-jack",
-    "support.*": "support/libspa-support"
-  },
-  "context.modules": [
-    {
-      "name": "libpipewire-module-rt",
-      "args": {
-        "nice.level": -11
-      },
-      "flags": [
-        "ifexists",
-        "nofail"
-      ]
-    },
-    {
-      "name": "libpipewire-module-protocol-native"
-    },
-    {
-      "name": "libpipewire-module-profiler"
-    },
-    {
-      "name": "libpipewire-module-metadata"
-    },
-    {
-      "name": "libpipewire-module-spa-device-factory"
-    },
-    {
-      "name": "libpipewire-module-spa-node-factory"
-    },
-    {
-      "name": "libpipewire-module-client-node"
-    },
-    {
-      "name": "libpipewire-module-client-device"
-    },
-    {
-      "name": "libpipewire-module-portal",
-      "flags": [
-        "ifexists",
-        "nofail"
-      ]
-    },
-    {
-      "name": "libpipewire-module-access",
-      "args": {}
-    },
-    {
-      "name": "libpipewire-module-adapter"
-    },
-    {
-      "name": "libpipewire-module-link-factory"
-    },
-    {
-      "name": "libpipewire-module-session-manager"
-    },
-    {
-      "name": "libpipewire-module-x11-bell",
-      "args": {},
-      "flags": [
-        "ifexists",
-        "nofail"
-      ],
-      "condition": [
-        {
-          "module.x11.bell": true
-        }
-      ]
-    }
-  ],
-  "context.objects": [
-    {
-      "factory": "spa-node-factory",
-      "args": {
-        "factory.name": "support.node.driver",
-        "node.name": "Dummy-Driver",
-        "node.group": "pipewire.dummy",
-        "priority.driver": 20000
-      }
-    },
-    {
-      "factory": "spa-node-factory",
-      "args": {
-        "factory.name": "support.node.driver",
-        "node.name": "Freewheel-Driver",
-        "priority.driver": 19000,
-        "node.group": "pipewire.freewheel",
-        "node.freewheel": true
-      }
-    }
-  ],
-  "context.exec": []
-}
diff --git a/nixos/modules/services/desktops/pipewire/media-session/alsa-monitor.conf.json b/nixos/modules/services/desktops/pipewire/media-session/alsa-monitor.conf.json
deleted file mode 100644
index 53fc9cc96343b..0000000000000
--- a/nixos/modules/services/desktops/pipewire/media-session/alsa-monitor.conf.json
+++ /dev/null
@@ -1,34 +0,0 @@
-{
-  "properties": {},
-  "rules": [
-    {
-      "matches": [
-        {
-          "device.name": "~alsa_card.*"
-        }
-      ],
-      "actions": {
-        "update-props": {
-          "api.alsa.use-acp": true,
-          "api.acp.auto-profile": false,
-          "api.acp.auto-port": false
-        }
-      }
-    },
-    {
-      "matches": [
-        {
-          "node.name": "~alsa_input.*"
-        },
-        {
-          "node.name": "~alsa_output.*"
-        }
-      ],
-      "actions": {
-        "update-props": {
-          "node.pause-on-idle": false
-        }
-      }
-    }
-  ]
-}
diff --git a/nixos/modules/services/desktops/pipewire/media-session/bluez-monitor.conf.json b/nixos/modules/services/desktops/pipewire/media-session/bluez-monitor.conf.json
deleted file mode 100644
index 6d1c23e825699..0000000000000
--- a/nixos/modules/services/desktops/pipewire/media-session/bluez-monitor.conf.json
+++ /dev/null
@@ -1,36 +0,0 @@
-{
-  "properties": {},
-  "rules": [
-    {
-      "matches": [
-        {
-          "device.name": "~bluez_card.*"
-        }
-      ],
-      "actions": {
-        "update-props": {
-          "bluez5.auto-connect": [
-            "hfp_hf",
-            "hsp_hs",
-            "a2dp_sink"
-          ]
-        }
-      }
-    },
-    {
-      "matches": [
-        {
-          "node.name": "~bluez_input.*"
-        },
-        {
-          "node.name": "~bluez_output.*"
-        }
-      ],
-      "actions": {
-        "update-props": {
-          "node.pause-on-idle": false
-        }
-      }
-    }
-  ]
-}
diff --git a/nixos/modules/services/desktops/pipewire/media-session/media-session.conf.json b/nixos/modules/services/desktops/pipewire/media-session/media-session.conf.json
deleted file mode 100644
index 4b4e302af3876..0000000000000
--- a/nixos/modules/services/desktops/pipewire/media-session/media-session.conf.json
+++ /dev/null
@@ -1,68 +0,0 @@
-{
-  "context.properties": {},
-  "context.spa-libs": {
-    "api.bluez5.*": "bluez5/libspa-bluez5",
-    "api.alsa.*": "alsa/libspa-alsa",
-    "api.v4l2.*": "v4l2/libspa-v4l2",
-    "api.libcamera.*": "libcamera/libspa-libcamera"
-  },
-  "context.modules": [
-    {
-      "name": "libpipewire-module-rtkit",
-      "args": {},
-      "flags": [
-        "ifexists",
-        "nofail"
-      ]
-    },
-    {
-      "name": "libpipewire-module-protocol-native"
-    },
-    {
-      "name": "libpipewire-module-client-node"
-    },
-    {
-      "name": "libpipewire-module-client-device"
-    },
-    {
-      "name": "libpipewire-module-adapter"
-    },
-    {
-      "name": "libpipewire-module-metadata"
-    },
-    {
-      "name": "libpipewire-module-session-manager"
-    }
-  ],
-  "session.modules": {
-    "default": [
-      "flatpak",
-      "portal",
-      "v4l2",
-      "suspend-node",
-      "policy-node"
-    ],
-    "with-audio": [
-      "metadata",
-      "default-nodes",
-      "default-profile",
-      "default-routes",
-      "alsa-seq",
-      "alsa-monitor"
-    ],
-    "with-alsa": [
-      "with-audio"
-    ],
-    "with-jack": [
-      "with-audio"
-    ],
-    "with-pulseaudio": [
-      "with-audio",
-      "bluez5",
-      "bluez5-autoswitch",
-      "logind",
-      "restore-stream",
-      "streams-follow-default"
-    ]
-  }
-}
diff --git a/nixos/modules/services/desktops/pipewire/media-session/v4l2-monitor.conf.json b/nixos/modules/services/desktops/pipewire/media-session/v4l2-monitor.conf.json
deleted file mode 100644
index b08cba1b604b5..0000000000000
--- a/nixos/modules/services/desktops/pipewire/media-session/v4l2-monitor.conf.json
+++ /dev/null
@@ -1,30 +0,0 @@
-{
-  "properties": {},
-  "rules": [
-    {
-      "matches": [
-        {
-          "device.name": "~v4l2_device.*"
-        }
-      ],
-      "actions": {
-        "update-props": {}
-      }
-    },
-    {
-      "matches": [
-        {
-          "node.name": "~v4l2_input.*"
-        },
-        {
-          "node.name": "~v4l2_output.*"
-        }
-      ],
-      "actions": {
-        "update-props": {
-          "node.pause-on-idle": false
-        }
-      }
-    }
-  ]
-}
diff --git a/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix b/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix
deleted file mode 100644
index 203139294c6b5..0000000000000
--- a/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix
+++ /dev/null
@@ -1,141 +0,0 @@
-# pipewire example session manager.
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  json = pkgs.formats.json {};
-  cfg = config.services.pipewire.media-session;
-  enable32BitAlsaPlugins = cfg.alsa.support32Bit
-                           && pkgs.stdenv.isx86_64
-                           && pkgs.pkgsi686Linux.pipewire != null;
-
-  # Use upstream config files passed through spa-json-dump as the base
-  # Patched here as necessary for them to work with this module
-  defaults = {
-    alsa-monitor = lib.importJSON ./media-session/alsa-monitor.conf.json;
-    bluez-monitor = lib.importJSON ./media-session/bluez-monitor.conf.json;
-    media-session = lib.importJSON ./media-session/media-session.conf.json;
-    v4l2-monitor = lib.importJSON ./media-session/v4l2-monitor.conf.json;
-  };
-
-  configs = {
-    alsa-monitor = recursiveUpdate defaults.alsa-monitor cfg.config.alsa-monitor;
-    bluez-monitor = recursiveUpdate defaults.bluez-monitor cfg.config.bluez-monitor;
-    media-session = recursiveUpdate defaults.media-session cfg.config.media-session;
-    v4l2-monitor = recursiveUpdate defaults.v4l2-monitor cfg.config.v4l2-monitor;
-  };
-in {
-
-  meta = {
-    maintainers = teams.freedesktop.members;
-    # uses attributes of the linked package
-    buildDocsInSandbox = false;
-  };
-
-  ###### interface
-  options = {
-    services.pipewire.media-session = {
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc "Whether to enable the deprecated example Pipewire session manager";
-      };
-
-      package = mkOption {
-        type = types.package;
-        default = pkgs.pipewire-media-session;
-        defaultText = literalExpression "pkgs.pipewire-media-session";
-        description = lib.mdDoc ''
-          The pipewire-media-session derivation to use.
-        '';
-      };
-
-      config = {
-        media-session = mkOption {
-          type = json.type;
-          description = lib.mdDoc ''
-            Configuration for the media session core. For details see
-            https://gitlab.freedesktop.org/pipewire/media-session/-/blob/${cfg.package.version}/src/daemon/media-session.d/media-session.conf
-          '';
-          default = defaults.media-session;
-        };
-
-        alsa-monitor = mkOption {
-          type = json.type;
-          description = lib.mdDoc ''
-            Configuration for the alsa monitor. For details see
-            https://gitlab.freedesktop.org/pipewire/media-session/-/blob/${cfg.package.version}/src/daemon/media-session.d/alsa-monitor.conf
-          '';
-          default = defaults.alsa-monitor;
-        };
-
-        bluez-monitor = mkOption {
-          type = json.type;
-          description = lib.mdDoc ''
-            Configuration for the bluez5 monitor. For details see
-            https://gitlab.freedesktop.org/pipewire/media-session/-/blob/${cfg.package.version}/src/daemon/media-session.d/bluez-monitor.conf
-          '';
-          default = defaults.bluez-monitor;
-        };
-
-        v4l2-monitor = mkOption {
-          type = json.type;
-          description = lib.mdDoc ''
-            Configuration for the V4L2 monitor. For details see
-            https://gitlab.freedesktop.org/pipewire/media-session/-/blob/${cfg.package.version}/src/daemon/media-session.d/v4l2-monitor.conf
-          '';
-          default = defaults.v4l2-monitor;
-        };
-      };
-    };
-  };
-
-  ###### implementation
-  config = mkIf cfg.enable {
-    environment.systemPackages = [ cfg.package ];
-    systemd.packages = [ cfg.package ];
-
-    # Enable either system or user units.
-    systemd.services.pipewire-media-session.enable = config.services.pipewire.systemWide;
-    systemd.user.services.pipewire-media-session.enable = !config.services.pipewire.systemWide;
-
-    systemd.services.pipewire-media-session.wantedBy = [ "pipewire.service" ];
-    systemd.user.services.pipewire-media-session.wantedBy = [ "pipewire.service" ];
-
-    environment.etc."pipewire/media-session.d/media-session.conf" = {
-      source = json.generate "media-session.conf" configs.media-session;
-    };
-    environment.etc."pipewire/media-session.d/v4l2-monitor.conf" = {
-      source = json.generate "v4l2-monitor.conf" configs.v4l2-monitor;
-    };
-
-    environment.etc."pipewire/media-session.d/with-audio" =
-      mkIf config.services.pipewire.audio.enable {
-        text = "";
-      };
-
-    environment.etc."pipewire/media-session.d/with-alsa" =
-      mkIf config.services.pipewire.alsa.enable {
-        text = "";
-      };
-    environment.etc."pipewire/media-session.d/alsa-monitor.conf" =
-      mkIf config.services.pipewire.alsa.enable {
-        source = json.generate "alsa-monitor.conf" configs.alsa-monitor;
-      };
-
-    environment.etc."pipewire/media-session.d/with-pulseaudio" =
-      mkIf config.services.pipewire.pulse.enable {
-        text = "";
-      };
-    environment.etc."pipewire/media-session.d/bluez-monitor.conf" =
-      mkIf config.services.pipewire.pulse.enable {
-        source = json.generate "bluez-monitor.conf" configs.bluez-monitor;
-      };
-
-    environment.etc."pipewire/media-session.d/with-jack" =
-      mkIf config.services.pipewire.jack.enable {
-        text = "";
-      };
-  };
-}
diff --git a/nixos/modules/services/desktops/pipewire/pipewire.nix b/nixos/modules/services/desktops/pipewire/pipewire.nix
index 09cec9a791091..ae695baf42c60 100644
--- a/nixos/modules/services/desktops/pipewire/pipewire.nix
+++ b/nixos/modules/services/desktops/pipewire/pipewire.nix
@@ -4,7 +4,6 @@
 with lib;
 
 let
-  json = pkgs.formats.json {};
   cfg = config.services.pipewire;
   enable32BitAlsaPlugins = cfg.alsa.support32Bit
                            && pkgs.stdenv.isx86_64
@@ -18,34 +17,8 @@ let
     mkdir -p "$out/lib"
     ln -s "${cfg.package.jack}/lib" "$out/lib/pipewire"
   '';
-
-  # Use upstream config files passed through spa-json-dump as the base
-  # Patched here as necessary for them to work with this module
-  defaults = {
-    client = lib.importJSON ./daemon/client.conf.json;
-    client-rt = lib.importJSON ./daemon/client-rt.conf.json;
-    jack = lib.importJSON ./daemon/jack.conf.json;
-    minimal = lib.importJSON ./daemon/minimal.conf.json;
-    pipewire = lib.importJSON ./daemon/pipewire.conf.json;
-    pipewire-pulse = lib.importJSON ./daemon/pipewire-pulse.conf.json;
-  };
-
-  useSessionManager = cfg.wireplumber.enable || cfg.media-session.enable;
-
-  configs = {
-    client = recursiveUpdate defaults.client cfg.config.client;
-    client-rt = recursiveUpdate defaults.client-rt cfg.config.client-rt;
-    jack = recursiveUpdate defaults.jack cfg.config.jack;
-    pipewire = recursiveUpdate (if useSessionManager then defaults.pipewire else defaults.minimal) cfg.config.pipewire;
-    pipewire-pulse = recursiveUpdate defaults.pipewire-pulse cfg.config.pipewire-pulse;
-  };
 in {
-
-  meta = {
-    maintainers = teams.freedesktop.members ++ [ lib.maintainers.k900 ];
-    # uses attributes of the linked package
-    buildDocsInSandbox = false;
-  };
+  meta.maintainers = teams.freedesktop.members ++ [ lib.maintainers.k900 ];
 
   ###### interface
   options = {
@@ -69,53 +42,6 @@ in {
         '';
       };
 
-      config = {
-        client = mkOption {
-          type = json.type;
-          default = {};
-          description = lib.mdDoc ''
-            Configuration for pipewire clients. For details see
-            https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/client.conf.in
-          '';
-        };
-
-        client-rt = mkOption {
-          type = json.type;
-          default = {};
-          description = lib.mdDoc ''
-            Configuration for realtime pipewire clients. For details see
-            https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/client-rt.conf.in
-          '';
-        };
-
-        jack = mkOption {
-          type = json.type;
-          default = {};
-          description = lib.mdDoc ''
-            Configuration for the pipewire daemon's jack module. For details see
-            https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/jack.conf.in
-          '';
-        };
-
-        pipewire = mkOption {
-          type = json.type;
-          default = {};
-          description = lib.mdDoc ''
-            Configuration for the pipewire daemon. For details see
-            https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/pipewire.conf.in
-          '';
-        };
-
-        pipewire-pulse = mkOption {
-          type = json.type;
-          default = {};
-          description = lib.mdDoc ''
-            Configuration for the pipewire-pulse daemon. For details see
-            https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/pipewire-pulse.conf.in
-          '';
-        };
-      };
-
       audio = {
         enable = lib.mkOption {
           type = lib.types.bool;
@@ -153,10 +79,20 @@ in {
           https://github.com/PipeWire/pipewire/blob/master/NEWS
         '';
       };
-
     };
   };
 
+  imports = [
+    (lib.mkRemovedOptionModule ["services" "pipewire" "config"] ''
+      Overriding default Pipewire configuration through NixOS options never worked correctly and is no longer supported.
+      Please create drop-in files in /etc/pipewire/pipewire.conf.d/ to make the desired setting changes instead.
+    '')
+
+    (lib.mkRemovedOptionModule ["services" "pipewire" "media-session"] ''
+      pipewire-media-session is no longer supported upstream and has been removed.
+      Please switch to `services.pipewire.wireplumber` instead.
+    '')
+  ];
 
   ###### implementation
   config = mkIf cfg.enable {
@@ -222,22 +158,6 @@ in {
       source = "${cfg.package}/share/alsa/alsa.conf.d/99-pipewire-default.conf";
     };
 
-    environment.etc."pipewire/client.conf" = {
-      source = json.generate "client.conf" configs.client;
-    };
-    environment.etc."pipewire/client-rt.conf" = {
-      source = json.generate "client-rt.conf" configs.client-rt;
-    };
-    environment.etc."pipewire/jack.conf" = {
-      source = json.generate "jack.conf" configs.jack;
-    };
-    environment.etc."pipewire/pipewire.conf" = {
-      source = json.generate "pipewire.conf" configs.pipewire;
-    };
-    environment.etc."pipewire/pipewire-pulse.conf" = mkIf cfg.pulse.enable {
-      source = json.generate "pipewire-pulse.conf" configs.pipewire-pulse;
-    };
-
     environment.sessionVariables.LD_LIBRARY_PATH =
       lib.mkIf cfg.jack.enable [ "${cfg.package.jack}/lib" ];
 
@@ -256,12 +176,5 @@ in {
       };
       groups.pipewire.gid = config.ids.gids.pipewire;
     };
-
-    # https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/464#note_723554
-    systemd.services.pipewire.environment."PIPEWIRE_LINK_PASSIVE" = "1";
-    systemd.user.services.pipewire.environment."PIPEWIRE_LINK_PASSIVE" = "1";
-
-    # pipewire-pulse default config expects pactl to be in PATH
-    systemd.user.services.pipewire-pulse.path = lib.mkIf cfg.pulse.enable [ pkgs.pulseaudio ];
   };
 }
diff --git a/nixos/modules/services/desktops/pipewire/wireplumber.nix b/nixos/modules/services/desktops/pipewire/wireplumber.nix
index 4b36b99aa7c1e..95a7ece26c5d2 100644
--- a/nixos/modules/services/desktops/pipewire/wireplumber.nix
+++ b/nixos/modules/services/desktops/pipewire/wireplumber.nix
@@ -29,10 +29,6 @@ in
   config = lib.mkIf cfg.enable {
     assertions = [
       {
-        assertion = !config.services.pipewire.media-session.enable;
-        message = "WirePlumber and pipewire-media-session can't be enabled at the same time.";
-      }
-      {
         assertion = !config.hardware.bluetooth.hsphfpd.enable;
         message = "Using Wireplumber conflicts with hsphfpd, as it provides the same functionality. `hardware.bluetooth.hsphfpd.enable` needs be set to false";
       }
diff --git a/nixos/modules/services/hardware/kanata.nix b/nixos/modules/services/hardware/kanata.nix
index bb730037277b8..7d544050130b9 100644
--- a/nixos/modules/services/hardware/kanata.nix
+++ b/nixos/modules/services/hardware/kanata.nix
@@ -86,6 +86,7 @@ let
   mkService = name: keyboard: nameValuePair (mkName name) {
     wantedBy = [ "multi-user.target" ];
     serviceConfig = {
+      Type = "notify";
       ExecStart = ''
         ${getExe cfg.package} \
           --cfg ${mkConfig name keyboard} \
@@ -123,8 +124,7 @@ let
       ProtectKernelModules = true;
       ProtectKernelTunables = true;
       ProtectProc = "invisible";
-      RestrictAddressFamilies =
-        if (keyboard.port == null) then "none" else [ "AF_INET" ];
+      RestrictAddressFamilies = [ "AF_UNIX" ] ++ optional (keyboard.port != null) "AF_INET";
       RestrictNamespaces = true;
       RestrictRealtime = true;
       SystemCallArchitectures = [ "native" ];
diff --git a/nixos/modules/services/hardware/keyd.nix b/nixos/modules/services/hardware/keyd.nix
new file mode 100644
index 0000000000000..64c769405fabc
--- /dev/null
+++ b/nixos/modules/services/hardware/keyd.nix
@@ -0,0 +1,112 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.keyd;
+  settingsFormat = pkgs.formats.ini { };
+in
+{
+  options = {
+    services.keyd = {
+      enable = mkEnableOption (lib.mdDoc "keyd, a key remapping daemon");
+
+      ids = mkOption {
+        type = types.listOf types.string;
+        default = [ "*" ];
+        example = [ "*" "-0123:0456" ];
+        description = lib.mdDoc ''
+          Device identifiers, as shown by {manpage}`keyd(1)`.
+        '';
+      };
+
+      settings = mkOption {
+        type = settingsFormat.type;
+        default = { };
+        example = {
+          main = {
+            capslock = "overload(control, esc)";
+            rightalt = "layer(rightalt)";
+          };
+
+          rightalt = {
+            j = "down";
+            k = "up";
+            h = "left";
+            l = "right";
+          };
+        };
+        description = lib.mdDoc ''
+          Configuration, except `ids` section, that is written to {file}`/etc/keyd/default.conf`.
+          See <https://github.com/rvaiya/keyd> how to configure.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.etc."keyd/default.conf".source = pkgs.runCommand "default.conf"
+      {
+        ids = ''
+          [ids]
+          ${concatStringsSep "\n" cfg.ids}
+        '';
+        passAsFile = [ "ids" ];
+      } ''
+      cat $idsPath <(echo) ${settingsFormat.generate "keyd-main.conf" cfg.settings} >$out
+    '';
+
+    hardware.uinput.enable = lib.mkDefault true;
+
+    systemd.services.keyd = {
+      description = "Keyd remapping daemon";
+      documentation = [ "man:keyd(1)" ];
+
+      wantedBy = [ "multi-user.target" ];
+
+      restartTriggers = [
+        config.environment.etc."keyd/default.conf".source
+      ];
+
+      # this is configurable in 2.4.2, later versions seem to remove this option.
+      # post-2.4.2 may need to set makeFlags in the derivation:
+      #
+      #     makeFlags = [ "SOCKET_PATH/run/keyd/keyd.socket" ];
+      environment.KEYD_SOCKET = "/run/keyd/keyd.sock";
+
+      serviceConfig = {
+        ExecStart = "${pkgs.keyd}/bin/keyd";
+        Restart = "always";
+
+        DynamicUser = true;
+        SupplementaryGroups = [
+          config.users.groups.input.name
+          config.users.groups.uinput.name
+        ];
+
+        RuntimeDirectory = "keyd";
+
+        # Hardening
+        CapabilityBoundingSet = "";
+        DeviceAllow = [
+          "char-input rw"
+          "/dev/uinput rw"
+        ];
+        ProtectClock = true;
+        PrivateNetwork = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        PrivateUsers = true;
+        PrivateMounts = true;
+        RestrictNamespaces = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectControlGroups = true;
+        MemoryDenyWriteExecute = true;
+        RestrictRealtime = true;
+        LockPersonality = true;
+        ProtectProc = "noaccess";
+        UMask = "0077";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/hardware/supergfxd.nix b/nixos/modules/services/hardware/supergfxd.nix
index df339e4ba011f..5ea05ac277167 100644
--- a/nixos/modules/services/hardware/supergfxd.nix
+++ b/nixos/modules/services/hardware/supergfxd.nix
@@ -32,6 +32,7 @@ in
 
     systemd.packages = [ pkgs.supergfxctl ];
     systemd.services.supergfxd.wantedBy = [ "multi-user.target" ];
+    systemd.services.supergfxd.path = [ pkgs.kmod ];
 
     services.dbus.packages = [ pkgs.supergfxctl ];
     services.udev.packages = [ pkgs.supergfxctl ];
diff --git a/nixos/modules/services/hardware/undervolt.nix b/nixos/modules/services/hardware/undervolt.nix
index c49d944cdc188..944777475401b 100644
--- a/nixos/modules/services/hardware/undervolt.nix
+++ b/nixos/modules/services/hardware/undervolt.nix
@@ -5,8 +5,8 @@ let
   cfg = config.services.undervolt;
 
   mkPLimit = limit: window:
-    if (isNull limit && isNull window) then null
-    else assert asserts.assertMsg (!isNull limit && !isNull window) "Both power limit and window must be set";
+    if (limit == null && window == null) then null
+    else assert asserts.assertMsg (limit != null && window != null) "Both power limit and window must be set";
       "${toString limit} ${toString window}";
   cliArgs = lib.cli.toGNUCommandLine {} {
     inherit (cfg)
diff --git a/nixos/modules/services/home-automation/home-assistant.nix b/nixos/modules/services/home-automation/home-assistant.nix
index 6adc58ec58ec4..cea8a2b14cc22 100644
--- a/nixos/modules/services/home-automation/home-assistant.nix
+++ b/nixos/modules/services/home-automation/home-assistant.nix
@@ -362,7 +362,7 @@ in {
   config = mkIf cfg.enable {
     assertions = [
       {
-        assertion = cfg.openFirewall -> !isNull cfg.config;
+        assertion = cfg.openFirewall -> cfg.config != null;
         message = "openFirewall can only be used with a declarative config";
       }
     ];
diff --git a/nixos/modules/services/logging/logrotate.nix b/nixos/modules/services/logging/logrotate.nix
index 1799e9282b3b0..b056f96c3630b 100644
--- a/nixos/modules/services/logging/logrotate.nix
+++ b/nixos/modules/services/logging/logrotate.nix
@@ -187,7 +187,7 @@ in
           A configuration file automatically generated by NixOS.
         '';
         description = lib.mdDoc ''
-          Override the configuration file used by MySQL. By default,
+          Override the configuration file used by logrotate. By default,
           NixOS generates one automatically from [](#opt-services.logrotate.settings).
         '';
         example = literalExpression ''
diff --git a/nixos/modules/services/mail/roundcube.nix b/nixos/modules/services/mail/roundcube.nix
index 95dc2f6aa2c92..7b6d82219298c 100644
--- a/nixos/modules/services/mail/roundcube.nix
+++ b/nixos/modules/services/mail/roundcube.nix
@@ -132,6 +132,8 @@ in
       $config['plugins'] = [${concatMapStringsSep "," (p: "'${p}'") cfg.plugins}];
       $config['des_key'] = file_get_contents('/var/lib/roundcube/des_key');
       $config['mime_types'] = '${pkgs.nginx}/conf/mime.types';
+      # Roundcube uses PHP-FPM which has `PrivateTmp = true;`
+      $config['temp_dir'] = '/tmp';
       $config['enable_spellcheck'] = ${if cfg.dicts == [] then "false" else "true"};
       # by default, spellchecking uses a third-party cloud services
       $config['spellcheck_engine'] = 'pspell';
diff --git a/nixos/modules/services/matrix/synapse.md b/nixos/modules/services/matrix/synapse.md
index 7a9ddf8c9daf3..cad91ebf58d56 100644
--- a/nixos/modules/services/matrix/synapse.md
+++ b/nixos/modules/services/matrix/synapse.md
@@ -27,10 +27,7 @@ please refer to the
 { pkgs, lib, config, ... }:
 let
   fqdn = "${config.networking.hostName}.${config.networking.domain}";
-  clientConfig = {
-    "m.homeserver".base_url = "https://${fqdn}";
-    "m.identity_server" = {};
-  };
+  clientConfig."m.homeserver".base_url = "https://${fqdn}";
   serverConfig."m.server" = "${fqdn}:443";
   mkWellKnown = data: ''
     add_header Content-Type application/json;
diff --git a/nixos/modules/services/matrix/synapse.nix b/nixos/modules/services/matrix/synapse.nix
index aee275dab1ec1..b6b51b21c796f 100644
--- a/nixos/modules/services/matrix/synapse.nix
+++ b/nixos/modules/services/matrix/synapse.nix
@@ -60,7 +60,7 @@ in {
     '')
     (mkRemovedOptionModule [ "services" "matrix-synapse" "create_local_database" ] ''
       Database configuration must be done manually. An exemplary setup is demonstrated in
-      <nixpkgs/nixos/tests/matrix-synapse.nix>
+      <nixpkgs/nixos/tests/matrix/synapse.nix>
     '')
     (mkRemovedOptionModule [ "services" "matrix-synapse" "web_client" ] "")
     (mkRemovedOptionModule [ "services" "matrix-synapse" "room_invite_state_types" ] ''
@@ -711,7 +711,7 @@ in {
 
             If you
             - try to deploy a fresh synapse, you need to configure the database yourself. An example
-              for this can be found in <nixpkgs/nixos/tests/matrix-synapse.nix>
+              for this can be found in <nixpkgs/nixos/tests/matrix/synapse.nix>
             - update your existing matrix-synapse instance, you simply need to add `services.postgresql.enable = true`
               to your configuration.
 
diff --git a/nixos/modules/services/misc/gitea.nix b/nixos/modules/services/misc/gitea.nix
index 014c5b16097c9..e019e431a1890 100644
--- a/nixos/modules/services/misc/gitea.nix
+++ b/nixos/modules/services/misc/gitea.nix
@@ -365,6 +365,8 @@ in
     ];
 
     services.gitea.settings = {
+      "cron.update_checker".ENABLED = lib.mkDefault false;
+
       database = mkMerge [
         {
           DB_TYPE = cfg.database.type;
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index c7299c1ccad86..d278b571a6410 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -156,7 +156,7 @@ let
       };
       extra = {};
       uploads.storage_path = cfg.statePath;
-      pages = {
+      pages = optionalAttrs cfg.pages.enable {
         enabled = cfg.pages.enable;
         port = 8090;
         host = cfg.pages.settings.pages-domain;
diff --git a/nixos/modules/services/misc/portunus.nix b/nixos/modules/services/misc/portunus.nix
index f60cbe3477132..5504fb942968f 100644
--- a/nixos/modules/services/misc/portunus.nix
+++ b/nixos/modules/services/misc/portunus.nix
@@ -238,7 +238,7 @@ in
           PORTUNUS_SERVER_BINARY = "${cfg.package}/bin/portunus-server";
           PORTUNUS_SERVER_GROUP = cfg.group;
           PORTUNUS_SERVER_USER = cfg.user;
-          PORTUNUS_SERVER_HTTP_LISTEN = "[::]:${toString cfg.port}";
+          PORTUNUS_SERVER_HTTP_LISTEN = "127.0.0.1:${toString cfg.port}";
           PORTUNUS_SERVER_STATE_DIR = cfg.stateDir;
           PORTUNUS_SLAPD_BINARY = "${cfg.ldap.package}/libexec/slapd";
           PORTUNUS_SLAPD_GROUP = cfg.ldap.group;
diff --git a/nixos/modules/services/misc/sssd.nix b/nixos/modules/services/misc/sssd.nix
index edd5750a4a478..7c7a3b464a836 100644
--- a/nixos/modules/services/misc/sssd.nix
+++ b/nixos/modules/services/misc/sssd.nix
@@ -77,6 +77,10 @@ in {
   };
   config = mkMerge [
     (mkIf cfg.enable {
+      # For `sssctl` to work.
+      environment.etc."sssd/sssd.conf".source = settingsFile;
+      environment.etc."sssd/conf.d".source = "${dataDir}/conf.d";
+
       systemd.services.sssd = {
         description = "System Security Services Daemon";
         wantedBy    = [ "multi-user.target" ];
@@ -101,6 +105,7 @@ in {
           EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
         };
         preStart = ''
+          mkdir -p "${dataDir}/conf.d"
           [ -f ${settingsFile} ] && rm -f ${settingsFile}
           old_umask=$(umask)
           umask 0177
diff --git a/nixos/modules/services/misc/zoneminder.nix b/nixos/modules/services/misc/zoneminder.nix
index 109415a20ee63..11722979851c2 100644
--- a/nixos/modules/services/misc/zoneminder.nix
+++ b/nixos/modules/services/misc/zoneminder.nix
@@ -283,7 +283,8 @@ in {
       phpfpm = lib.mkIf useNginx {
         pools.zoneminder = {
           inherit user group;
-          phpPackage = pkgs.php.withExtensions ({ enabled, all }: enabled ++ [ all.apcu ]);
+          phpPackage = pkgs.php.withExtensions (
+            { enabled, all }: enabled ++ [ all.apcu all.sysvsem ]);
           phpOptions = ''
             date.timezone = "${config.time.timeZone}"
           '';
@@ -326,6 +327,15 @@ in {
           fi
 
           ${zoneminder}/bin/zmupdate.pl -nointeractive
+          ${zoneminder}/bin/zmupdate.pl --nointeractive -f
+
+          # Update ZM's Nix store path in the configuration table. Do nothing if the config doesn't
+          # contain ZM's Nix store path.
+          ${config.services.mysql.package}/bin/mysql -u zoneminder zm << EOF
+            UPDATE Config
+              SET Value = REGEXP_REPLACE(Value, "^/nix/store/[^-/]+-zoneminder-[^/]+", "${pkgs.zoneminder}")
+              WHERE Name = "ZM_FONT_FILE_LOCATION";
+          EOF
         '';
         serviceConfig = {
           User = user;
diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix
index ba3f89e24dd45..5a8c65b9dc3fb 100644
--- a/nixos/modules/services/monitoring/grafana.nix
+++ b/nixos/modules/services/monitoring/grafana.nix
@@ -1300,7 +1300,7 @@ in {
         SystemCallFilter = [
           "@system-service"
           "~@privileged"
-        ] ++ lib.optional (cfg.settings.server.protocol == "socket") [ "@chown" ];
+        ] ++ lib.optionals (cfg.settings.server.protocol == "socket") [ "@chown" ];
         UMask = "0027";
       };
       preStart = ''
diff --git a/nixos/modules/services/monitoring/prometheus/alertmanager-irc-relay.nix b/nixos/modules/services/monitoring/prometheus/alertmanager-irc-relay.nix
new file mode 100644
index 0000000000000..b81d5f6db5e08
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/alertmanager-irc-relay.nix
@@ -0,0 +1,107 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.alertmanagerIrcRelay;
+
+  configFormat = pkgs.formats.yaml { };
+  configFile = configFormat.generate "alertmanager-irc-relay.yml" cfg.settings;
+in
+{
+  options.services.prometheus.alertmanagerIrcRelay = {
+    enable = mkEnableOption (mdDoc "Alertmanager IRC Relay");
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.alertmanager-irc-relay;
+      defaultText = literalExpression "pkgs.alertmanager-irc-relay";
+      description = mdDoc "Alertmanager IRC Relay package to use.";
+    };
+
+    extraFlags = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = mdDoc "Extra command line options to pass to alertmanager-irc-relay.";
+    };
+
+    settings = mkOption {
+      type = configFormat.type;
+      example = literalExpression ''
+        {
+          http_host = "localhost";
+          http_port = 8000;
+
+          irc_host = "irc.example.com";
+          irc_port = 7000;
+          irc_nickname = "myalertbot";
+
+          irc_channels = [
+            { name = "#mychannel"; }
+          ];
+        }
+      '';
+      description = mdDoc ''
+        Configuration for Alertmanager IRC Relay as a Nix attribute set.
+        For a reference, check out the
+        [example configuration](https://github.com/google/alertmanager-irc-relay#configuring-and-running-the-bot)
+        and the
+        [source code](https://github.com/google/alertmanager-irc-relay/blob/master/config.go).
+
+        Note: The webhook's URL MUST point to the IRC channel where the message
+        should be posted. For `#mychannel` from the example, this would be
+        `http://localhost:8080/mychannel`.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.alertmanager-irc-relay = {
+      description = "Alertmanager IRC Relay";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" ];
+
+      serviceConfig = {
+        ExecStart = ''
+          ${cfg.package}/bin/alertmanager-irc-relay \
+          -config ${configFile} \
+          ${escapeShellArgs cfg.extraFlags}
+        '';
+
+        DynamicUser = true;
+        NoNewPrivileges = true;
+
+        ProtectProc = "invisible";
+        ProtectSystem = "strict";
+        ProtectHome = "tmpfs";
+
+        PrivateTmp = true;
+        PrivateDevices = true;
+        PrivateIPC = true;
+
+        ProtectHostname = true;
+        ProtectClock = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectKernelLogs = true;
+        ProtectControlGroups = true;
+
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+
+        SystemCallFilter = [
+          "@system-service"
+          "~@cpu-emulation"
+          "~@privileged"
+          "~@reboot"
+          "~@setuid"
+          "~@swap"
+        ];
+      };
+    };
+  };
+
+  meta.maintainers = [ maintainers.oxzi ];
+}
diff --git a/nixos/modules/services/monitoring/prometheus/default.nix b/nixos/modules/services/monitoring/prometheus/default.nix
index 4f197b9b5820e..fb3bab7963ea8 100644
--- a/nixos/modules/services/monitoring/prometheus/default.nix
+++ b/nixos/modules/services/monitoring/prometheus/default.nix
@@ -31,7 +31,7 @@ let
     if checkConfigEnabled then
       pkgs.runCommandLocal
         "${name}-${replaceStrings [" "] [""] what}-checked"
-        { buildInputs = [ cfg.package ]; } ''
+        { buildInputs = [ cfg.package.cli ]; } ''
         ln -s ${file} $out
         promtool ${what} $out
       '' else file;
diff --git a/nixos/modules/services/network-filesystems/kubo.nix b/nixos/modules/services/network-filesystems/kubo.nix
index 4d423c9059866..0cb0e126d4c50 100644
--- a/nixos/modules/services/network-filesystems/kubo.nix
+++ b/nixos/modules/services/network-filesystems/kubo.nix
@@ -171,7 +171,11 @@ in
                 "/ip4/0.0.0.0/tcp/4001"
                 "/ip6/::/tcp/4001"
                 "/ip4/0.0.0.0/udp/4001/quic"
+                "/ip4/0.0.0.0/udp/4001/quic-v1"
+                "/ip4/0.0.0.0/udp/4001/quic-v1/webtransport"
                 "/ip6/::/udp/4001/quic"
+                "/ip6/::/udp/4001/quic-v1"
+                "/ip6/::/udp/4001/quic-v1/webtransport"
               ];
               description = lib.mdDoc "Where Kubo listens for incoming p2p connections";
             };
diff --git a/nixos/modules/services/networking/avahi-daemon.nix b/nixos/modules/services/networking/avahi-daemon.nix
index 103f73fdaa685..3a7519c7230b3 100644
--- a/nixos/modules/services/networking/avahi-daemon.nix
+++ b/nixos/modules/services/networking/avahi-daemon.nix
@@ -5,7 +5,7 @@ with lib;
 let
   cfg = config.services.avahi;
 
-  yesNo = yes : if yes then "yes" else "no";
+  yesNo = yes: if yes then "yes" else "no";
 
   avahiDaemonConf = with cfg; pkgs.writeText "avahi-daemon.conf" ''
     [server]
@@ -17,7 +17,8 @@ let
     browse-domains=${concatStringsSep ", " browseDomains}
     use-ipv4=${yesNo ipv4}
     use-ipv6=${yesNo ipv6}
-    ${optionalString (interfaces!=null) "allow-interfaces=${concatStringsSep "," interfaces}"}
+    ${optionalString (allowInterfaces!=null) "allow-interfaces=${concatStringsSep "," allowInterfaces}"}
+    ${optionalString (denyInterfaces!=null) "deny-interfaces=${concatStringsSep "," denyInterfaces}"}
     ${optionalString (domainName!=null) "domain-name=${domainName}"}
     allow-point-to-point=${yesNo allowPointToPoint}
     ${optionalString (cacheEntriesMax!=null) "cache-entries-max=${toString cacheEntriesMax}"}
@@ -39,6 +40,10 @@ let
   '';
 in
 {
+  imports = [
+    (lib.mkRenamedOptionModule [ "services" "avahi" "interfaces" ] [ "services" "avahi" "allowInterfaces" ])
+  ];
+
   options.services.avahi = {
     enable = mkOption {
       type = types.bool;
@@ -91,7 +96,7 @@ in
       description = lib.mdDoc "Whether to use IPv6.";
     };
 
-    interfaces = mkOption {
+    allowInterfaces = mkOption {
       type = types.nullOr (types.listOf types.str);
       default = null;
       description = lib.mdDoc ''
@@ -101,6 +106,17 @@ in
       '';
     };
 
+    denyInterfaces = mkOption {
+      type = types.nullOr (types.listOf types.str);
+      default = null;
+      description = lib.mdDoc ''
+        List of network interfaces that should be ignored by the
+        {command}`avahi-daemon`. Other unspecified interfaces will be used,
+        unless {option}`allowInterfaces` is set. This option takes precedence
+        over {option}`allowInterfaces`.
+      '';
+    };
+
     openFirewall = mkOption {
       type = types.bool;
       default = true;
@@ -134,7 +150,7 @@ in
 
     extraServiceFiles = mkOption {
       type = with types; attrsOf (either str path);
-      default = {};
+      default = { };
       example = literalExpression ''
         {
           ssh = "''${pkgs.avahi}/etc/avahi/services/ssh.service";
@@ -236,7 +252,7 @@ in
       isSystemUser = true;
     };
 
-    users.groups.avahi = {};
+    users.groups.avahi = { };
 
     system.nssModules = optional cfg.nssmdns pkgs.nssmdns;
     system.nssDatabases.hosts = optionals cfg.nssmdns (mkMerge [
@@ -246,10 +262,12 @@ in
 
     environment.systemPackages = [ pkgs.avahi ];
 
-    environment.etc = (mapAttrs' (n: v: nameValuePair
-      "avahi/services/${n}.service"
-      { ${if types.path.check v then "source" else "text"} = v; }
-    ) cfg.extraServiceFiles);
+    environment.etc = (mapAttrs'
+      (n: v: nameValuePair
+        "avahi/services/${n}.service"
+        { ${if types.path.check v then "source" else "text"} = v; }
+      )
+      cfg.extraServiceFiles);
 
     systemd.sockets.avahi-daemon = {
       description = "Avahi mDNS/DNS-SD Stack Activation Socket";
diff --git a/nixos/modules/services/networking/firewall-nftables.nix b/nixos/modules/services/networking/firewall-nftables.nix
index 0ed3c228075d3..452dd97d89d29 100644
--- a/nixos/modules/services/networking/firewall-nftables.nix
+++ b/nixos/modules/services/networking/firewall-nftables.nix
@@ -94,7 +94,13 @@ in
           ${optionalString (ifaceSet != "") ''iifname { ${ifaceSet} } accept comment "trusted interfaces"''}
 
           # Some ICMPv6 types like NDP is untracked
-          ct state vmap { invalid : drop, established : accept, related : accept, * : jump input-allow } comment "*: new and untracked"
+          ct state vmap {
+            invalid : drop,
+            established : accept,
+            related : accept,
+            new : jump input-allow,
+            untracked: jump input-allow,
+          }
 
           ${optionalString cfg.logRefusedConnections ''
             tcp flags syn / fin,syn,rst,ack log level info prefix "refused connection: "
@@ -143,7 +149,13 @@ in
           chain forward {
             type filter hook forward priority filter; policy drop;
 
-            ct state vmap { invalid : drop, established : accept, related : accept, * : jump forward-allow } comment "*: new and untracked"
+            ct state vmap {
+              invalid : drop,
+              established : accept,
+              related : accept,
+              new : jump forward-allow,
+              untracked : jump forward-allow,
+            }
 
           }
 
diff --git a/nixos/modules/services/networking/headscale.nix b/nixos/modules/services/networking/headscale.nix
index 390a448ab5842..d2851e72a0dd6 100644
--- a/nixos/modules/services/networking/headscale.nix
+++ b/nixos/modules/services/networking/headscale.nix
@@ -291,11 +291,11 @@ in {
                 '';
               };
 
-              client_secret_file = mkOption {
+              client_secret_path = mkOption {
                 type = types.nullOr types.path;
                 default = null;
                 description = lib.mdDoc ''
-                  Path to OpenID Connect client secret file.
+                  Path to OpenID Connect client secret file. Expands environment variables in format ''${VAR}.
                 '';
               };
 
@@ -425,7 +425,7 @@ in {
     (mkRenamedOptionModule ["services" "headscale" "dns" "baseDomain"] ["services" "headscale" "settings" "dns_config" "base_domain"])
     (mkRenamedOptionModule ["services" "headscale" "openIdConnect" "issuer"] ["services" "headscale" "settings" "oidc" "issuer"])
     (mkRenamedOptionModule ["services" "headscale" "openIdConnect" "clientId"] ["services" "headscale" "settings" "oidc" "client_id"])
-    (mkRenamedOptionModule ["services" "headscale" "openIdConnect" "clientSecretFile"] ["services" "headscale" "settings" "oidc" "client_secret_file"])
+    (mkRenamedOptionModule ["services" "headscale" "openIdConnect" "clientSecretFile"] ["services" "headscale" "settings" "oidc" "client_secret_path"])
     (mkRenamedOptionModule ["services" "headscale" "tls" "letsencrypt" "hostname"] ["services" "headscale" "settings" "tls_letsencrypt_hostname"])
     (mkRenamedOptionModule ["services" "headscale" "tls" "letsencrypt" "challengeType"] ["services" "headscale" "settings" "tls_letsencrypt_challenge_type"])
     (mkRenamedOptionModule ["services" "headscale" "tls" "letsencrypt" "httpListen"] ["services" "headscale" "settings" "tls_letsencrypt_listen"])
@@ -478,9 +478,6 @@ in {
           export HEADSCALE_DB_PASS="$(head -n1 ${escapeShellArg cfg.settings.db_password_file})"
         ''}
 
-        ${optionalString (cfg.settings.oidc.client_secret_file != null) ''
-          export HEADSCALE_OIDC_CLIENT_SECRET="$(head -n1 ${escapeShellArg cfg.settings.oidc.client_secret_file})"
-        ''}
         exec ${cfg.package}/bin/headscale serve
       '';
 
diff --git a/nixos/modules/services/networking/jicofo.nix b/nixos/modules/services/networking/jicofo.nix
index 5e97889607363..0886bbe004c46 100644
--- a/nixos/modules/services/networking/jicofo.nix
+++ b/nixos/modules/services/networking/jicofo.nix
@@ -4,6 +4,15 @@ with lib;
 
 let
   cfg = config.services.jicofo;
+
+  # HOCON is a JSON superset that some jitsi-meet components use for configuration
+  toHOCON = x: if isAttrs x && x ? __hocon_envvar then ("\${" + x.__hocon_envvar + "}")
+    else if isAttrs x && x ? __hocon_unquoted_string then x.__hocon_unquoted_string
+    else if isAttrs x then "{${ concatStringsSep "," (mapAttrsToList (k: v: ''"${k}":${toHOCON v}'') x) }}"
+    else if isList x then "[${ concatMapStringsSep "," toHOCON x }]"
+    else builtins.toJSON x;
+
+  configFile = pkgs.writeText "jicofo.conf" (toHOCON cfg.config);
 in
 {
   options.services.jicofo = with types; {
@@ -68,22 +77,34 @@ in
     };
 
     config = mkOption {
-      type = attrsOf str;
+      type = (pkgs.formats.json {}).type;
       default = { };
       example = literalExpression ''
         {
-          "org.jitsi.jicofo.auth.URL" = "XMPP:jitsi-meet.example.com";
+          jicofo.bridge.max-bridge-participants = 42;
         }
       '';
       description = lib.mdDoc ''
-        Contents of the {file}`sip-communicator.properties` configuration file for jicofo.
+        Contents of the {file}`jicofo.conf` configuration file.
       '';
     };
   };
 
   config = mkIf cfg.enable {
-    services.jicofo.config = mapAttrs (_: v: mkDefault v) {
-      "org.jitsi.jicofo.BRIDGE_MUC" = cfg.bridgeMuc;
+    services.jicofo.config = {
+      jicofo = {
+        bridge.brewery-jid = cfg.bridgeMuc;
+        xmpp = rec {
+          client = {
+            hostname = cfg.xmppHost;
+            username = cfg.userName;
+            domain = cfg.userDomain;
+            password = { __hocon_envvar = "JICOFO_AUTH_PASS"; };
+            xmpp-domain = if cfg.xmppDomain == null then cfg.xmppHost else cfg.xmppDomain;
+          };
+          service = client;
+        };
+      };
     };
 
     users.groups.jitsi-meet = {};
@@ -93,6 +114,7 @@ in
         "-Dnet.java.sip.communicator.SC_HOME_DIR_LOCATION" = "/etc/jitsi";
         "-Dnet.java.sip.communicator.SC_HOME_DIR_NAME" = "jicofo";
         "-Djava.util.logging.config.file" = "/etc/jitsi/jicofo/logging.properties";
+        "-Dconfig.file" = configFile;
       };
     in
     {
@@ -101,18 +123,13 @@ in
       after = [ "network.target" ];
 
       restartTriggers = [
-        config.environment.etc."jitsi/jicofo/sip-communicator.properties".source
+        configFile
       ];
       environment.JAVA_SYS_PROPS = concatStringsSep " " (mapAttrsToList (k: v: "${k}=${toString v}") jicofoProps);
 
       script = ''
-        ${pkgs.jicofo}/bin/jicofo \
-          --host=${cfg.xmppHost} \
-          --domain=${if cfg.xmppDomain == null then cfg.xmppHost else cfg.xmppDomain} \
-          --secret=$(cat ${cfg.componentPasswordFile}) \
-          --user_name=${cfg.userName} \
-          --user_domain=${cfg.userDomain} \
-          --user_password=$(cat ${cfg.userPasswordFile})
+        export JICOFO_AUTH_PASS="$(<${cfg.userPasswordFile})"
+        exec "${pkgs.jicofo}/bin/jicofo"
       '';
 
       serviceConfig = {
@@ -140,10 +157,7 @@ in
       };
     };
 
-    environment.etc."jitsi/jicofo/sip-communicator.properties".source =
-      pkgs.writeText "sip-communicator.properties" (
-        generators.toKeyValue {} cfg.config
-      );
+    environment.etc."jitsi/jicofo/sip-communicator.properties".text = "";
     environment.etc."jitsi/jicofo/logging.properties".source =
       mkDefault "${pkgs.jicofo}/etc/jitsi/jicofo/logging.properties-journal";
   };
diff --git a/nixos/modules/services/networking/multipath.nix b/nixos/modules/services/networking/multipath.nix
index b20ec76ddf594..bd403e109c2af 100644
--- a/nixos/modules/services/networking/multipath.nix
+++ b/nixos/modules/services/networking/multipath.nix
@@ -513,22 +513,22 @@ in {
         ${indentLines 2 devices}
         }
 
-        ${optionalString (!isNull defaults) ''
+        ${optionalString (defaults != null) ''
           defaults {
           ${indentLines 2 defaults}
           }
         ''}
-        ${optionalString (!isNull blacklist) ''
+        ${optionalString (blacklist != null) ''
           blacklist {
           ${indentLines 2 blacklist}
           }
         ''}
-        ${optionalString (!isNull blacklist_exceptions) ''
+        ${optionalString (blacklist_exceptions != null) ''
           blacklist_exceptions {
           ${indentLines 2 blacklist_exceptions}
           }
         ''}
-        ${optionalString (!isNull overrides) ''
+        ${optionalString (overrides != null) ''
           overrides {
           ${indentLines 2 overrides}
           }
diff --git a/nixos/modules/services/networking/peroxide.nix b/nixos/modules/services/networking/peroxide.nix
new file mode 100644
index 0000000000000..6cac4bf2f89a1
--- /dev/null
+++ b/nixos/modules/services/networking/peroxide.nix
@@ -0,0 +1,131 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.peroxide;
+  settingsFormat = pkgs.formats.yaml { };
+  stateDir = "peroxide";
+in
+{
+  options.services.peroxide = {
+    enable = mkEnableOption (lib.mdDoc "enable");
+
+    package = mkPackageOptionMD pkgs "peroxide" {
+      default = [ "peroxide" ];
+    };
+
+    logLevel = mkOption {
+      # https://github.com/sirupsen/logrus#level-logging
+      type = types.enum [ "Panic" "Fatal" "Error" "Warning" "Info" "Debug" "Trace" ];
+      default = "Warning";
+      example = "Info";
+      description = lib.mdDoc "Only log messages of this priority or higher.";
+    };
+
+    settings = mkOption {
+      type = types.submodule {
+        freeformType = settingsFormat.type;
+
+        options = {
+          UserPortImap = mkOption {
+            type = types.port;
+            default = 1143;
+            description = lib.mdDoc "The port on which to listen for IMAP connections.";
+          };
+
+          UserPortSmtp = mkOption {
+            type = types.port;
+            default = 1025;
+            description = lib.mdDoc "The port on which to listen for SMTP connections.";
+          };
+
+          ServerAddress = mkOption {
+            type = types.str;
+            default = "[::0]";
+            example = "localhost";
+            description = lib.mdDoc "The address on which to listen for connections.";
+          };
+        };
+      };
+      default = { };
+      description = lib.mdDoc ''
+        Configuration for peroxide.  See
+        [config.example.yaml](https://github.com/ljanyst/peroxide/blob/master/config.example.yaml)
+        for an example configuration.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.peroxide.settings = {
+      # peroxide deletes the cache directory on startup, which requires write
+      # permission on the parent directory, so we can't use
+      # /var/cache/peroxide
+      CacheDir = "/var/cache/peroxide/cache";
+      X509Key = mkDefault "/var/lib/${stateDir}/key.pem";
+      X509Cert = mkDefault "/var/lib/${stateDir}/cert.pem";
+      CookieJar = "/var/lib/${stateDir}/cookies.json";
+      CredentialsStore = "/var/lib/${stateDir}/credentials.json";
+    };
+
+    users.users.peroxide = {
+      isSystemUser = true;
+      group = "peroxide";
+    };
+    users.groups.peroxide = { };
+
+    systemd.services.peroxide = {
+      description = "Peroxide ProtonMail bridge";
+      requires = [ "network.target" ];
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      restartTriggers = [ config.environment.etc."peroxide.conf".source ];
+
+      serviceConfig = {
+        Type = "simple";
+        User = "peroxide";
+        LogsDirectory = "peroxide";
+        LogsDirectoryMode = "0750";
+        # Specify just "peroxide" so that the user has write permission, because
+        # peroxide deletes and recreates the cache directory on startup.
+        CacheDirectory = [ "peroxide" "peroxide/cache" ];
+        CacheDirectoryMode = "0700";
+        StateDirectory = stateDir;
+        StateDirectoryMode = "0700";
+        ExecStart = "${cfg.package}/bin/peroxide -log-file=/var/log/peroxide/peroxide.log -log-level ${cfg.logLevel}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+      };
+
+      preStart = ''
+        # Create a self-signed certificate if no certificate exists.
+        if [[ ! -e "${cfg.settings.X509Key}" && ! -e "${cfg.settings.X509Cert}" ]]; then
+            ${cfg.package}/bin/peroxide-cfg -action gen-x509 \
+              -x509-org 'N/A' \
+              -x509-cn 'nixos' \
+              -x509-cert "${cfg.settings.X509Cert}" \
+              -x509-key "${cfg.settings.X509Key}"
+        fi
+      '';
+    };
+
+    # https://github.com/ljanyst/peroxide/blob/master/peroxide.logrotate
+    services.logrotate.settings.peroxide = {
+      files = "/var/log/peroxide/peroxide.log";
+      rotate = 31;
+      frequency = "daily";
+      compress = true;
+      delaycompress = true;
+      missingok = true;
+      notifempty = true;
+      su = "peroxide peroxide";
+      postrotate = "systemctl reload peroxide";
+    };
+
+    environment.etc."peroxide.conf".source = settingsFormat.generate "peroxide.conf" cfg.settings;
+    environment.systemPackages = [ cfg.package ];
+  };
+
+  meta.maintainers = with maintainers; [ aanderse aidalgol ];
+}
diff --git a/nixos/modules/services/networking/radicale.nix b/nixos/modules/services/networking/radicale.nix
index 8e4789c7ca597..00dbd6bbe386d 100644
--- a/nixos/modules/services/networking/radicale.nix
+++ b/nixos/modules/services/networking/radicale.nix
@@ -9,7 +9,7 @@ let
     listToValue = concatMapStringsSep ", " (generators.mkValueStringDefault { });
   };
 
-  pkg = if isNull cfg.package then
+  pkg = if cfg.package == null then
     pkgs.radicale
   else
     cfg.package;
@@ -117,13 +117,13 @@ in {
       }
     ];
 
-    warnings = optional (isNull cfg.package && versionOlder config.system.stateVersion "17.09") ''
+    warnings = optional (cfg.package == null && versionOlder config.system.stateVersion "17.09") ''
       The configuration and storage formats of your existing Radicale
       installation might be incompatible with the newest version.
       For upgrade instructions see
       https://radicale.org/2.1.html#documentation/migration-from-1xx-to-2xx.
       Set services.radicale.package to suppress this warning.
-    '' ++ optional (isNull cfg.package && versionOlder config.system.stateVersion "20.09") ''
+    '' ++ optional (cfg.package == null && versionOlder config.system.stateVersion "20.09") ''
       The configuration format of your existing Radicale installation might be
       incompatible with the newest version.  For upgrade instructions see
       https://github.com/Kozea/Radicale/blob/3.0.6/NEWS.md#upgrade-checklist.
diff --git a/nixos/modules/services/networking/ssh/sshd.nix b/nixos/modules/services/networking/ssh/sshd.nix
index 095c7de0b7aa1..5f225682b7779 100644
--- a/nixos/modules/services/networking/ssh/sshd.nix
+++ b/nixos/modules/services/networking/ssh/sshd.nix
@@ -19,7 +19,7 @@ let
         else if true  ==   v then "yes"
         else if false ==   v then "no"
         else if isList     v then concatStringsSep "," v
-        else throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty {}) v}";
+        else throw "unsupported type ${builtins.typeOf v}: ${(lib.generators.toPretty {}) v}";
 
   # dont use the "=" operator
   settingsFormat = (pkgs.formats.keyValue {
diff --git a/nixos/modules/services/networking/yggdrasil.nix b/nixos/modules/services/networking/yggdrasil.nix
index fd7193154c6c7..55a6002d61af1 100644
--- a/nixos/modules/services/networking/yggdrasil.nix
+++ b/nixos/modules/services/networking/yggdrasil.nix
@@ -8,7 +8,8 @@ let
   configFileProvided = cfg.configFile != null;
 
   format = pkgs.formats.json { };
-in {
+in
+{
   imports = [
     (mkRenamedOptionModule
       [ "services" "yggdrasil" "config" ]
@@ -21,7 +22,7 @@ in {
 
       settings = mkOption {
         type = format.type;
-        default = {};
+        default = { };
         example = {
           Peers = [
             "tcp://aa.bb.cc.dd:eeeee"
@@ -45,7 +46,7 @@ in {
 
           If no keys are specified then ephemeral keys are generated
           and the Yggdrasil interface will have a random IPv6 address
-          each time the service is started, this is the default.
+          each time the service is started. This is the default.
 
           If both {option}`configFile` and {option}`settings`
           are supplied, they will be combined, with values from
@@ -61,8 +62,13 @@ in {
         default = null;
         example = "/run/keys/yggdrasil.conf";
         description = lib.mdDoc ''
-          A file which contains JSON configuration for yggdrasil.
-          See the {option}`settings` option for more information.
+          A file which contains JSON or HJSON configuration for yggdrasil. See
+          the {option}`settings` option for more information.
+
+          Note: This file must not be larger than 1 MB because it is passed to
+          the yggdrasil process via systemd‘s LoadCredential mechanism. For
+          details, see <https://systemd.io/CREDENTIALS/> and `man 5
+          systemd.exec`.
         '';
       };
 
@@ -77,20 +83,20 @@ in {
         type = bool;
         default = false;
         description = lib.mdDoc ''
-          Whether to open the UDP port used for multicast peer
-          discovery. The NixOS firewall blocks link-local
-          communication, so in order to make local peering work you
-          will also need to set `LinkLocalTCPPort` in your
-          yggdrasil configuration ({option}`settings` or
-          {option}`configFile`) to a port number other than 0,
-          and then add that port to
-          {option}`networking.firewall.allowedTCPPorts`.
+          Whether to open the UDP port used for multicast peer discovery. The
+          NixOS firewall blocks link-local communication, so in order to make
+          incoming local peering work you will also need to configure
+          `MulticastInterfaces` in your Yggdrasil configuration
+          ({option}`settings` or {option}`configFile`). You will then have to
+          add the ports that you configure there to your firewall configuration
+          ({option}`networking.firewall.allowedTCPPorts` or
+          {option}`networking.firewall.interfaces.<name>.allowedTCPPorts`).
         '';
       };
 
       denyDhcpcdInterfaces = mkOption {
         type = listOf str;
-        default = [];
+        default = [ ];
         example = [ "tap*" ];
         description = lib.mdDoc ''
           Disable the DHCP client for any interface whose name matches
@@ -118,80 +124,102 @@ in {
     };
   };
 
-  config = mkIf cfg.enable (let binYggdrasil = cfg.package + "/bin/yggdrasil";
-  in {
-    assertions = [{
-      assertion = config.networking.enableIPv6;
-      message = "networking.enableIPv6 must be true for yggdrasil to work";
-    }];
-
-    system.activationScripts.yggdrasil = mkIf cfg.persistentKeys ''
-      if [ ! -e ${keysPath} ]
-      then
-        mkdir --mode=700 -p ${builtins.dirOf keysPath}
-        ${binYggdrasil} -genconf -json \
-          | ${pkgs.jq}/bin/jq \
-              'to_entries|map(select(.key|endswith("Key")))|from_entries' \
-          > ${keysPath}
-      fi
-    '';
-
-    systemd.services.yggdrasil = {
-      description = "Yggdrasil Network Service";
-      after = [ "network-pre.target" ];
-      wants = [ "network.target" ];
-      before = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
-
-      preStart =
-        (if settingsProvided || configFileProvided || cfg.persistentKeys then
-          "echo "
-
-          + (lib.optionalString settingsProvided
-            "'${builtins.toJSON cfg.settings}'")
-          + (lib.optionalString configFileProvided "$(cat ${cfg.configFile})")
-          + (lib.optionalString cfg.persistentKeys "$(cat ${keysPath})")
-          + " | ${pkgs.jq}/bin/jq -s add | ${binYggdrasil} -normaliseconf -useconf"
-        else
-          "${binYggdrasil} -genconf") + " > /run/yggdrasil/yggdrasil.conf";
-
-      serviceConfig = {
-        ExecStart =
-          "${binYggdrasil} -useconffile /run/yggdrasil/yggdrasil.conf";
-        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
-        Restart = "always";
-
-        DynamicUser = true;
-        StateDirectory = "yggdrasil";
-        RuntimeDirectory = "yggdrasil";
-        RuntimeDirectoryMode = "0750";
-        BindReadOnlyPaths = lib.optional configFileProvided cfg.configFile
-          ++ lib.optional cfg.persistentKeys keysPath;
-        ReadWritePaths = "/run/yggdrasil";
-
-        AmbientCapabilities = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE";
-        CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE";
-        MemoryDenyWriteExecute = true;
-        ProtectControlGroups = true;
-        ProtectHome = "tmpfs";
-        ProtectKernelModules = true;
-        ProtectKernelTunables = true;
-        RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
-        RestrictNamespaces = true;
-        RestrictRealtime = true;
-        SystemCallArchitectures = "native";
-        SystemCallFilter = [ "@system-service" "~@privileged @keyring" ];
-      } // (if (cfg.group != null) then {
-        Group = cfg.group;
-      } else {});
-    };
+  config = mkIf cfg.enable (
+    let
+      binYggdrasil = "${cfg.package}/bin/yggdrasil";
+      binHjson = "${pkgs.hjson-go}/bin/hjson-cli";
+    in
+    {
+      assertions = [{
+        assertion = config.networking.enableIPv6;
+        message = "networking.enableIPv6 must be true for yggdrasil to work";
+      }];
+
+      system.activationScripts.yggdrasil = mkIf cfg.persistentKeys ''
+        if [ ! -e ${keysPath} ]
+        then
+          mkdir --mode=700 -p ${builtins.dirOf keysPath}
+          ${binYggdrasil} -genconf -json \
+            | ${pkgs.jq}/bin/jq \
+                'to_entries|map(select(.key|endswith("Key")))|from_entries' \
+            > ${keysPath}
+        fi
+      '';
+
+      systemd.services.yggdrasil = {
+        description = "Yggdrasil Network Service";
+        after = [ "network-pre.target" ];
+        wants = [ "network.target" ];
+        before = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+
+        # This script first prepares the config file, then it starts Yggdrasil.
+        # The preparation could also be done in ExecStartPre/preStart but only
+        # systemd versions >= v252 support reading credentials in ExecStartPre. As
+        # of February 2023, systemd v252 is not yet in the stable branch of NixOS.
+        #
+        # This could be changed in the future once systemd version v252 has
+        # reached NixOS but it does not have to be. Config file preparation is
+        # fast enough, it does not need elevated privileges, and `set -euo
+        # pipefail` should make sure that the service is not started if the
+        # preparation fails. Therefore, it is not necessary to move the
+        # preparation to ExecStartPre.
+        script = ''
+          set -euo pipefail
+
+          # prepare config file
+          ${(if settingsProvided || configFileProvided || cfg.persistentKeys then
+            "echo "
+
+            + (lib.optionalString settingsProvided
+              "'${builtins.toJSON cfg.settings}'")
+            + (lib.optionalString configFileProvided
+              "$(${binHjson} -c \"$CREDENTIALS_DIRECTORY/yggdrasil.conf\")")
+            + (lib.optionalString cfg.persistentKeys "$(cat ${keysPath})")
+            + " | ${pkgs.jq}/bin/jq -s add | ${binYggdrasil} -normaliseconf -useconf"
+          else
+            "${binYggdrasil} -genconf") + " > /run/yggdrasil/yggdrasil.conf"}
+
+          # start yggdrasil
+          ${binYggdrasil} -useconffile /run/yggdrasil/yggdrasil.conf
+        '';
+
+        serviceConfig = {
+          ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+          Restart = "always";
+
+          DynamicUser = true;
+          StateDirectory = "yggdrasil";
+          RuntimeDirectory = "yggdrasil";
+          RuntimeDirectoryMode = "0750";
+          BindReadOnlyPaths = lib.optional cfg.persistentKeys keysPath;
+          LoadCredential =
+            mkIf configFileProvided "yggdrasil.conf:${cfg.configFile}";
+
+          AmbientCapabilities = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE";
+          CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE";
+          MemoryDenyWriteExecute = true;
+          ProtectControlGroups = true;
+          ProtectHome = "tmpfs";
+          ProtectKernelModules = true;
+          ProtectKernelTunables = true;
+          RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
+          RestrictNamespaces = true;
+          RestrictRealtime = true;
+          SystemCallArchitectures = "native";
+          SystemCallFilter = [ "@system-service" "~@privileged @keyring" ];
+        } // (if (cfg.group != null) then {
+          Group = cfg.group;
+        } else { });
+      };
 
-    networking.dhcpcd.denyInterfaces = cfg.denyDhcpcdInterfaces;
-    networking.firewall.allowedUDPPorts = mkIf cfg.openMulticastPort [ 9001 ];
+      networking.dhcpcd.denyInterfaces = cfg.denyDhcpcdInterfaces;
+      networking.firewall.allowedUDPPorts = mkIf cfg.openMulticastPort [ 9001 ];
 
-    # Make yggdrasilctl available on the command line.
-    environment.systemPackages = [ cfg.package ];
-  });
+      # Make yggdrasilctl available on the command line.
+      environment.systemPackages = [ cfg.package ];
+    }
+  );
   meta = {
     doc = ./yggdrasil.md;
     maintainers = with lib.maintainers; [ gazally ehmry ];
diff --git a/nixos/modules/services/search/solr.nix b/nixos/modules/services/search/solr.nix
deleted file mode 100644
index 05592e9fa247d..0000000000000
--- a/nixos/modules/services/search/solr.nix
+++ /dev/null
@@ -1,110 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  cfg = config.services.solr;
-
-in
-
-{
-  options = {
-    services.solr = {
-      enable = mkEnableOption (lib.mdDoc "Solr");
-
-      package = mkOption {
-        type = types.package;
-        default = pkgs.solr;
-        defaultText = literalExpression "pkgs.solr";
-        description = lib.mdDoc "Which Solr package to use.";
-      };
-
-      port = mkOption {
-        type = types.port;
-        default = 8983;
-        description = lib.mdDoc "Port on which Solr is ran.";
-      };
-
-      stateDir = mkOption {
-        type = types.path;
-        default = "/var/lib/solr";
-        description = lib.mdDoc "The solr home directory containing config, data, and logging files.";
-      };
-
-      extraJavaOptions = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        description = lib.mdDoc "Extra command line options given to the java process running Solr.";
-      };
-
-      user = mkOption {
-        type = types.str;
-        default = "solr";
-        description = lib.mdDoc "User under which Solr is ran.";
-      };
-
-      group = mkOption {
-        type = types.str;
-        default = "solr";
-        description = lib.mdDoc "Group under which Solr is ran.";
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-
-    environment.systemPackages = [ cfg.package ];
-
-    systemd.services.solr = {
-      after = [ "network.target" "remote-fs.target" "nss-lookup.target" "systemd-journald-dev-log.socket" ];
-      wantedBy = [ "multi-user.target" ];
-
-      environment = {
-        SOLR_HOME = "${cfg.stateDir}/data";
-        LOG4J_PROPS = "${cfg.stateDir}/log4j2.xml";
-        SOLR_LOGS_DIR = "${cfg.stateDir}/logs";
-        SOLR_PORT = "${toString cfg.port}";
-      };
-      path = with pkgs; [
-        gawk
-        procps
-      ];
-      preStart = ''
-        mkdir -p "${cfg.stateDir}/data";
-        mkdir -p "${cfg.stateDir}/logs";
-
-        if ! test -e "${cfg.stateDir}/data/solr.xml"; then
-          install -D -m0640 ${cfg.package}/server/solr/solr.xml "${cfg.stateDir}/data/solr.xml"
-          install -D -m0640 ${cfg.package}/server/solr/zoo.cfg "${cfg.stateDir}/data/zoo.cfg"
-        fi
-
-        if ! test -e "${cfg.stateDir}/log4j2.xml"; then
-          install -D -m0640 ${cfg.package}/server/resources/log4j2.xml "${cfg.stateDir}/log4j2.xml"
-        fi
-      '';
-
-      serviceConfig = {
-        User = cfg.user;
-        Group = cfg.group;
-        ExecStart="${cfg.package}/bin/solr start -f -a \"${concatStringsSep " " cfg.extraJavaOptions}\"";
-        ExecStop="${cfg.package}/bin/solr stop";
-      };
-    };
-
-    users.users = optionalAttrs (cfg.user == "solr") {
-      solr = {
-        group = cfg.group;
-        home = cfg.stateDir;
-        createHome = true;
-        uid = config.ids.uids.solr;
-      };
-    };
-
-    users.groups = optionalAttrs (cfg.group == "solr") {
-      solr.gid = config.ids.gids.solr;
-    };
-
-  };
-
-}
diff --git a/nixos/modules/services/security/authelia.nix b/nixos/modules/services/security/authelia.nix
new file mode 100644
index 0000000000000..143c441c7e153
--- /dev/null
+++ b/nixos/modules/services/security/authelia.nix
@@ -0,0 +1,401 @@
+{ lib
+, pkgs
+, config
+, ...
+}:
+
+let
+  cfg = config.services.authelia;
+
+  format = pkgs.formats.yaml { };
+  configFile = format.generate "config.yml" cfg.settings;
+
+  autheliaOpts = with lib; { name, ... }: {
+    options = {
+      enable = mkEnableOption (mdDoc "Authelia instance");
+
+      name = mkOption {
+        type = types.str;
+        default = name;
+        description = mdDoc ''
+          Name is used as a suffix for the service name, user, and group.
+          By default it takes the value you use for `<instance>` in:
+          {option}`services.authelia.<instance>`
+        '';
+      };
+
+      package = mkOption {
+        default = pkgs.authelia;
+        type = types.package;
+        defaultText = literalExpression "pkgs.authelia";
+        description = mdDoc "Authelia derivation to use.";
+      };
+
+      user = mkOption {
+        default = "authelia-${name}";
+        type = types.str;
+        description = mdDoc "The name of the user for this authelia instance.";
+      };
+
+      group = mkOption {
+        default = "authelia-${name}";
+        type = types.str;
+        description = mdDoc "The name of the group for this authelia instance.";
+      };
+
+      secrets = mkOption {
+        description = mdDoc ''
+          It is recommended you keep your secrets separate from the configuration.
+          It's especially important to keep the raw secrets out of your nix configuration,
+          as the values will be preserved in your nix store.
+          This attribute allows you to configure the location of secret files to be loaded at runtime.
+
+          https://www.authelia.com/configuration/methods/secrets/
+        '';
+        default = { };
+        type = types.submodule {
+          options = {
+            manual = mkOption {
+              default = false;
+              example = true;
+              description = mdDoc ''
+                Configuring authelia's secret files via the secrets attribute set
+                is intended to be convenient and help catch cases where values are required
+                to run at all.
+                If a user wants to set these values themselves and bypass the validation they can set this value to true.
+              '';
+              type = types.bool;
+            };
+
+            # required
+            jwtSecretFile = mkOption {
+              type = types.nullOr types.path;
+              default = null;
+              description = mdDoc ''
+                Path to your JWT secret used during identity verificaiton.
+              '';
+            };
+
+            oidcIssuerPrivateKeyFile = mkOption {
+              type = types.nullOr types.path;
+              default = null;
+              description = mdDoc ''
+                Path to your private key file used to encrypt OIDC JWTs.
+              '';
+            };
+
+            oidcHmacSecretFile = mkOption {
+              type = types.nullOr types.path;
+              default = null;
+              description = mdDoc ''
+                Path to your HMAC secret used to sign OIDC JWTs.
+              '';
+            };
+
+            sessionSecretFile = mkOption {
+              type = types.nullOr types.path;
+              default = null;
+              description = mdDoc ''
+                Path to your session secret. Only used when redis is used as session storage.
+              '';
+            };
+
+            # required
+            storageEncryptionKeyFile = mkOption {
+              type = types.nullOr types.path;
+              default = null;
+              description = mdDoc ''
+                Path to your storage encryption key.
+              '';
+            };
+          };
+        };
+      };
+
+      environmentVariables = mkOption {
+        type = types.attrsOf types.str;
+        description = mdDoc ''
+          Additional environment variables to provide to authelia.
+          If you are providing secrets please consider the options under {option}`services.authelia.<instance>.secrets`
+          or make sure you use the `_FILE` suffix.
+          If you provide the raw secret rather than the location of a secret file that secret will be preserved in the nix store.
+          For more details: https://www.authelia.com/configuration/methods/secrets/
+        '';
+        default = { };
+      };
+
+      settings = mkOption {
+        description = mdDoc ''
+          Your Authelia config.yml as a Nix attribute set.
+          There are several values that are defined and documented in nix such as `default_2fa_method`,
+          but additional items can also be included.
+
+          https://github.com/authelia/authelia/blob/master/config.template.yml
+        '';
+        default = { };
+        example = ''
+          {
+            theme = "light";
+            default_2fa_method = "totp";
+            log.level = "debug";
+            server.disable_healthcheck = true;
+          }
+        '';
+        type = types.submodule {
+          freeformType = format.type;
+          options = {
+            theme = mkOption {
+              type = types.enum [ "light" "dark" "grey" "auto" ];
+              default = "light";
+              example = "dark";
+              description = mdDoc "The theme to display.";
+            };
+
+            default_2fa_method = mkOption {
+              type = types.enum [ "" "totp" "webauthn" "mobile_push" ];
+              default = "";
+              example = "webauthn";
+              description = mdDoc ''
+                Default 2FA method for new users and fallback for preferred but disabled methods.
+              '';
+            };
+
+            server = {
+              host = mkOption {
+                type = types.str;
+                default = "localhost";
+                example = "0.0.0.0";
+                description = mdDoc "The address to listen on.";
+              };
+
+              port = mkOption {
+                type = types.port;
+                default = 9091;
+                description = mdDoc "The port to listen on.";
+              };
+            };
+
+            log = {
+              level = mkOption {
+                type = types.enum [ "info" "debug" "trace" ];
+                default = "debug";
+                example = "info";
+                description = mdDoc "Level of verbosity for logs: info, debug, trace.";
+              };
+
+              format = mkOption {
+                type = types.enum [ "json" "text" ];
+                default = "json";
+                example = "text";
+                description = mdDoc "Format the logs are written as.";
+              };
+
+              file_path = mkOption {
+                type = types.nullOr types.path;
+                default = null;
+                example = "/var/log/authelia/authelia.log";
+                description = mdDoc "File path where the logs will be written. If not set logs are written to stdout.";
+              };
+
+              keep_stdout = mkOption {
+                type = types.bool;
+                default = false;
+                example = true;
+                description = mdDoc "Whether to also log to stdout when a `file_path` is defined.";
+              };
+            };
+
+            telemetry = {
+              metrics = {
+                enabled = mkOption {
+                  type = types.bool;
+                  default = false;
+                  example = true;
+                  description = mdDoc "Enable Metrics.";
+                };
+
+                address = mkOption {
+                  type = types.str;
+                  default = "tcp://127.0.0.1:9959";
+                  example = "tcp://0.0.0.0:8888";
+                  description = mdDoc "The address to listen on for metrics. This should be on a different port to the main `server.port` value.";
+                };
+              };
+            };
+          };
+        };
+      };
+
+      settingsFiles = mkOption {
+        type = types.listOf types.path;
+        default = [ ];
+        example = [ "/etc/authelia/config.yml" "/etc/authelia/access-control.yml" "/etc/authelia/config/" ];
+        description = mdDoc ''
+          Here you can provide authelia with configuration files or directories.
+          It is possible to give authelia multiple files and use the nix generated configuration
+          file set via {option}`services.authelia.<instance>.settings`.
+        '';
+      };
+    };
+  };
+in
+{
+  options.services.authelia.instances = with lib; mkOption {
+    default = { };
+    type = types.attrsOf (types.submodule autheliaOpts);
+    description = mdDoc ''
+      Multi-domain protection currently requires multiple instances of Authelia.
+      If you don't require multiple instances of Authelia you can define just the one.
+
+      https://www.authelia.com/roadmap/active/multi-domain-protection/
+    '';
+    example = ''
+      {
+        main = {
+          enable = true;
+          secrets.storageEncryptionKeyFile = "/etc/authelia/storageEncryptionKeyFile";
+          secrets.jwtSecretFile = "/etc/authelia/jwtSecretFile";
+          settings = {
+            theme = "light";
+            default_2fa_method = "totp";
+            log.level = "debug";
+            server.disable_healthcheck = true;
+          };
+        };
+        preprod = {
+          enable = false;
+          secrets.storageEncryptionKeyFile = "/mnt/pre-prod/authelia/storageEncryptionKeyFile";
+          secrets.jwtSecretFile = "/mnt/pre-prod/jwtSecretFile";
+          settings = {
+            theme = "dark";
+            default_2fa_method = "webauthn";
+            server.host = "0.0.0.0";
+          };
+        };
+        test.enable = true;
+        test.secrets.manual = true;
+        test.settings.theme = "grey";
+        test.settings.server.disable_healthcheck = true;
+        test.settingsFiles = [ "/mnt/test/authelia" "/mnt/test-authelia.conf" ];
+        };
+      }
+    '';
+  };
+
+  config =
+    let
+      mkInstanceServiceConfig = instance:
+        let
+          execCommand = "${instance.package}/bin/authelia";
+          configFile = format.generate "config.yml" instance.settings;
+          configArg = "--config ${builtins.concatStringsSep "," (lib.concatLists [[configFile] instance.settingsFiles])}";
+        in
+        {
+          description = "Authelia authentication and authorization server";
+          wantedBy = [ "multi-user.target" ];
+          after = [ "network.target" ];
+          environment =
+            (lib.filterAttrs (_: v: v != null) {
+              AUTHELIA_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;
+
+          preStart = "${execCommand} ${configArg} validate-config";
+          serviceConfig = {
+            User = instance.user;
+            Group = instance.group;
+            ExecStart = "${execCommand} ${configArg}";
+            Restart = "always";
+            RestartSec = "5s";
+            StateDirectory = "authelia-${instance.name}";
+            StateDirectoryMode = "0700";
+
+            # Security options:
+            AmbientCapabilities = "";
+            CapabilityBoundingSet = "";
+            DeviceAllow = "";
+            LockPersonality = true;
+            MemoryDenyWriteExecute = true;
+            NoNewPrivileges = true;
+
+            PrivateTmp = true;
+            PrivateDevices = true;
+            PrivateUsers = true;
+
+            ProtectClock = true;
+            ProtectControlGroups = true;
+            ProtectHome = "read-only";
+            ProtectHostname = true;
+            ProtectKernelLogs = true;
+            ProtectKernelModules = true;
+            ProtectKernelTunables = true;
+            ProtectProc = "noaccess";
+            ProtectSystem = "strict";
+
+            RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+            RestrictNamespaces = true;
+            RestrictRealtime = true;
+            RestrictSUIDSGID = true;
+
+            SystemCallArchitectures = "native";
+            SystemCallErrorNumber = "EPERM";
+            SystemCallFilter = [
+              "@system-service"
+              "~@cpu-emulation"
+              "~@debug"
+              "~@keyring"
+              "~@memlock"
+              "~@obsolete"
+              "~@privileged"
+              "~@setuid"
+            ];
+          };
+        };
+      mkInstanceUsersConfig = instance: {
+        groups."authelia-${instance.name}" =
+          lib.mkIf (instance.group == "authelia-${instance.name}") {
+            name = "authelia-${instance.name}";
+          };
+        users."authelia-${instance.name}" =
+          lib.mkIf (instance.user == "authelia-${instance.name}") {
+            name = "authelia-${instance.name}";
+            isSystemUser = true;
+            group = instance.group;
+          };
+      };
+      instances = lib.attrValues cfg.instances;
+    in
+    {
+      assertions = lib.flatten (lib.flip lib.mapAttrsToList cfg.instances (name: instance:
+        [
+          {
+            assertion = instance.secrets.manual || (instance.secrets.jwtSecretFile != null && instance.secrets.storageEncryptionKeyFile != null);
+            message = ''
+              Authelia requires a JWT Secret and a Storage Encryption Key to work.
+              Either set them like so:
+              services.authelia.${name}.secrets.jwtSecretFile = /my/path/to/jwtsecret;
+              services.authelia.${name}.secrets.storageEncryptionKeyFile = /my/path/to/encryptionkey;
+              Or set services.authelia.${name}.secrets.manual = true and provide them yourself via
+              environmentVariables or settingsFiles.
+              Do not include raw secrets in nix settings.
+            '';
+          }
+        ]
+      ));
+
+      systemd.services = lib.mkMerge
+        (map
+          (instance: lib.mkIf instance.enable {
+            "authelia-${instance.name}" = mkInstanceServiceConfig instance;
+          })
+          instances);
+      users = lib.mkMerge
+        (map
+          (instance: lib.mkIf instance.enable (mkInstanceUsersConfig instance))
+          instances);
+    };
+}
diff --git a/nixos/modules/services/security/fail2ban.nix b/nixos/modules/services/security/fail2ban.nix
index 3c4bcd1ac2659..ead24d1470717 100644
--- a/nixos/modules/services/security/fail2ban.nix
+++ b/nixos/modules/services/security/fail2ban.nix
@@ -273,26 +273,16 @@ in
       "fail2ban/filter.d".source = "${cfg.package}/etc/fail2ban/filter.d/*.conf";
     };
 
+    systemd.packages = [ cfg.package ];
     systemd.services.fail2ban = {
-      description = "Fail2ban Intrusion Prevention System";
-
       wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" ];
       partOf = optional config.networking.firewall.enable "firewall.service";
 
       restartTriggers = [ fail2banConf jailConf pathsConf ];
 
       path = [ cfg.package cfg.packageFirewall pkgs.iproute2 ] ++ cfg.extraPackages;
 
-      unitConfig.Documentation = "man:fail2ban(1)";
-
       serviceConfig = {
-        ExecStart = "${cfg.package}/bin/fail2ban-server -xf start";
-        ExecStop = "${cfg.package}/bin/fail2ban-server stop";
-        ExecReload = "${cfg.package}/bin/fail2ban-server reload";
-        Type = "simple";
-        Restart = "on-failure";
-        PIDFile = "/run/fail2ban/fail2ban.pid";
         # Capabilities
         CapabilityBoundingSet = [ "CAP_AUDIT_READ" "CAP_DAC_READ_SEARCH" "CAP_NET_ADMIN" "CAP_NET_RAW" ];
         # Security
diff --git a/nixos/modules/services/system/cachix-watch-store.nix b/nixos/modules/services/system/cachix-watch-store.nix
index ec73c0bcdcfe5..85e9509bcc82d 100644
--- a/nixos/modules/services/system/cachix-watch-store.nix
+++ b/nixos/modules/services/system/cachix-watch-store.nix
@@ -25,7 +25,7 @@ in
 
     compressionLevel = mkOption {
       type = types.nullOr types.int;
-      description = lib.mdDoc "The compression level for XZ compression (between 0 and 9)";
+      description = lib.mdDoc "The compression level for ZSTD compression (between 0 and 16)";
       default = null;
     };
 
diff --git a/nixos/modules/services/system/self-deploy.nix b/nixos/modules/services/system/self-deploy.nix
index 16a793a42253c..5f9ee06124cb4 100644
--- a/nixos/modules/services/system/self-deploy.nix
+++ b/nixos/modules/services/system/self-deploy.nix
@@ -132,7 +132,7 @@ in
 
       requires = lib.mkIf (!(isPathType cfg.repository)) [ "network-online.target" ];
 
-      environment.GIT_SSH_COMMAND = lib.mkIf (!(isNull cfg.sshKeyFile))
+      environment.GIT_SSH_COMMAND = lib.mkIf (cfg.sshKeyFile != null)
         "${pkgs.openssh}/bin/ssh -i ${lib.escapeShellArg cfg.sshKeyFile}";
 
       restartIfChanged = false;
diff --git a/nixos/modules/services/web-apps/baget.nix b/nixos/modules/services/web-apps/baget.nix
deleted file mode 100644
index e4d5a1faddb2e..0000000000000
--- a/nixos/modules/services/web-apps/baget.nix
+++ /dev/null
@@ -1,170 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  cfg = config.services.baget;
-
-  defaultConfig = {
-    "PackageDeletionBehavior" = "Unlist";
-    "AllowPackageOverwrites" = false;
-
-    "Database" = {
-      "Type" = "Sqlite";
-      "ConnectionString" = "Data Source=baget.db";
-    };
-
-    "Storage" = {
-      "Type" = "FileSystem";
-      "Path" = "";
-    };
-
-    "Search" = {
-      "Type" = "Database";
-    };
-
-    "Mirror" = {
-      "Enabled" = false;
-      "PackageSource" = "https://api.nuget.org/v3/index.json";
-    };
-
-    "Logging" = {
-      "IncludeScopes" = false;
-      "Debug" = {
-        "LogLevel" = {
-          "Default" = "Warning";
-        };
-      };
-      "Console" = {
-        "LogLevel" = {
-          "Microsoft.Hosting.Lifetime" = "Information";
-          "Default" = "Warning";
-        };
-      };
-    };
-  };
-
-  configAttrs = recursiveUpdate defaultConfig cfg.extraConfig;
-
-  configFormat = pkgs.formats.json {};
-  configFile = configFormat.generate "appsettings.json" configAttrs;
-
-in
-{
-  options.services.baget = {
-    enable = mkEnableOption (lib.mdDoc "BaGet NuGet-compatible server");
-
-    apiKeyFile = mkOption {
-      type = types.path;
-      example = "/root/baget.key";
-      description = lib.mdDoc ''
-        Private API key for BaGet.
-      '';
-    };
-
-    extraConfig = mkOption {
-      type = configFormat.type;
-      default = {};
-      example = {
-        "Database" = {
-          "Type" = "PostgreSql";
-          "ConnectionString" = "Server=/run/postgresql;Port=5432;";
-        };
-      };
-      defaultText = literalExpression ''
-        {
-          "PackageDeletionBehavior" = "Unlist";
-          "AllowPackageOverwrites" = false;
-
-          "Database" = {
-            "Type" = "Sqlite";
-            "ConnectionString" = "Data Source=baget.db";
-          };
-
-          "Storage" = {
-            "Type" = "FileSystem";
-            "Path" = "";
-          };
-
-          "Search" = {
-            "Type" = "Database";
-          };
-
-          "Mirror" = {
-            "Enabled" = false;
-            "PackageSource" = "https://api.nuget.org/v3/index.json";
-          };
-
-          "Logging" = {
-            "IncludeScopes" = false;
-            "Debug" = {
-              "LogLevel" = {
-                "Default" = "Warning";
-              };
-            };
-            "Console" = {
-              "LogLevel" = {
-                "Microsoft.Hosting.Lifetime" = "Information";
-                "Default" = "Warning";
-              };
-            };
-          };
-        }
-      '';
-      description = lib.mdDoc ''
-        Extra configuration options for BaGet. Refer to <https://loic-sharma.github.io/BaGet/configuration/> for details.
-        Default value is merged with values from here.
-      '';
-    };
-  };
-
-  # implementation
-
-  config = mkIf cfg.enable {
-
-    systemd.services.baget = {
-      description = "BaGet server";
-      wantedBy = [ "multi-user.target" ];
-      wants = [ "network-online.target" ];
-      after = [ "network.target" "network-online.target" ];
-      path = [ pkgs.jq ];
-      serviceConfig = {
-        WorkingDirectory = "/var/lib/baget";
-        DynamicUser = true;
-        StateDirectory = "baget";
-        StateDirectoryMode = "0700";
-        LoadCredential = "api_key:${cfg.apiKeyFile}";
-
-        CapabilityBoundingSet = "";
-        NoNewPrivileges = true;
-        PrivateDevices = true;
-        PrivateTmp = true;
-        PrivateUsers = true;
-        PrivateMounts = true;
-        ProtectHome = true;
-        ProtectClock = true;
-        ProtectProc = "noaccess";
-        ProcSubset = "pid";
-        ProtectKernelLogs = true;
-        ProtectKernelModules = true;
-        ProtectKernelTunables = true;
-        ProtectControlGroups = true;
-        ProtectHostname = true;
-        RestrictSUIDSGID = true;
-        RestrictRealtime = true;
-        RestrictNamespaces = true;
-        LockPersonality = true;
-        RemoveIPC = true;
-        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
-        SystemCallFilter = [ "@system-service" "~@privileged" ];
-      };
-      script = ''
-        jq --slurpfile apiKeys <(jq -R . "$CREDENTIALS_DIRECTORY/api_key") '.ApiKey = $apiKeys[0]' ${configFile} > appsettings.json
-        ln -snf ${pkgs.baget}/lib/BaGet/wwwroot wwwroot
-        exec ${pkgs.baget}/bin/BaGet
-      '';
-    };
-
-  };
-}
diff --git a/nixos/modules/services/web-apps/dolibarr.nix b/nixos/modules/services/web-apps/dolibarr.nix
index a9df391128ee5..453229c130c22 100644
--- a/nixos/modules/services/web-apps/dolibarr.nix
+++ b/nixos/modules/services/web-apps/dolibarr.nix
@@ -16,7 +16,7 @@ let
         if (any (str: k == str) secretKeys) then v
         else if isString v then "'${v}'"
         else if isBool v then boolToString v
-        else if isNull v then "null"
+        else if v == null then "null"
         else toString v
       ;
     in
diff --git a/nixos/modules/services/web-apps/jitsi-meet.nix b/nixos/modules/services/web-apps/jitsi-meet.nix
index 28be3a3702eb6..3825b03c24496 100644
--- a/nixos/modules/services/web-apps/jitsi-meet.nix
+++ b/nixos/modules/services/web-apps/jitsi-meet.nix
@@ -411,11 +411,14 @@ in
       componentPasswordFile = "/var/lib/jitsi-meet/jicofo-component-secret";
       bridgeMuc = "jvbbrewery@internal.${cfg.hostName}";
       config = mkMerge [{
-        "org.jitsi.jicofo.ALWAYS_TRUST_MODE_ENABLED" = "true";
+        jicofo.xmpp.service.disable-certificate-verification = true;
+        jicofo.xmpp.client.disable-certificate-verification = true;
       #} (lib.mkIf cfg.jibri.enable {
        } (lib.mkIf (config.services.jibri.enable || cfg.jibri.enable) {
-        "org.jitsi.jicofo.jibri.BREWERY" = "JibriBrewery@internal.${cfg.hostName}";
-        "org.jitsi.jicofo.jibri.PENDING_TIMEOUT" = "90";
+         jicofo.jibri = {
+           brewery-jid = "JibriBrewery@internal.${cfg.hostName}";
+           pending-timeout = "90";
+         };
       })];
     };
 
diff --git a/nixos/modules/services/web-apps/limesurvey.nix b/nixos/modules/services/web-apps/limesurvey.nix
index dd51174c8b8e0..8e6b39cbdebcb 100644
--- a/nixos/modules/services/web-apps/limesurvey.nix
+++ b/nixos/modules/services/web-apps/limesurvey.nix
@@ -34,6 +34,24 @@ in
   options.services.limesurvey = {
     enable = mkEnableOption (lib.mdDoc "Limesurvey web application");
 
+    encryptionKey = mkOption {
+      type = types.str;
+      default = "E17687FC77CEE247F0E22BB3ECF27FDE8BEC310A892347EC13013ABA11AA7EB5";
+      description = lib.mdDoc ''
+        This is a 32-byte key used to encrypt variables in the database.
+        You _must_ change this from the default value.
+      '';
+    };
+
+    encryptionNonce = mkOption {
+      type = types.str;
+      default = "1ACC8555619929DB91310BE848025A427B0F364A884FFA77";
+      description = lib.mdDoc ''
+        This is a 24-byte nonce used to encrypt variables in the database.
+        You _must_ change this from the default value.
+      '';
+    };
+
     database = {
       type = mkOption {
         type = types.enum [ "mysql" "pgsql" "odbc" "mssql" ];
@@ -42,6 +60,12 @@ in
         description = lib.mdDoc "Database engine to use.";
       };
 
+      dbEngine = mkOption {
+        type = types.enum [ "MyISAM" "InnoDB" ];
+        default = "InnoDB";
+        description = lib.mdDoc "Database storage engine to use.";
+      };
+
       host = mkOption {
         type = types.str;
         default = "localhost";
@@ -180,6 +204,8 @@ in
       config = {
         tempdir = "${stateDir}/tmp";
         uploaddir = "${stateDir}/upload";
+        encryptionnonce = cfg.encryptionNonce;
+        encryptionsecretboxkey = cfg.encryptionKey;
         force_ssl = mkIf (cfg.virtualHost.addSSL || cfg.virtualHost.forceSSL || cfg.virtualHost.onlySSL) "on";
         config.defaultlang = "en";
       };
@@ -200,6 +226,8 @@ in
 
     services.phpfpm.pools.limesurvey = {
       inherit user group;
+      phpPackage = pkgs.php80;
+      phpEnv.DBENGINE = "${cfg.database.dbEngine}";
       phpEnv.LIMESURVEY_CONFIG = "${limesurveyConfig}";
       settings = {
         "listen.owner" = config.services.httpd.user;
@@ -256,11 +284,12 @@ in
       wantedBy = [ "multi-user.target" ];
       before = [ "phpfpm-limesurvey.service" ];
       after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
+      environment.DBENGINE = "${cfg.database.dbEngine}";
       environment.LIMESURVEY_CONFIG = limesurveyConfig;
       script = ''
         # update or install the database as required
-        ${pkgs.php}/bin/php ${pkg}/share/limesurvey/application/commands/console.php updatedb || \
-        ${pkgs.php}/bin/php ${pkg}/share/limesurvey/application/commands/console.php install admin password admin admin@example.com verbose
+        ${pkgs.php80}/bin/php ${pkg}/share/limesurvey/application/commands/console.php updatedb || \
+        ${pkgs.php80}/bin/php ${pkg}/share/limesurvey/application/commands/console.php install admin password admin admin@example.com verbose
       '';
       serviceConfig = {
         User = user;
diff --git a/nixos/modules/services/web-apps/mattermost.nix b/nixos/modules/services/web-apps/mattermost.nix
index 56a53198b3fbf..db5122e79f006 100644
--- a/nixos/modules/services/web-apps/mattermost.nix
+++ b/nixos/modules/services/web-apps/mattermost.nix
@@ -184,6 +184,22 @@ in
           .tar.gz files.
         '';
       };
+      environmentFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = lib.mdDoc ''
+          Environment file (see {manpage}`systemd.exec(5)`
+          "EnvironmentFile=" section for the syntax) which sets config options
+          for mattermost (see [the mattermost documentation](https://docs.mattermost.com/configure/configuration-settings.html#environment-variables)).
+
+          Settings defined in the environment file will overwrite settings
+          set via nix or via the {option}`services.mattermost.extraConfig`
+          option.
+
+          Useful for setting config options without their value ending up in the
+          (world-readable) nix store, e.g. for a database password.
+        '';
+      };
 
       localDatabaseCreate = mkOption {
         type = types.bool;
@@ -321,6 +337,7 @@ in
           Restart = "always";
           RestartSec = "10";
           LimitNOFILE = "49152";
+          EnvironmentFile = cfg.environmentFile;
         };
         unitConfig.JoinsNamespaceOf = mkIf cfg.localDatabaseCreate "postgresql.service";
       };
diff --git a/nixos/modules/services/web-apps/nextcloud.md b/nixos/modules/services/web-apps/nextcloud.md
index 014807f3da23c..7ef3cca281f9e 100644
--- a/nixos/modules/services/web-apps/nextcloud.md
+++ b/nixos/modules/services/web-apps/nextcloud.md
@@ -5,7 +5,7 @@ self-hostable cloud platform. The server setup can be automated using
 [services.nextcloud](#opt-services.nextcloud.enable). A
 desktop client is packaged at `pkgs.nextcloud-client`.
 
-The current default by NixOS is `nextcloud25` which is also the latest
+The current default by NixOS is `nextcloud26` which is also the latest
 major version available.
 
 ## Basic usage {#module-services-nextcloud-basic-usage}
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
index c5e161c2516ad..76a0172747ffd 100644
--- a/nixos/modules/services/web-apps/nextcloud.nix
+++ b/nixos/modules/services/web-apps/nextcloud.nix
@@ -204,7 +204,7 @@ in {
     package = mkOption {
       type = types.package;
       description = lib.mdDoc "Which package to use for the Nextcloud instance.";
-      relatedPackages = [ "nextcloud24" "nextcloud25" ];
+      relatedPackages = [ "nextcloud24" "nextcloud25" "nextcloud26" ];
     };
     phpPackage = mkOption {
       type = types.package;
@@ -514,6 +514,27 @@ in {
               `http://hostname.domain/bucket` instead.
             '';
           };
+          sseCKeyFile = mkOption {
+            type = types.nullOr types.path;
+            default = null;
+            example = "/var/nextcloud-objectstore-s3-sse-c-key";
+            description = lib.mdDoc ''
+              If provided this is the full path to a file that contains the key
+              to enable [server-side encryption with customer-provided keys][1]
+              (SSE-C).
+
+              The file must contain a random 32-byte key encoded as a base64
+              string, e.g. generated with the command
+
+              ```
+              openssl rand 32 | base64
+              ```
+
+              Must be readable by user `nextcloud`.
+
+              [1]: https://docs.aws.amazon.com/AmazonS3/latest/userguide/ServerSideEncryptionCustomerKeys.html
+            '';
+          };
         };
       };
     };
@@ -652,7 +673,7 @@ in {
 
   config = mkIf cfg.enable (mkMerge [
     { warnings = let
-        latest = 25;
+        latest = 26;
         upgradeWarning = major: nixos:
           ''
             A legacy Nextcloud install (from before NixOS ${nixos}) may be installed.
@@ -667,20 +688,6 @@ in {
             `services.nextcloud.package`.
           '';
 
-        # FIXME(@Ma27) remove as soon as nextcloud properly supports
-        # mariadb >=10.6.
-        isUnsupportedMariadb =
-          # All currently supported Nextcloud versions are affected (https://github.com/nextcloud/server/issues/25436).
-          (versionOlder cfg.package.version "24")
-          # This module uses mysql
-          && (cfg.config.dbtype == "mysql")
-          # MySQL is managed via NixOS
-          && config.services.mysql.enable
-          # We're using MariaDB
-          && (getName config.services.mysql.package) == "mariadb-server"
-          # MariaDB is at least 10.6 and thus not supported
-          && (versionAtLeast (getVersion config.services.mysql.package) "10.6");
-
       in (optional (cfg.poolConfig != null) ''
           Using config.services.nextcloud.poolConfig is deprecated and will become unsupported in a future release.
           Please migrate your configuration to config.services.nextcloud.poolSettings.
@@ -688,6 +695,7 @@ in {
         ++ (optional (versionOlder cfg.package.version "23") (upgradeWarning 22 "22.05"))
         ++ (optional (versionOlder cfg.package.version "24") (upgradeWarning 23 "22.05"))
         ++ (optional (versionOlder cfg.package.version "25") (upgradeWarning 24 "22.11"))
+        ++ (optional (versionOlder cfg.package.version "26") (upgradeWarning 25 "23.05"))
         ++ (optional cfg.enableBrokenCiphersForSSE ''
           You're using PHP's openssl extension built against OpenSSL 1.1 for Nextcloud.
           This is only necessary if you're using Nextcloud's server-side encryption.
@@ -704,18 +712,7 @@ in {
           See <https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/encryption_configuration.html#disabling-encryption> on how to achieve this.
 
           For more context, here is the implementing pull request: https://github.com/NixOS/nixpkgs/pull/198470
-        '')
-        ++ (optional isUnsupportedMariadb ''
-            You seem to be using MariaDB at an unsupported version (i.e. at least 10.6)!
-            Please note that this isn't supported officially by Nextcloud. You can either
-
-            * Switch to `pkgs.mysql`
-            * Downgrade MariaDB to at least 10.5
-            * Work around Nextcloud's problems by specifying `innodb_read_only_compressed=0`
-
-            For further context, please read
-            https://help.nextcloud.com/t/update-to-next-cloud-21-0-2-has-get-an-error/117028/15
-          '');
+        '');
 
       services.nextcloud.package = with pkgs;
         mkDefault (
@@ -726,12 +723,13 @@ in {
               `pkgs.nextcloud`.
             ''
           else if versionOlder stateVersion "22.11" then nextcloud24
-          else nextcloud25
+          else if versionOlder stateVersion "23.05" then nextcloud25
+          else nextcloud26
         );
 
       services.nextcloud.phpPackage =
-        if versionOlder cfg.package.version "24" then pkgs.php80
-        else pkgs.php81;
+        if versionOlder cfg.package.version "26" then pkgs.php81
+        else pkgs.php82;
     }
 
     { assertions = [
@@ -773,6 +771,7 @@ in {
                 'use_ssl' => ${boolToString s3.useSsl},
                 ${optionalString (s3.region != null) "'region' => '${s3.region}',"}
                 'use_path_style' => ${boolToString s3.usePathStyle},
+                ${optionalString (s3.sseCKeyFile != null) "'sse_c_key' => nix_read_secret('${s3.sseCKeyFile}'),"}
               ],
             ]
           '';
@@ -958,6 +957,9 @@ in {
           '';
           serviceConfig.Type = "oneshot";
           serviceConfig.User = "nextcloud";
+          # On Nextcloud ≥ 26, it is not necessary to patch the database files to prevent
+          # an automatic creation of the database user.
+          environment.NC_setup_create_db_user = lib.mkIf (nextcloudGreaterOrEqualThan "26") "false";
         };
         nextcloud-cron = {
           after = [ "nextcloud-setup.service" ];
@@ -1009,14 +1011,6 @@ in {
           name = cfg.config.dbuser;
           ensurePermissions = { "${cfg.config.dbname}.*" = "ALL PRIVILEGES"; };
         }];
-        # FIXME(@Ma27) Nextcloud isn't compatible with mariadb 10.6,
-        # this is a workaround.
-        # See https://help.nextcloud.com/t/update-to-next-cloud-21-0-2-has-get-an-error/117028/22
-        settings = mkIf (versionOlder cfg.package.version "24") {
-          mysqld = {
-            innodb_read_only_compressed = 0;
-          };
-        };
         initialScript = pkgs.writeText "mysql-init" ''
           CREATE USER '${cfg.config.dbname}'@'localhost' IDENTIFIED BY '${builtins.readFile( cfg.config.dbpassFile )}';
           CREATE DATABASE IF NOT EXISTS ${cfg.config.dbname};
@@ -1118,7 +1112,7 @@ in {
           ${optionalString (cfg.nginx.recommendedHttpHeaders) ''
             add_header X-Content-Type-Options nosniff;
             add_header X-XSS-Protection "1; mode=block";
-            add_header X-Robots-Tag none;
+            add_header X-Robots-Tag "noindex, nofollow";
             add_header X-Download-Options noopen;
             add_header X-Permitted-Cross-Domain-Policies none;
             add_header X-Frame-Options sameorigin;
diff --git a/nixos/modules/services/web-apps/writefreely.nix b/nixos/modules/services/web-apps/writefreely.nix
index dec00b46f335e..a7671aa717f43 100644
--- a/nixos/modules/services/web-apps/writefreely.nix
+++ b/nixos/modules/services/web-apps/writefreely.nix
@@ -10,12 +10,11 @@ let
   format = pkgs.formats.ini {
     mkKeyValue = key: value:
       let
-        value' = if builtins.isNull value then
-          ""
-        else if builtins.isBool value then
-          if value == true then "true" else "false"
-        else
-          toString value;
+        value' = lib.optionalString (value != null)
+          (if builtins.isBool value then
+            if value == true then "true" else "false"
+          else
+            toString value);
       in "${key} = ${value'}";
   };
 
diff --git a/nixos/modules/services/web-servers/garage.nix b/nixos/modules/services/web-servers/garage.nix
index 2491c788d6c51..df8ed96f8d908 100644
--- a/nixos/modules/services/web-servers/garage.nix
+++ b/nixos/modules/services/web-servers/garage.nix
@@ -60,7 +60,7 @@ in
 
     package = mkOption {
       # TODO: when 23.05 is released and if Garage 0.9 is the default, put a stateVersion check.
-      default = if versionAtLeast stateVersion "23.05" then pkgs.garage_0_8_0
+      default = if versionAtLeast config.system.stateVersion "23.05" then pkgs.garage_0_8
                 else pkgs.garage_0_7;
       defaultText = literalExpression "pkgs.garage_0_7";
       type = types.package;
diff --git a/nixos/modules/services/web-servers/minio.nix b/nixos/modules/services/web-servers/minio.nix
index 1a9eacb431b3c..21bec4f63a879 100644
--- a/nixos/modules/services/web-servers/minio.nix
+++ b/nixos/modules/services/web-servers/minio.nix
@@ -60,7 +60,7 @@ in
       '';
     };
 
-    rootCredentialsFile = mkOption  {
+    rootCredentialsFile = mkOption {
       type = types.nullOr types.path;
       default = null;
       description = lib.mdDoc ''
@@ -96,29 +96,62 @@ in
   config = mkIf cfg.enable {
     warnings = optional ((cfg.accessKey != "") || (cfg.secretKey != "")) "services.minio.`accessKey` and services.minio.`secretKey` are deprecated, please use services.minio.`rootCredentialsFile` instead.";
 
-    systemd.tmpfiles.rules = [
-      "d '${cfg.configDir}' - minio minio - -"
-    ] ++ (map (x:  "d '" + x + "' - minio minio - - ") cfg.dataDir);
-
-    systemd.services.minio = {
-      description = "Minio Object Storage";
-      after = [ "network-online.target" ];
-      wantedBy = [ "multi-user.target" ];
-      serviceConfig = {
-        ExecStart = "${cfg.package}/bin/minio server --json --address ${cfg.listenAddress} --console-address ${cfg.consoleAddress} --config-dir=${cfg.configDir} ${toString cfg.dataDir}";
-        Type = "simple";
-        User = "minio";
-        Group = "minio";
-        LimitNOFILE = 65536;
-        EnvironmentFile = if (cfg.rootCredentialsFile != null) then cfg.rootCredentialsFile
-                          else if ((cfg.accessKey != "") || (cfg.secretKey != "")) then (legacyCredentials cfg)
-                          else null;
+    systemd = lib.mkMerge [{
+      tmpfiles.rules = [
+        "d '${cfg.configDir}' - minio minio - -"
+      ] ++ (map (x: "d '" + x + "' - minio minio - - ") cfg.dataDir);
+
+      services.minio = {
+        description = "Minio Object Storage";
+        after = [ "network-online.target" ];
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          ExecStart = "${cfg.package}/bin/minio server --json --address ${cfg.listenAddress} --console-address ${cfg.consoleAddress} --config-dir=${cfg.configDir} ${toString cfg.dataDir}";
+          Type = "simple";
+          User = "minio";
+          Group = "minio";
+          LimitNOFILE = 65536;
+          EnvironmentFile =
+            if (cfg.rootCredentialsFile != null) then cfg.rootCredentialsFile
+            else if ((cfg.accessKey != "") || (cfg.secretKey != "")) then (legacyCredentials cfg)
+            else null;
+        };
+        environment = {
+          MINIO_REGION = "${cfg.region}";
+          MINIO_BROWSER = "${if cfg.browser then "on" else "off"}";
+        };
       };
-      environment = {
-        MINIO_REGION = "${cfg.region}";
-        MINIO_BROWSER = "${if cfg.browser then "on" else "off"}";
-      };
-    };
+    }
+
+      (lib.mkIf (cfg.rootCredentialsFile != null) {
+        # The service will fail if the credentials file is missing
+        services.minio.unitConfig.ConditionPathExists = cfg.rootCredentialsFile;
+
+        # The service will not restart if the credentials file has
+        # been changed. This can cause stale root credentials.
+        paths.minio-root-credentials = {
+          wantedBy = [ "multi-user.target" ];
+
+          pathConfig = {
+            PathChanged = [ cfg.rootCredentialsFile ];
+            Unit = "minio-restart.service";
+          };
+        };
+
+        services.minio-restart = {
+          description = "Restart MinIO";
+
+          script = ''
+            systemctl restart minio.service
+          '';
+
+          serviceConfig = {
+            Type = "oneshot";
+            Restart = "on-failure";
+            RestartSec = 5;
+          };
+        };
+      })];
 
     users.users.minio = {
       group = "minio";
diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix
index 905dd5bef1f71..064c86a9a7e29 100644
--- a/nixos/modules/services/web-servers/nginx/default.nix
+++ b/nixos/modules/services/web-servers/nginx/default.nix
@@ -184,8 +184,8 @@ let
         brotli_types ${lib.concatStringsSep " " compressMimeTypes};
       ''}
 
-      # https://docs.nginx.com/nginx/admin-guide/web-server/compression/
       ${optionalString cfg.recommendedGzipSettings ''
+        # https://docs.nginx.com/nginx/admin-guide/web-server/compression/
         gzip on;
         gzip_static on;
         gzip_vary on;
@@ -195,6 +195,14 @@ let
         gzip_types ${lib.concatStringsSep " " compressMimeTypes};
       ''}
 
+      ${optionalString cfg.recommendedZstdSettings ''
+        zstd on;
+        zstd_comp_level 9;
+        zstd_min_length 256;
+        zstd_static on;
+        zstd_types ${lib.concatStringsSep " " compressMimeTypes};
+      ''}
+
       ${optionalString cfg.recommendedProxySettings ''
         proxy_redirect          off;
         proxy_connect_timeout   ${cfg.proxyTimeout};
@@ -490,6 +498,16 @@ in
         '';
       };
 
+      recommendedZstdSettings = mkOption {
+        default = false;
+        type = types.bool;
+        description = lib.mdDoc ''
+          Enable recommended zstd settings. Learn more about compression in Zstd format [here](https://github.com/tokers/zstd-nginx-module).
+
+          This adds `pkgs.nginxModules.zstd` to `services.nginx.additionalModules`.
+        '';
+      };
+
       proxyTimeout = mkOption {
         type = types.str;
         default = "60s";
@@ -1015,7 +1033,8 @@ in
       groups = config.users.groups;
     }) dependentCertNames;
 
-    services.nginx.additionalModules = optional cfg.recommendedBrotliSettings pkgs.nginxModules.brotli;
+    services.nginx.additionalModules = optional cfg.recommendedBrotliSettings pkgs.nginxModules.brotli
+      ++ lib.optional cfg.recommendedZstdSettings pkgs.nginxModules.zstd;
 
     systemd.services.nginx = {
       description = "Nginx Web Server";
diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix
index f0c4b2172f9d2..73a864bb95fe8 100644
--- a/nixos/modules/services/x11/desktop-managers/plasma5.nix
+++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix
@@ -81,99 +81,90 @@ let
 in
 
 {
-  options.services.xserver.desktopManager.plasma5 = {
-    enable = mkOption {
-      type = types.bool;
-      default = false;
-      description = lib.mdDoc "Enable the Plasma 5 (KDE 5) desktop environment.";
-    };
-
-    phononBackend = mkOption {
-      type = types.enum [ "gstreamer" "vlc" ];
-      default = "vlc";
-      example = "gstreamer";
-      description = lib.mdDoc "Phonon audio backend to install.";
-    };
-
-    supportDDC = mkOption {
-      type = types.bool;
-      default = false;
-      description = lib.mdDoc ''
-        Support setting monitor brightness via DDC.
-
-        This is not needed for controlling brightness of the internal monitor
-        of a laptop and as it is considered experimental by upstream, it is
-        disabled by default.
-      '';
-    };
+  options = {
+    services.xserver.desktopManager.plasma5 = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc "Enable the Plasma 5 (KDE 5) desktop environment.";
+      };
 
-    useQtScaling = mkOption {
-      type = types.bool;
-      default = false;
-      description = lib.mdDoc "Enable HiDPI scaling in Qt.";
-    };
+      phononBackend = mkOption {
+        type = types.enum [ "gstreamer" "vlc" ];
+        default = "vlc";
+        example = "gstreamer";
+        description = lib.mdDoc "Phonon audio backend to install.";
+      };
 
-    runUsingSystemd = mkOption {
-      description = lib.mdDoc "Use systemd to manage the Plasma session";
-      type = types.bool;
-      default = true;
-    };
+      useQtScaling = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc "Enable HiDPI scaling in Qt.";
+      };
 
-    excludePackages = mkOption {
-      description = lib.mdDoc "List of default packages to exclude from the configuration";
-      type = types.listOf types.package;
-      default = [];
-      example = literalExpression "[ pkgs.plasma5Packages.oxygen ]";
-    };
+      runUsingSystemd = mkOption {
+        description = lib.mdDoc "Use systemd to manage the Plasma session";
+        type = types.bool;
+        default = true;
+      };
 
-    notoPackage = mkPackageOptionMD pkgs "Noto fonts" {
-      default = [ "noto-fonts" ];
-      example = "noto-fonts-lgc-plus";
-    };
+      notoPackage = mkPackageOptionMD pkgs "Noto fonts" {
+        default = [ "noto-fonts" ];
+        example = "noto-fonts-lgc-plus";
+      };
 
-    # Internally allows configuring kdeglobals globally
-    kdeglobals = mkOption {
-      internal = true;
-      default = {};
-      type = kdeConfigurationType;
-    };
+      # Internally allows configuring kdeglobals globally
+      kdeglobals = mkOption {
+        internal = true;
+        default = {};
+        type = kdeConfigurationType;
+      };
 
-    # Internally allows configuring kwin globally
-    kwinrc = mkOption {
-      internal = true;
-      default = {};
-      type = kdeConfigurationType;
-    };
+      # Internally allows configuring kwin globally
+      kwinrc = mkOption {
+        internal = true;
+        default = {};
+        type = kdeConfigurationType;
+      };
 
-    mobile.enable = mkOption {
-      type = types.bool;
-      default = false;
-      description = lib.mdDoc ''
-        Enable support for running the Plasma Mobile shell.
-      '';
-    };
+      mobile.enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Enable support for running the Plasma Mobile shell.
+        '';
+      };
 
-    mobile.installRecommendedSoftware = mkOption {
-      type = types.bool;
-      default = true;
-      description = lib.mdDoc ''
-        Installs software recommended for use with Plasma Mobile, but which
-        is not strictly required for Plasma Mobile to run.
-      '';
-    };
+      mobile.installRecommendedSoftware = mkOption {
+        type = types.bool;
+        default = true;
+        description = lib.mdDoc ''
+          Installs software recommended for use with Plasma Mobile, but which
+          is not strictly required for Plasma Mobile to run.
+        '';
+      };
 
-    bigscreen.enable = mkOption {
-      type = types.bool;
-      default = false;
-      description = lib.mdDoc ''
-        Enable support for running the Plasma Bigscreen session.
-      '';
+      bigscreen.enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Enable support for running the Plasma Bigscreen session.
+        '';
+      };
     };
+    environment.plasma5.excludePackages = mkOption {
+        description = lib.mdDoc "List of default packages to exclude from the configuration";
+        type = types.listOf types.package;
+        default = [];
+        example = literalExpression "[ pkgs.plasma5Packages.oxygen ]";
+      };
   };
 
   imports = [
     (mkRemovedOptionModule [ "services" "xserver" "desktopManager" "plasma5" "enableQt4Support" ] "Phonon no longer supports Qt 4.")
+    (mkRemovedOptionModule [ "services" "xserver" "desktopManager" "plasma5" "supportDDC" ] "DDC/CI is no longer supported upstream.")
     (mkRenamedOptionModule [ "services" "xserver" "desktopManager" "kde5" ] [ "services" "xserver" "desktopManager" "plasma5" ])
+    (mkRenamedOptionModule [ "services" "xserver" "desktopManager" "plasma5" "excludePackages" ] [ "environment" "plasma5" "excludePackages" ])
   ];
 
   config = mkMerge [
@@ -201,12 +192,6 @@ in
         };
       };
 
-      # DDC support
-      boot.kernelModules = lib.optional cfg.supportDDC "i2c_dev";
-      services.udev.extraRules = lib.optionalString cfg.supportDDC ''
-        KERNEL=="i2c-[0-9]*", TAG+="uaccess"
-      '';
-
       environment.systemPackages =
         with libsForQt5;
         with plasma5; with kdeGear; with kdeFrameworks;
@@ -301,7 +286,7 @@ in
           ];
         in
         requiredPackages
-        ++ utils.removePackagesByName optionalPackages cfg.excludePackages
+        ++ utils.removePackagesByName optionalPackages config.environment.plasma5.excludePackages
 
         # Phonon audio backend
         ++ lib.optional (cfg.phononBackend == "gstreamer") libsForQt5.phonon-backend-gstreamer
@@ -455,7 +440,7 @@ in
             khelpcenter
             print-manager
           ];
-      in requiredPackages ++ utils.removePackagesByName optionalPackages cfg.excludePackages;
+      in requiredPackages ++ utils.removePackagesByName optionalPackages config.environment.plasma5.excludePackages;
 
       systemd.user.services = {
         plasma-run-with-systemd = {
diff --git a/nixos/modules/services/x11/display-managers/gdm.nix b/nixos/modules/services/x11/display-managers/gdm.nix
index 1c3881bef2de2..f8f82bda3fa43 100644
--- a/nixos/modules/services/x11/display-managers/gdm.nix
+++ b/nixos/modules/services/x11/display-managers/gdm.nix
@@ -323,7 +323,7 @@ in
 
         account   sufficient    pam_unix.so
 
-        password  requisite     pam_unix.so nullok sha512
+        password  requisite     pam_unix.so nullok yescrypt
 
         session   optional      pam_keyinit.so revoke
         session   include       login
diff --git a/nixos/modules/services/x11/display-managers/lightdm.nix b/nixos/modules/services/x11/display-managers/lightdm.nix
index 65f414705fc51..548d3c5bc46a5 100644
--- a/nixos/modules/services/x11/display-managers/lightdm.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm.nix
@@ -302,7 +302,7 @@ in
 
         account   sufficient    pam_unix.so
 
-        password  requisite     pam_unix.so nullok sha512
+        password  requisite     pam_unix.so nullok yescrypt
 
         session   optional      pam_keyinit.so revoke
         session   include       login
diff --git a/nixos/modules/services/x11/gdk-pixbuf.nix b/nixos/modules/services/x11/gdk-pixbuf.nix
index 2105224f92ff3..9c088e4cc4237 100644
--- a/nixos/modules/services/x11/gdk-pixbuf.nix
+++ b/nixos/modules/services/x11/gdk-pixbuf.nix
@@ -21,7 +21,7 @@ in
   # loaders.cache based on that and set the environment variable
   # GDK_PIXBUF_MODULE_FILE to point to it.
   config = lib.mkIf (cfg.modulePackages != []) {
-    environment.variables = {
+    environment.sessionVariables = {
       GDK_PIXBUF_MODULE_FILE = "${loadersCache}";
     };
   };
diff --git a/nixos/modules/services/x11/window-managers/qtile.nix b/nixos/modules/services/x11/window-managers/qtile.nix
index fc27566d49ee6..cc24522970f3a 100644
--- a/nixos/modules/services/x11/window-managers/qtile.nix
+++ b/nixos/modules/services/x11/window-managers/qtile.nix
@@ -1,23 +1,63 @@
-{ config, lib, pkgs, ... }:
+{ config, pkgs, lib, ... }:
 
 with lib;
 
 let
   cfg = config.services.xserver.windowManager.qtile;
+  pyEnv = pkgs.python3.withPackages (p: [ (cfg.package.unwrapped or cfg.package) ] ++ (cfg.extraPackages p));
 in
 
 {
   options.services.xserver.windowManager.qtile = {
     enable = mkEnableOption (lib.mdDoc "qtile");
 
-    package = mkPackageOptionMD pkgs "qtile" { };
+    package = mkPackageOptionMD pkgs "qtile-unwrapped" { };
+
+    configFile = mkOption {
+      type = with types; nullOr path;
+      default = null;
+      example = literalExpression "./your_config.py";
+      description = lib.mdDoc ''
+          Path to the qtile configuration file.
+          If null, $XDG_CONFIG_HOME/qtile/config.py will be used.
+      '';
+    };
+
+    backend = mkOption {
+      type = types.enum [ "x11" "wayland" ];
+      default = "x11";
+      description = lib.mdDoc ''
+          Backend to use in qtile:
+          <option>x11</option> or <option>wayland</option>.
+      '';
+    };
+
+    extraPackages = mkOption {
+        type = types.functionTo (types.listOf types.package);
+        default = _: [];
+        defaultText = literalExpression ''
+          python3Packages: with python3Packages; [];
+        '';
+        description = lib.mdDoc ''
+          Extra Python packages available to Qtile.
+          An example would be to include `python3Packages.qtile-extras`
+          for additional unoffical widgets.
+        '';
+        example = literalExpression ''
+          python3Packages: with python3Packages; [
+            qtile-extras
+          ];
+        '';
+      };
   };
 
   config = mkIf cfg.enable {
     services.xserver.windowManager.session = [{
       name = "qtile";
       start = ''
-        ${cfg.package}/bin/qtile start &
+        ${pyEnv}/bin/qtile start -b ${cfg.backend} \
+        ${optionalString (cfg.configFile != null)
+        "--config \"${cfg.configFile}\""} &
         waitPID=$!
       '';
     }];
diff --git a/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix
index adb079c87a8b8..fcc18c9a26fd0 100644
--- a/nixos/modules/services/x11/xserver.nix
+++ b/nixos/modules/services/x11/xserver.nix
@@ -138,6 +138,26 @@ let
     concatMapStringsSep "\n" (line: prefix + line) (splitString "\n" str);
 
   indent = prefixStringLines "  ";
+
+  # A scalable variant of the X11 "core" cursor
+  #
+  # If not running a fancy desktop environment, the cursor is likely set to
+  # the default `cursor.pcf` bitmap font. This is 17px wide, so it's very
+  # small and almost invisible on 4K displays.
+  fontcursormisc_hidpi = pkgs.xorg.fontxfree86type1.overrideAttrs (old:
+    let
+      # The scaling constant is 230/96: the scalable `left_ptr` glyph at
+      # about 23 points is rendered as 17px, on a 96dpi display.
+      # Note: the XLFD font size is in decipoints.
+      size = 2.39583 * cfg.dpi;
+      sizeString = builtins.head (builtins.split "\\." (toString size));
+    in
+    {
+      postInstall = ''
+        alias='cursor -xfree86-cursor-medium-r-normal--0-${sizeString}-0-0-p-0-adobe-fontspecific'
+        echo "$alias" > $out/lib/X11/fonts/Type1/fonts.alias
+      '';
+    });
 in
 
 {
@@ -576,6 +596,15 @@ in
           Whether to terminate X upon server reset.
         '';
       };
+
+      upscaleDefaultCursor = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Upscale the default X cursor to be more visible on high-density displays.
+          Requires `config.services.xserver.dpi` to be set.
+        '';
+      };
     };
 
   };
@@ -592,7 +621,8 @@ in
                     || dmConf.sddm.enable
                     || dmConf.xpra.enable
                     || dmConf.sx.enable
-                    || dmConf.startx.enable);
+                    || dmConf.startx.enable
+                    || config.services.greetd.enable);
       in mkIf (default) (mkDefault true);
 
     # so that the service won't be enabled when only startx is used
@@ -626,6 +656,10 @@ in
                 + "${toString (length primaryHeads)} heads set to primary: "
                 + concatMapStringsSep ", " (x: x.output) primaryHeads;
       })
+      {
+        assertion = cfg.upscaleDefaultCursor -> cfg.dpi != null;
+        message = "Specify `config.services.xserver.dpi` to upscale the default cursor.";
+      }
     ];
 
     environment.etc =
@@ -850,6 +884,10 @@ in
       '';
 
     fonts.enableDefaultFonts = mkDefault true;
+    fonts.fonts = [
+      (if cfg.upscaleDefaultCursor then fontcursormisc_hidpi else pkgs.xorg.fontcursormisc)
+      pkgs.xorg.fontmiscmisc
+    ];
 
   };