about summary refs log tree commit diff
path: root/nixos/modules
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules')
-rw-r--r--nixos/modules/config/no-x-libs.nix11
-rw-r--r--nixos/modules/config/stevenblack.nix2
-rw-r--r--nixos/modules/config/update-users-groups.pl10
-rw-r--r--nixos/modules/config/users-groups.nix10
-rw-r--r--nixos/modules/config/zram.nix161
-rw-r--r--nixos/modules/hardware/all-firmware.nix2
-rw-r--r--nixos/modules/hardware/flipperzero.nix18
-rw-r--r--nixos/modules/hardware/keyboard/qmk.nix16
-rw-r--r--nixos/modules/hardware/keyboard/teck.nix6
-rw-r--r--nixos/modules/hardware/keyboard/uhk.nix7
-rw-r--r--nixos/modules/hardware/keyboard/zsa.nix19
-rw-r--r--nixos/modules/hardware/video/nvidia.nix123
-rw-r--r--nixos/modules/i18n/input-method/default.nix2
-rw-r--r--nixos/modules/i18n/input-method/default.xml275
-rw-r--r--nixos/modules/installer/sd-card/sd-image-powerpc64le.nix49
-rw-r--r--nixos/modules/installer/tools/nix-fallback-paths.nix10
-rw-r--r--nixos/modules/installer/tools/tools.nix2
-rw-r--r--nixos/modules/misc/meta.nix2
-rw-r--r--nixos/modules/misc/version.nix2
-rw-r--r--nixos/modules/module-list.nix14
-rw-r--r--nixos/modules/programs/digitalbitbox/default.nix2
-rw-r--r--nixos/modules/programs/digitalbitbox/default.xml70
-rw-r--r--nixos/modules/programs/flashrom.nix1
-rw-r--r--nixos/modules/programs/k3b.nix4
-rw-r--r--nixos/modules/programs/miriway.nix60
-rw-r--r--nixos/modules/programs/plotinus.nix2
-rw-r--r--nixos/modules/programs/plotinus.xml30
-rw-r--r--nixos/modules/programs/proxychains.nix2
-rw-r--r--nixos/modules/programs/qdmr.nix25
-rw-r--r--nixos/modules/programs/sharing.nix19
-rw-r--r--nixos/modules/programs/singularity.nix102
-rw-r--r--nixos/modules/programs/ssh.nix2
-rw-r--r--nixos/modules/programs/sway.nix24
-rw-r--r--nixos/modules/programs/zsh/oh-my-zsh.nix2
-rw-r--r--nixos/modules/programs/zsh/oh-my-zsh.xml154
-rw-r--r--nixos/modules/rename.nix1
-rw-r--r--nixos/modules/security/acme/default.nix2
-rw-r--r--nixos/modules/security/acme/default.xml395
-rw-r--r--nixos/modules/security/polkit.nix2
-rw-r--r--nixos/modules/services/audio/hqplayerd.nix3
-rw-r--r--nixos/modules/services/audio/roon-bridge.nix2
-rw-r--r--nixos/modules/services/audio/tts.nix151
-rw-r--r--nixos/modules/services/audio/ympd.nix40
-rw-r--r--nixos/modules/services/backup/borgbackup.md2
-rw-r--r--nixos/modules/services/backup/borgbackup.nix2
-rw-r--r--nixos/modules/services/backup/borgbackup.xml215
-rw-r--r--nixos/modules/services/backup/zfs-replication.nix2
-rw-r--r--nixos/modules/services/cluster/k3s/default.nix9
-rw-r--r--nixos/modules/services/cluster/kubernetes/addon-manager.nix2
-rw-r--r--nixos/modules/services/cluster/kubernetes/kubelet.nix2
-rw-r--r--nixos/modules/services/computing/slurm/slurm.nix2
-rw-r--r--nixos/modules/services/continuous-integration/github-runner/options.nix41
-rw-r--r--nixos/modules/services/continuous-integration/github-runner/service.nix5
-rw-r--r--nixos/modules/services/continuous-integration/gitlab-runner.nix6
-rw-r--r--nixos/modules/services/databases/clickhouse.nix2
-rw-r--r--nixos/modules/services/databases/foundationdb.nix2
-rw-r--r--nixos/modules/services/databases/foundationdb.xml425
-rw-r--r--nixos/modules/services/databases/postgresql.nix2
-rw-r--r--nixos/modules/services/databases/postgresql.xml250
-rw-r--r--nixos/modules/services/desktops/flatpak.nix2
-rw-r--r--nixos/modules/services/desktops/flatpak.xml59
-rw-r--r--nixos/modules/services/desktops/gnome/evolution-data-server.nix4
-rw-r--r--nixos/modules/services/desktops/pipewire/daemon/client-rt.conf.json17
-rw-r--r--nixos/modules/services/desktops/pipewire/daemon/jack.conf.json12
-rw-r--r--nixos/modules/services/desktops/pipewire/daemon/pipewire-aes67.conf.json38
-rw-r--r--nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json9
-rw-r--r--nixos/modules/services/desktops/pipewire/pipewire.nix2
-rw-r--r--nixos/modules/services/development/blackfire.nix2
-rw-r--r--nixos/modules/services/development/blackfire.xml61
-rw-r--r--nixos/modules/services/development/zammad.nix2
-rw-r--r--nixos/modules/services/editors/emacs.nix2
-rw-r--r--nixos/modules/services/editors/emacs.xml490
-rw-r--r--nixos/modules/services/games/freeciv.nix2
-rw-r--r--nixos/modules/services/hardware/bluetooth.nix27
-rw-r--r--nixos/modules/services/hardware/fwupd.nix21
-rw-r--r--nixos/modules/services/hardware/kanata.nix86
-rw-r--r--nixos/modules/services/hardware/throttled.nix2
-rw-r--r--nixos/modules/services/hardware/trezord.nix2
-rw-r--r--nixos/modules/services/hardware/trezord.xml29
-rw-r--r--nixos/modules/services/hardware/udisks2.nix24
-rw-r--r--nixos/modules/services/home-automation/home-assistant.nix5
-rw-r--r--nixos/modules/services/mail/dovecot.nix14
-rw-r--r--nixos/modules/services/mail/maddy.nix51
-rw-r--r--nixos/modules/services/mail/mailman.nix2
-rw-r--r--nixos/modules/services/mail/mailman.xml112
-rw-r--r--nixos/modules/services/mail/postfix.nix2
-rw-r--r--nixos/modules/services/mail/roundcube.nix6
-rw-r--r--nixos/modules/services/matrix/appservice-discord.nix16
-rw-r--r--nixos/modules/services/matrix/mautrix-facebook.nix2
-rw-r--r--nixos/modules/services/matrix/mautrix-telegram.nix2
-rw-r--r--nixos/modules/services/matrix/mjolnir.nix2
-rw-r--r--nixos/modules/services/matrix/mjolnir.xml148
-rw-r--r--nixos/modules/services/matrix/synapse.md2
-rw-r--r--nixos/modules/services/matrix/synapse.nix2
-rw-r--r--nixos/modules/services/matrix/synapse.xml263
-rw-r--r--nixos/modules/services/misc/atuin.nix2
-rw-r--r--nixos/modules/services/misc/gitlab.nix2
-rw-r--r--nixos/modules/services/misc/gitlab.xml143
-rw-r--r--nixos/modules/services/misc/input-remapper.nix2
-rw-r--r--nixos/modules/services/misc/klipper.nix2
-rw-r--r--nixos/modules/services/misc/mbpfan.nix39
-rw-r--r--nixos/modules/services/misc/moonraker.nix49
-rw-r--r--nixos/modules/services/misc/nitter.nix7
-rw-r--r--nixos/modules/services/misc/octoprint.nix3
-rw-r--r--nixos/modules/services/misc/paperless.nix21
-rw-r--r--nixos/modules/services/misc/pykms.nix2
-rw-r--r--nixos/modules/services/misc/sourcehut/default.nix4
-rw-r--r--nixos/modules/services/misc/sourcehut/default.xml113
-rw-r--r--nixos/modules/services/misc/taskserver/default.nix2
-rw-r--r--nixos/modules/services/misc/taskserver/default.xml130
-rw-r--r--nixos/modules/services/misc/weechat.nix2
-rw-r--r--nixos/modules/services/misc/weechat.xml63
-rw-r--r--nixos/modules/services/monitoring/cadvisor.nix2
-rw-r--r--nixos/modules/services/monitoring/cockpit.nix231
-rw-r--r--nixos/modules/services/monitoring/mackerel-agent.nix2
-rw-r--r--nixos/modules/services/monitoring/mimir.nix9
-rw-r--r--nixos/modules/services/monitoring/parsedmarc.nix6
-rw-r--r--nixos/modules/services/monitoring/parsedmarc.xml126
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters.nix3
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters.xml245
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/pihole.nix36
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/shelly.nix27
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/unpoller.nix6
-rw-r--r--nixos/modules/services/monitoring/uptime-kuma.nix4
-rw-r--r--nixos/modules/services/network-filesystems/kubo.nix57
-rw-r--r--nixos/modules/services/network-filesystems/litestream/default.nix2
-rw-r--r--nixos/modules/services/network-filesystems/litestream/default.xml62
-rw-r--r--nixos/modules/services/network-filesystems/moosefs.nix6
-rw-r--r--nixos/modules/services/networking/avahi-daemon.nix1
-rw-r--r--nixos/modules/services/networking/blockbook-frontend.nix2
-rw-r--r--nixos/modules/services/networking/envoy.nix51
-rw-r--r--nixos/modules/services/networking/firefox-syncserver.nix6
-rw-r--r--nixos/modules/services/networking/firefox-syncserver.xml79
-rw-r--r--nixos/modules/services/networking/headscale.nix49
-rw-r--r--nixos/modules/services/networking/hostapd.nix4
-rw-r--r--nixos/modules/services/networking/imaginary.nix113
-rw-r--r--nixos/modules/services/networking/minidlna.nix38
-rw-r--r--nixos/modules/services/networking/mosquitto.nix2
-rw-r--r--nixos/modules/services/networking/mosquitto.xml149
-rw-r--r--nixos/modules/services/networking/multipath.nix1
-rw-r--r--nixos/modules/services/networking/nebula.nix71
-rw-r--r--nixos/modules/services/networking/networkd-dispatcher.nix63
-rw-r--r--nixos/modules/services/networking/ntp/chrony.nix4
-rw-r--r--nixos/modules/services/networking/openconnect.nix1
-rw-r--r--nixos/modules/services/networking/openvpn.nix32
-rw-r--r--nixos/modules/services/networking/pleroma.nix2
-rw-r--r--nixos/modules/services/networking/pleroma.xml244
-rw-r--r--nixos/modules/services/networking/prosody.nix2
-rw-r--r--nixos/modules/services/networking/prosody.xml92
-rw-r--r--nixos/modules/services/networking/soju.nix2
-rw-r--r--nixos/modules/services/networking/ssh/sshd.nix168
-rw-r--r--nixos/modules/services/networking/tailscale.nix4
-rw-r--r--nixos/modules/services/networking/unbound.nix2
-rw-r--r--nixos/modules/services/networking/v2raya.nix49
-rw-r--r--nixos/modules/services/networking/wireguard.nix2
-rw-r--r--nixos/modules/services/networking/wpa_supplicant.nix14
-rw-r--r--nixos/modules/services/networking/yggdrasil.nix2
-rw-r--r--nixos/modules/services/networking/yggdrasil.xml157
-rw-r--r--nixos/modules/services/search/meilisearch.nix4
-rw-r--r--nixos/modules/services/search/meilisearch.xml87
-rw-r--r--nixos/modules/services/search/opensearch.nix248
-rw-r--r--nixos/modules/services/search/qdrant.nix128
-rw-r--r--nixos/modules/services/security/kanidm.nix2
-rw-r--r--nixos/modules/services/security/privacyidea.nix4
-rw-r--r--nixos/modules/services/security/yubikey-agent.nix3
-rw-r--r--nixos/modules/services/system/nscd.nix10
-rw-r--r--nixos/modules/services/torrent/rtorrent.nix11
-rw-r--r--nixos/modules/services/web-apps/akkoma.md8
-rw-r--r--nixos/modules/services/web-apps/akkoma.nix14
-rw-r--r--nixos/modules/services/web-apps/akkoma.xml398
-rw-r--r--nixos/modules/services/web-apps/alps.nix2
-rw-r--r--nixos/modules/services/web-apps/cloudlog.nix2
-rw-r--r--nixos/modules/services/web-apps/discourse.nix7
-rw-r--r--nixos/modules/services/web-apps/discourse.xml331
-rw-r--r--nixos/modules/services/web-apps/dokuwiki.nix4
-rw-r--r--nixos/modules/services/web-apps/galene.nix2
-rw-r--r--nixos/modules/services/web-apps/grocy.nix2
-rw-r--r--nixos/modules/services/web-apps/grocy.xml84
-rw-r--r--nixos/modules/services/web-apps/hledger-web.nix2
-rw-r--r--nixos/modules/services/web-apps/jirafeau.nix2
-rw-r--r--nixos/modules/services/web-apps/jitsi-meet.nix2
-rw-r--r--nixos/modules/services/web-apps/jitsi-meet.xml55
-rw-r--r--nixos/modules/services/web-apps/keycloak.nix2
-rw-r--r--nixos/modules/services/web-apps/keycloak.xml177
-rw-r--r--nixos/modules/services/web-apps/lemmy.nix2
-rw-r--r--nixos/modules/services/web-apps/lemmy.xml53
-rw-r--r--nixos/modules/services/web-apps/limesurvey.nix2
-rw-r--r--nixos/modules/services/web-apps/matomo.nix2
-rw-r--r--nixos/modules/services/web-apps/matomo.xml107
-rw-r--r--nixos/modules/services/web-apps/nextcloud-notify_push.nix96
-rw-r--r--nixos/modules/services/web-apps/nextcloud.nix4
-rw-r--r--nixos/modules/services/web-apps/nextcloud.xml333
-rw-r--r--nixos/modules/services/web-apps/onlyoffice.nix7
-rw-r--r--nixos/modules/services/web-apps/pict-rs.nix2
-rw-r--r--nixos/modules/services/web-apps/pict-rs.xml185
-rw-r--r--nixos/modules/services/web-apps/plausible.nix2
-rw-r--r--nixos/modules/services/web-apps/plausible.xml45
-rw-r--r--nixos/modules/services/web-apps/wordpress.nix63
-rw-r--r--nixos/modules/services/web-servers/caddy/default.nix3
-rw-r--r--nixos/modules/services/web-servers/garage.nix2
-rw-r--r--nixos/modules/services/web-servers/garage.xml206
-rw-r--r--nixos/modules/services/web-servers/nginx/default.nix25
-rw-r--r--nixos/modules/services/x11/desktop-managers/cinnamon.nix3
-rw-r--r--nixos/modules/services/x11/desktop-managers/gnome.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/gnome.xml261
-rw-r--r--nixos/modules/services/x11/desktop-managers/pantheon.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/pantheon.xml171
-rw-r--r--nixos/modules/services/x11/desktop-managers/plasma5.nix89
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm.nix2
-rw-r--r--nixos/modules/services/x11/display-managers/sddm.nix10
-rw-r--r--nixos/modules/services/x11/extra-layouts.nix4
-rw-r--r--nixos/modules/system/boot/binfmt.nix14
-rw-r--r--nixos/modules/system/boot/initrd-ssh.nix10
-rw-r--r--nixos/modules/system/boot/kernel.nix2
-rw-r--r--nixos/modules/system/boot/loader/external/external.nix2
-rw-r--r--nixos/modules/system/boot/loader/external/external.xml43
-rw-r--r--nixos/modules/system/boot/luksroot.nix13
-rw-r--r--nixos/modules/system/boot/plymouth.nix3
-rw-r--r--nixos/modules/system/boot/stage-1.nix2
-rw-r--r--nixos/modules/system/boot/systemd/coredump.nix16
-rw-r--r--nixos/modules/system/boot/systemd/initrd.nix7
-rw-r--r--nixos/modules/system/boot/systemd/repart.nix123
-rw-r--r--nixos/modules/tasks/filesystems/envfs.nix18
-rw-r--r--nixos/modules/virtualisation/amazon-image.nix6
-rw-r--r--nixos/modules/virtualisation/amazon-options.nix4
-rw-r--r--nixos/modules/virtualisation/docker.nix4
-rw-r--r--nixos/modules/virtualisation/linode-config.nix4
-rw-r--r--nixos/modules/virtualisation/multipass.nix61
-rw-r--r--nixos/modules/virtualisation/nixos-containers.nix11
-rw-r--r--nixos/modules/virtualisation/openstack-options.nix4
-rw-r--r--nixos/modules/virtualisation/podman/default.nix10
-rw-r--r--nixos/modules/virtualisation/qemu-vm.nix32
-rw-r--r--nixos/modules/virtualisation/virtualbox-image.nix47
233 files changed, 2814 insertions, 8039 deletions
diff --git a/nixos/modules/config/no-x-libs.nix b/nixos/modules/config/no-x-libs.nix
index 9542de9fa1f62..eb1e41a3d8dc6 100644
--- a/nixos/modules/config/no-x-libs.nix
+++ b/nixos/modules/config/no-x-libs.nix
@@ -30,8 +30,8 @@ with lib;
       beam = super.beam_nox;
       cairo = super.cairo.override { x11Support = false; };
       dbus = super.dbus.override { x11Support = false; };
-      ffmpeg_4 = super.ffmpeg_4-headless;
-      ffmpeg_5 = super.ffmpeg_5-headless;
+      ffmpeg_4 = super.ffmpeg_4.override { ffmpegVariant = "headless"; };
+      ffmpeg_5 = super.ffmpeg_5.override { ffmpegVariant = "headless"; };
       # dep of graphviz, libXpm is optional for Xpm support
       gd = super.gd.override { withXorg = false; };
       gobject-introspection = super.gobject-introspection.override { x11Support = false; };
@@ -46,8 +46,10 @@ with lib;
       libextractor = super.libextractor.override { gtkSupport = false; };
       libva = super.libva-minimal;
       limesuite = super.limesuite.override { withGui = false; };
+      mc = super.mc.override { x11Support = false; };
       mpv-unwrapped = super.mpv-unwrapped.override { sdl2Support = false; x11Support = false; };
       msmtp = super.msmtp.override { withKeyring = false; };
+      neofetch = super.neofetch.override { x11Support = false; };
       networkmanager-fortisslvpn = super.networkmanager-fortisslvpn.override { withGnome = false; };
       networkmanager-iodine = super.networkmanager-iodine.override { withGnome = false; };
       networkmanager-l2tp = super.networkmanager-l2tp.override { withGnome = false; };
@@ -59,12 +61,13 @@ with lib;
       pinentry = super.pinentry.override { enabledFlavors = [ "curses" "tty" "emacs" ]; withLibsecret = false; };
       qemu = super.qemu.override { gtkSupport = false; spiceSupport = false; sdlSupport = false; };
       qrencode = super.qrencode.overrideAttrs (_: { doCheck = false; });
-      qt5 = super.qt5.overrideScope' (self: super: {
-        qtbase = super.qtbase.override { withGtk3 = false; };
+      qt5 = super.qt5.overrideScope' (self': super': {
+        qtbase = super'.qtbase.override { withGtk3 = false; };
       });
       stoken = super.stoken.override { withGTK3 = false; };
       # translateManpages -> perlPackages.po4a -> texlive-combined-basic -> texlive-core-big -> libX11
       util-linux = super.util-linux.override { translateManpages = false; };
+      vim-full = super.vim-full.override { guiSupport = false; };
       zbar = super.zbar.override { enableVideo = false; withXorg = false; };
     }));
   };
diff --git a/nixos/modules/config/stevenblack.nix b/nixos/modules/config/stevenblack.nix
index ec6868484942b..07a0aa339a561 100644
--- a/nixos/modules/config/stevenblack.nix
+++ b/nixos/modules/config/stevenblack.nix
@@ -15,7 +15,7 @@ let
 in
 {
   options.networking.stevenblack = {
-    enable = mkEnableOption (mdDoc "Enable the stevenblack hosts file blocklist.");
+    enable = mkEnableOption (mdDoc "Enable the stevenblack hosts file blocklist");
 
     block = mkOption {
       type = types.listOf (types.enum [ "fakenews" "gambling" "porn" "social" ]);
diff --git a/nixos/modules/config/update-users-groups.pl b/nixos/modules/config/update-users-groups.pl
index 4368ec24ea9e9..54352a517a24d 100644
--- a/nixos/modules/config/update-users-groups.pl
+++ b/nixos/modules/config/update-users-groups.pl
@@ -215,10 +215,12 @@ foreach my $u (@{$spec->{users}}) {
     } else {
         $u->{uid} = allocUid($name, $u->{isSystemUser}) if !defined $u->{uid};
 
-        if (defined $u->{initialPassword}) {
-            $u->{hashedPassword} = hashPassword($u->{initialPassword});
-        } elsif (defined $u->{initialHashedPassword}) {
-            $u->{hashedPassword} = $u->{initialHashedPassword};
+        if (!defined $u->{hashedPassword}) {
+            if (defined $u->{initialPassword}) {
+                $u->{hashedPassword} = hashPassword($u->{initialPassword});
+            } elsif (defined $u->{initialHashedPassword}) {
+                $u->{hashedPassword} = $u->{initialHashedPassword};
+            }
         }
     }
 
diff --git a/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix
index 76092e738ebd4..ee4692fc6a6a6 100644
--- a/nixos/modules/config/users-groups.nix
+++ b/nixos/modules/config/users-groups.nix
@@ -90,7 +90,7 @@ let
           only has an effect if {option}`uid` is
           {option}`null`, in which case it determines whether
           the user's UID is allocated in the range for system users
-          (below 500) or in the range for normal users (starting at
+          (below 1000) or in the range for normal users (starting at
           1000).
           Exactly one of `isNormalUser` and
           `isSystemUser` must be true.
@@ -273,6 +273,9 @@ let
           {command}`passwd` command. Otherwise, it's
           equivalent to setting the {option}`hashedPassword` option.
 
+          Note that the {option}`hashedPassword` option will override
+          this option if both are set.
+
           ${hashedPasswordDescription}
         '';
       };
@@ -291,6 +294,9 @@ let
           is world-readable in the Nix store, so it should only be
           used for guest accounts or passwords that will be changed
           promptly.
+
+          Note that the {option}`password` option will override this
+          option if both are set.
         '';
       };
 
@@ -677,7 +683,7 @@ in {
           {
             assertion = let
               xor = a: b: a && !b || b && !a;
-              isEffectivelySystemUser = user.isSystemUser || (user.uid != null && user.uid < 500);
+              isEffectivelySystemUser = user.isSystemUser || (user.uid != null && user.uid < 1000);
             in xor isEffectivelySystemUser user.isNormalUser;
             message = ''
               Exactly one of users.users.${user.name}.isSystemUser and users.users.${user.name}.isNormalUser must be set.
diff --git a/nixos/modules/config/zram.nix b/nixos/modules/config/zram.nix
index 87ac53a60b7ed..4df646cf27966 100644
--- a/nixos/modules/config/zram.nix
+++ b/nixos/modules/config/zram.nix
@@ -1,45 +1,27 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
 
   cfg = config.zramSwap;
-
-  # don't set swapDevices as mkDefault, so we can detect user had read our warning
-  # (see below) and made an action (or not)
-  devicesCount = if cfg.swapDevices != null then cfg.swapDevices else cfg.numDevices;
-
-  devices = map (nr: "zram${toString nr}") (range 0 (devicesCount - 1));
-
-  modprobe = "${pkgs.kmod}/bin/modprobe";
-
-  warnings =
-  assert cfg.swapDevices != null -> cfg.numDevices >= cfg.swapDevices;
-  flatten [
-    (optional (cfg.numDevices > 1 && cfg.swapDevices == null) ''
-      Using several small zram devices as swap is no better than using one large.
-      Set either zramSwap.numDevices = 1 or explicitly set zramSwap.swapDevices.
-
-      Previously multiple zram devices were used to enable multithreaded
-      compression. Linux supports multithreaded compression for 1 device
-      since 3.15. See https://lkml.org/lkml/2014/2/28/404 for details.
-    '')
-  ];
+  devices = map (nr: "zram${toString nr}") (lib.range 0 (cfg.swapDevices - 1));
 
 in
 
 {
 
+  imports = [
+    (lib.mkRemovedOptionModule [ "zramSwap" "numDevices" ] "Using ZRAM devices as general purpose ephemeral block devices is no longer supported")
+  ];
+
   ###### interface
 
   options = {
 
     zramSwap = {
 
-      enable = mkOption {
+      enable = lib.mkOption {
         default = false;
-        type = types.bool;
+        type = lib.types.bool;
         description = lib.mdDoc ''
           Enable in-memory compressed devices and swap space provided by the zram
           kernel module.
@@ -49,29 +31,17 @@ in
         '';
       };
 
-      numDevices = mkOption {
+      swapDevices = lib.mkOption {
         default = 1;
-        type = types.int;
+        type = lib.types.int;
         description = lib.mdDoc ''
-          Number of zram devices to create. See also
-          `zramSwap.swapDevices`
+          Number of zram devices to be used as swap, recommended is 1.
         '';
       };
 
-      swapDevices = mkOption {
-        default = null;
-        example = 1;
-        type = with types; nullOr int;
-        description = lib.mdDoc ''
-          Number of zram devices to be used as swap. Must be
-          `<= zramSwap.numDevices`.
-          Default is same as `zramSwap.numDevices`, recommended is 1.
-        '';
-      };
-
-      memoryPercent = mkOption {
+      memoryPercent = lib.mkOption {
         default = 50;
-        type = types.int;
+        type = lib.types.int;
         description = lib.mdDoc ''
           Maximum total amount of memory that can be stored in the zram swap devices
           (as a percentage of your total memory). Defaults to 1/2 of your total
@@ -80,9 +50,9 @@ in
         '';
       };
 
-      memoryMax = mkOption {
+      memoryMax = lib.mkOption {
         default = null;
-        type = with types; nullOr int;
+        type = with lib.types; nullOr int;
         description = lib.mdDoc ''
           Maximum total amount of memory (in bytes) that can be stored in the zram
           swap devices.
@@ -90,9 +60,9 @@ in
         '';
       };
 
-      priority = mkOption {
+      priority = lib.mkOption {
         default = 5;
-        type = types.int;
+        type = lib.types.int;
         description = lib.mdDoc ''
           Priority of the zram swap devices. It should be a number higher than
           the priority of your disk-based swap devices (so that the system will
@@ -100,10 +70,10 @@ in
         '';
       };
 
-      algorithm = mkOption {
+      algorithm = lib.mkOption {
         default = "zstd";
         example = "lz4";
-        type = with types; either (enum [ "lzo" "lz4" "zstd" ]) str;
+        type = with lib.types; either (enum [ "lzo" "lz4" "zstd" ]) str;
         description = lib.mdDoc ''
           Compression algorithm. `lzo` has good compression,
           but is slow. `lz4` has bad compression, but is fast.
@@ -116,9 +86,7 @@ in
 
   };
 
-  config = mkIf cfg.enable {
-
-    inherit warnings;
+  config = lib.mkIf cfg.enable {
 
     system.requiredKernelConfig = with config.lib.kernelConfig; [
       (isModule "ZRAM")
@@ -128,78 +96,25 @@ in
     # once in stage 2 boot, and again when the zram-reloader service starts.
     # boot.kernelModules = [ "zram" ];
 
-    boot.extraModprobeConfig = ''
-      options zram num_devices=${toString cfg.numDevices}
-    '';
-
-    boot.kernelParams = ["zram.num_devices=${toString cfg.numDevices}"];
-
-    services.udev.extraRules = ''
-      KERNEL=="zram[0-9]*", ENV{SYSTEMD_WANTS}="zram-init-%k.service", TAG+="systemd"
-    '';
-
-    systemd.services =
-      let
-        createZramInitService = dev:
-          nameValuePair "zram-init-${dev}" {
-            description = "Init swap on zram-based device ${dev}";
-            after = [ "dev-${dev}.device" "zram-reloader.service" ];
-            requires = [ "dev-${dev}.device" "zram-reloader.service" ];
-            before = [ "dev-${dev}.swap" ];
-            requiredBy = [ "dev-${dev}.swap" ];
-            unitConfig.DefaultDependencies = false; # needed to prevent a cycle
-            serviceConfig = {
-              Type = "oneshot";
-              RemainAfterExit = true;
-              ExecStop = "${pkgs.runtimeShell} -c 'echo 1 > /sys/class/block/${dev}/reset'";
-            };
-            script = ''
-              set -euo pipefail
-
-              # Calculate memory to use for zram
-              mem=$(${pkgs.gawk}/bin/awk '/MemTotal: / {
-                  value=int($2*${toString cfg.memoryPercent}/100.0/${toString devicesCount}*1024);
-                    ${lib.optionalString (cfg.memoryMax != null) ''
-                      memory_max=int(${toString cfg.memoryMax}/${toString devicesCount});
-                      if (value > memory_max) { value = memory_max }
-                    ''}
-                  print value
-              }' /proc/meminfo)
-
-              ${pkgs.util-linux}/sbin/zramctl --size $mem --algorithm ${cfg.algorithm} /dev/${dev}
-              ${pkgs.util-linux}/sbin/mkswap /dev/${dev}
-            '';
-            restartIfChanged = false;
-          };
-      in listToAttrs ((map createZramInitService devices) ++ [(nameValuePair "zram-reloader"
-        {
-          description = "Reload zram kernel module when number of devices changes";
-          wants = [ "systemd-udevd.service" ];
-          after = [ "systemd-udevd.service" ];
-          unitConfig.DefaultDependencies = false; # needed to prevent a cycle
-          serviceConfig = {
-            Type = "oneshot";
-            RemainAfterExit = true;
-            ExecStartPre = "-${modprobe} -r zram";
-            ExecStart = "-${modprobe} zram";
-            ExecStop = "-${modprobe} -r zram";
-          };
-          restartTriggers = [
-            cfg.numDevices
-            cfg.algorithm
-            cfg.memoryPercent
-          ];
-          restartIfChanged = true;
-        })]);
-
-    swapDevices =
-      let
-        useZramSwap = dev:
-          {
-            device = "/dev/${dev}";
-            priority = cfg.priority;
-          };
-      in map useZramSwap devices;
+    systemd.packages = [ pkgs.zram-generator ];
+    systemd.services."systemd-zram-setup@".path = [ pkgs.util-linux ]; # for mkswap
+
+    environment.etc."systemd/zram-generator.conf".source =
+      (pkgs.formats.ini { }).generate "zram-generator.conf" (lib.listToAttrs
+        (builtins.map
+          (dev: {
+            name = dev;
+            value =
+              let
+                size = "${toString cfg.memoryPercent} / 100 * ram";
+              in
+              {
+                zram-size = if cfg.memoryMax != null then "min(${size}, ${toString cfg.memoryMax} / 1024 / 1024)" else size;
+                compression-algorithm = cfg.algorithm;
+                swap-priority = cfg.priority;
+              };
+          })
+          devices));
 
   };
 
diff --git a/nixos/modules/hardware/all-firmware.nix b/nixos/modules/hardware/all-firmware.nix
index 2d5a0007ff01c..75247286368bc 100644
--- a/nixos/modules/hardware/all-firmware.nix
+++ b/nixos/modules/hardware/all-firmware.nix
@@ -65,8 +65,6 @@ in {
       ] ++ optional pkgs.stdenv.hostPlatform.isAarch raspberrypiWirelessFirmware
         ++ optionals (versionOlder config.boot.kernelPackages.kernel.version "4.13") [
         rtl8723bs-firmware
-      ] ++ optionals (versionOlder config.boot.kernelPackages.kernel.version "5.16") [
-        rtw89-firmware
       ];
       hardware.wirelessRegulatoryDatabase = true;
     })
diff --git a/nixos/modules/hardware/flipperzero.nix b/nixos/modules/hardware/flipperzero.nix
new file mode 100644
index 0000000000000..82f9b76fa3a73
--- /dev/null
+++ b/nixos/modules/hardware/flipperzero.nix
@@ -0,0 +1,18 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.hardware.flipperzero;
+
+in
+
+{
+  options.hardware.flipperzero.enable = mkEnableOption (mdDoc "udev rules and software for Flipper Zero devices");
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.qFlipper ];
+    services.udev.packages = [ pkgs.qFlipper ];
+  };
+}
diff --git a/nixos/modules/hardware/keyboard/qmk.nix b/nixos/modules/hardware/keyboard/qmk.nix
new file mode 100644
index 0000000000000..df3bcaeccd2ec
--- /dev/null
+++ b/nixos/modules/hardware/keyboard/qmk.nix
@@ -0,0 +1,16 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.hardware.keyboard.qmk;
+  inherit (lib) mdDoc mkEnableOption mkIf;
+
+in
+{
+  options.hardware.keyboard.qmk = {
+    enable = mkEnableOption (mdDoc "non-root access to the firmware of QMK keyboards");
+  };
+
+  config = mkIf cfg.enable {
+    services.udev.packages = [ pkgs.qmk-udev-rules ];
+  };
+}
diff --git a/nixos/modules/hardware/keyboard/teck.nix b/nixos/modules/hardware/keyboard/teck.nix
index 2705668d9a750..8376c6b9c50b2 100644
--- a/nixos/modules/hardware/keyboard/teck.nix
+++ b/nixos/modules/hardware/keyboard/teck.nix
@@ -1,16 +1,16 @@
 { config, lib, pkgs, ... }:
 
-with lib;
 let
   cfg = config.hardware.keyboard.teck;
+  inherit (lib) mdDoc mkEnableOption mkIf;
+
 in
 {
   options.hardware.keyboard.teck = {
-    enable = mkEnableOption (lib.mdDoc "non-root access to the firmware of TECK keyboards");
+    enable = mkEnableOption (mdDoc "non-root access to the firmware of TECK keyboards");
   };
 
   config = mkIf cfg.enable {
     services.udev.packages = [ pkgs.teck-udev-rules ];
   };
 }
-
diff --git a/nixos/modules/hardware/keyboard/uhk.nix b/nixos/modules/hardware/keyboard/uhk.nix
index c18051439938d..17baff83d886b 100644
--- a/nixos/modules/hardware/keyboard/uhk.nix
+++ b/nixos/modules/hardware/keyboard/uhk.nix
@@ -1,13 +1,14 @@
 { config, lib, pkgs, ... }:
 
-with lib;
 let
   cfg = config.hardware.keyboard.uhk;
+  inherit (lib) mdDoc mkEnableOption mkIf;
+
 in
 {
   options.hardware.keyboard.uhk = {
-    enable = mkEnableOption (lib.mdDoc ''
-    non-root access to the firmware of UHK keyboards.
+    enable = mkEnableOption (mdDoc ''
+      non-root access to the firmware of UHK keyboards.
       You need it when you want to flash a new firmware on the keyboard.
       Access to the keyboard is granted to users in the "input" group.
       You may want to install the uhk-agent package.
diff --git a/nixos/modules/hardware/keyboard/zsa.nix b/nixos/modules/hardware/keyboard/zsa.nix
index 5bf4022cdc435..a04b67b5c8d0e 100644
--- a/nixos/modules/hardware/keyboard/zsa.nix
+++ b/nixos/modules/hardware/keyboard/zsa.nix
@@ -1,21 +1,18 @@
 { config, lib, pkgs, ... }:
 
 let
-  inherit (lib) mkOption mkIf types;
   cfg = config.hardware.keyboard.zsa;
+  inherit (lib) mkEnableOption mkIf mdDoc;
+
 in
 {
   options.hardware.keyboard.zsa = {
-    enable = mkOption {
-      type = types.bool;
-      default = false;
-      description = lib.mdDoc ''
-        Enables udev rules for keyboards from ZSA like the ErgoDox EZ, Planck EZ and Moonlander Mark I.
-        You need it when you want to flash a new configuration on the keyboard
-        or use their live training in the browser.
-        You may want to install the wally-cli package.
-      '';
-    };
+    enable = mkEnableOption (mdDoc ''
+      udev rules for keyboards from ZSA like the ErgoDox EZ, Planck EZ and Moonlander Mark I.
+      You need it when you want to flash a new configuration on the keyboard
+      or use their live training in the browser.
+      You may want to install the wally-cli package.
+    '');
   };
 
   config = mkIf cfg.enable {
diff --git a/nixos/modules/hardware/video/nvidia.nix b/nixos/modules/hardware/video/nvidia.nix
index cee230ac41cb1..434931ccae5a7 100644
--- a/nixos/modules/hardware/video/nvidia.nix
+++ b/nixos/modules/hardware/video/nvidia.nix
@@ -21,7 +21,8 @@ let
   pCfg = cfg.prime;
   syncCfg = pCfg.sync;
   offloadCfg = pCfg.offload;
-  primeEnabled = syncCfg.enable || offloadCfg.enable;
+  reverseSyncCfg = pCfg.reverseSync;
+  primeEnabled = syncCfg.enable || reverseSyncCfg.enable || offloadCfg.enable;
   nvidiaPersistencedEnabled =  cfg.nvidiaPersistenced;
   nvidiaSettings = cfg.nvidiaSettings;
   busIDType = types.strMatching "([[:print:]]+[\:\@][0-9]{1,3}\:[0-9]{1,2}\:[0-9])?";
@@ -31,7 +32,8 @@ in
   imports =
     [
       (mkRenamedOptionModule [ "hardware" "nvidia" "optimus_prime" "enable" ] [ "hardware" "nvidia" "prime" "sync" "enable" ])
-      (mkRenamedOptionModule [ "hardware" "nvidia" "optimus_prime" "allowExternalGpu" ] [ "hardware" "nvidia" "prime" "sync" "allowExternalGpu" ])
+      (mkRenamedOptionModule [ "hardware" "nvidia" "optimus_prime" "allowExternalGpu" ] [ "hardware" "nvidia" "prime" "allowExternalGpu" ])
+      (mkRenamedOptionModule [ "hardware" "nvidia" "prime" "sync" "allowExternalGpu" ] [ "hardware" "nvidia" "prime" "allowExternalGpu" ])
       (mkRenamedOptionModule [ "hardware" "nvidia" "optimus_prime" "nvidiaBusId" ] [ "hardware" "nvidia" "prime" "nvidiaBusId" ])
       (mkRenamedOptionModule [ "hardware" "nvidia" "optimus_prime" "intelBusId" ] [ "hardware" "nvidia" "prime" "intelBusId" ])
     ];
@@ -104,16 +106,17 @@ in
       description = lib.mdDoc ''
         Enable NVIDIA Optimus support using the NVIDIA proprietary driver via PRIME.
         If enabled, the NVIDIA GPU will be always on and used for all rendering,
-        while enabling output to displays attached only to the integrated Intel GPU
-        without a multiplexer.
+        while enabling output to displays attached only to the integrated Intel/AMD
+        GPU without a multiplexer.
 
         Note that this option only has any effect if the "nvidia" driver is specified
         in {option}`services.xserver.videoDrivers`, and it should preferably
         be the only driver there.
 
-        If this is enabled, then the bus IDs of the NVIDIA and Intel GPUs have to be
-        specified ({option}`hardware.nvidia.prime.nvidiaBusId` and
-        {option}`hardware.nvidia.prime.intelBusId`).
+        If this is enabled, then the bus IDs of the NVIDIA and Intel/AMD GPUs have to
+        be specified ({option}`hardware.nvidia.prime.nvidiaBusId` and
+        {option}`hardware.nvidia.prime.intelBusId` or
+        {option}`hardware.nvidia.prime.amdgpuBusId`).
 
         If you enable this, you may want to also enable kernel modesetting for the
         NVIDIA driver ({option}`hardware.nvidia.modesetting.enable`) in order
@@ -125,11 +128,11 @@ in
       '';
     };
 
-    hardware.nvidia.prime.sync.allowExternalGpu = mkOption {
+    hardware.nvidia.prime.allowExternalGpu = mkOption {
       type = types.bool;
       default = false;
       description = lib.mdDoc ''
-        Configure X to allow external NVIDIA GPUs when using optimus.
+        Configure X to allow external NVIDIA GPUs when using Prime [Reverse] sync optimus.
       '';
     };
 
@@ -139,9 +142,54 @@ in
       description = lib.mdDoc ''
         Enable render offload support using the NVIDIA proprietary driver via PRIME.
 
-        If this is enabled, then the bus IDs of the NVIDIA and Intel GPUs have to be
-        specified ({option}`hardware.nvidia.prime.nvidiaBusId` and
-        {option}`hardware.nvidia.prime.intelBusId`).
+        If this is enabled, then the bus IDs of the NVIDIA and Intel/AMD GPUs have to
+        be specified ({option}`hardware.nvidia.prime.nvidiaBusId` and
+        {option}`hardware.nvidia.prime.intelBusId` or
+        {option}`hardware.nvidia.prime.amdgpuBusId`).
+      '';
+    };
+
+    hardware.nvidia.prime.offload.enableOffloadCmd = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Adds a `nvidia-offload` convenience script to {option}`environment.systemPackages`
+        for offloading programs to an nvidia device. To work, should have also enabled
+        {option}`hardware.nvidia.prime.offload.enable` or {option}`hardware.nvidia.prime.reverseSync.enable`.
+
+        Example usage `nvidia-offload sauerbraten_client`.
+      '';
+    };
+
+    hardware.nvidia.prime.reverseSync.enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Warning: This feature is relatively new, depending on your system this might
+        work poorly. AMD support, especially so.
+        See: https://forums.developer.nvidia.com/t/the-all-new-outputsink-feature-aka-reverse-prime/129828
+
+        Enable NVIDIA Optimus support using the NVIDIA proprietary driver via reverse
+        PRIME. If enabled, the Intel/AMD GPU will be used for all rendering, while
+        enabling output to displays attached only to the NVIDIA GPU without a
+        multiplexer.
+
+        Note that this option only has any effect if the "nvidia" driver is specified
+        in {option}`services.xserver.videoDrivers`, and it should preferably
+        be the only driver there.
+
+        If this is enabled, then the bus IDs of the NVIDIA and Intel/AMD GPUs have to
+        be specified ({option}`hardware.nvidia.prime.nvidiaBusId` and
+        {option}`hardware.nvidia.prime.intelBusId` or
+        {option}`hardware.nvidia.prime.amdgpuBusId`).
+
+        If you enable this, you may want to also enable kernel modesetting for the
+        NVIDIA driver ({option}`hardware.nvidia.modesetting.enable`) in order
+        to prevent tearing.
+
+        Note that this configuration will only be successful when a display manager
+        for which the {option}`services.xserver.displayManager.setupCommands`
+        option is supported is used.
       '';
     };
 
@@ -206,6 +254,13 @@ in
       }
 
       {
+        assertion = offloadCfg.enableOffloadCmd -> offloadCfg.enable || reverseSyncCfg.enable;
+        message = ''
+          Offload command requires offloading or reverse prime sync to be enabled.
+        '';
+      }
+
+      {
         assertion = primeEnabled -> pCfg.nvidiaBusId != "" && (pCfg.intelBusId != "" || pCfg.amdgpuBusId != "");
         message = ''
           When NVIDIA PRIME is enabled, the GPU bus IDs must configured.
@@ -218,8 +273,18 @@ in
       }
 
       {
+        assertion = (reverseSyncCfg.enable && pCfg.amdgpuBusId != "") -> versionAtLeast nvidia_x11.version "470.0";
+        message = "NVIDIA PRIME render offload for AMD APUs is currently only supported on versions >= 470 beta.";
+      }
+
+      {
         assertion = !(syncCfg.enable && offloadCfg.enable);
-        message = "Only one NVIDIA PRIME solution may be used at a time.";
+        message = "PRIME Sync and Offload cannot be both enabled";
+      }
+
+      {
+        assertion = !(syncCfg.enable && reverseSyncCfg.enable);
+        message = "PRIME Sync and PRIME Reverse Sync cannot be both enabled";
       }
 
       {
@@ -257,8 +322,10 @@ in
     # - Configure the display manager to run specific `xrandr` commands which will
     #   configure/enable displays connected to the Intel iGPU / AMD APU.
 
-    services.xserver.drivers = let
-    in optional primeEnabled {
+    # reverse sync implies offloading
+    hardware.nvidia.prime.offload.enable = mkDefault reverseSyncCfg.enable;
+
+    services.xserver.drivers = optional primeEnabled {
       name = igpuDriver;
       display = offloadCfg.enable;
       modules = optionals (igpuDriver == "amdgpu") [ pkgs.xorg.xf86videoamdgpu ];
@@ -273,7 +340,7 @@ in
       deviceSection = optionalString primeEnabled
         ''
           BusID "${pCfg.nvidiaBusId}"
-          ${optionalString syncCfg.allowExternalGpu "Option \"AllowExternalGpus\""}
+          ${optionalString pCfg.allowExternalGpu "Option \"AllowExternalGpus\""}
         '';
       screenSection =
         ''
@@ -290,19 +357,22 @@ in
 
     services.xserver.serverLayoutSection = optionalString syncCfg.enable ''
       Inactive "Device-${igpuDriver}[0]"
+    '' + optionalString reverseSyncCfg.enable ''
+      Inactive "Device-nvidia[0]"
     '' + optionalString offloadCfg.enable ''
       Option "AllowNVIDIAGPUScreens"
     '';
 
     services.xserver.displayManager.setupCommands = let
-      sinkGpuProviderName = if igpuDriver == "amdgpu" then
+      gpuProviderName = if igpuDriver == "amdgpu" then
         # find the name of the provider if amdgpu
         "`${pkgs.xorg.xrandr}/bin/xrandr --listproviders | ${pkgs.gnugrep}/bin/grep -i AMD | ${pkgs.gnused}/bin/sed -n 's/^.*name://p'`"
       else
         igpuDriver;
-    in optionalString syncCfg.enable ''
+      providerCmdParams = if syncCfg.enable then "\"${gpuProviderName}\" NVIDIA-0" else "NVIDIA-G0 \"${gpuProviderName}\"";
+    in optionalString (syncCfg.enable || reverseSyncCfg.enable) ''
       # Added by nvidia configuration module for Optimus/PRIME.
-      ${pkgs.xorg.xrandr}/bin/xrandr --setprovideroutputsource "${sinkGpuProviderName}" NVIDIA-0
+      ${pkgs.xorg.xrandr}/bin/xrandr --setprovideroutputsource ${providerCmdParams}
       ${pkgs.xorg.xrandr}/bin/xrandr --auto
     '';
 
@@ -325,7 +395,16 @@ in
 
     environment.systemPackages = [ nvidia_x11.bin ]
       ++ optionals cfg.nvidiaSettings [ nvidia_x11.settings ]
-      ++ optionals nvidiaPersistencedEnabled [ nvidia_x11.persistenced ];
+      ++ optionals nvidiaPersistencedEnabled [ nvidia_x11.persistenced ]
+      ++ optionals offloadCfg.enableOffloadCmd [
+        (pkgs.writeShellScriptBin "nvidia-offload" ''
+          export __NV_PRIME_RENDER_OFFLOAD=1
+          export __NV_PRIME_RENDER_OFFLOAD_PROVIDER=NVIDIA-G0
+          export __GLX_VENDOR_LIBRARY_NAME=nvidia
+          export __VK_LAYER_NV_optimus=NVIDIA_only
+          exec "$@"
+        '')
+      ];
 
     systemd.packages = optional cfg.powerManagement.enable nvidia_x11.out;
 
@@ -382,7 +461,9 @@ in
     # If requested enable modesetting via kernel parameter.
     boot.kernelParams = optional (offloadCfg.enable || cfg.modesetting.enable) "nvidia-drm.modeset=1"
       ++ optional cfg.powerManagement.enable "nvidia.NVreg_PreserveVideoMemoryAllocations=1"
-      ++ optional cfg.open "nvidia.NVreg_OpenRmEnableUnsupportedGpus=1";
+      ++ optional cfg.open "nvidia.NVreg_OpenRmEnableUnsupportedGpus=1"
+      # proprietary driver is not compiled with support for X86_KERNEL_IBT
+      ++ optional (!cfg.open && config.boot.kernelPackages.kernel.kernelAtLeast "6.2") "ibt=off";
 
     services.udev.extraRules =
       ''
diff --git a/nixos/modules/i18n/input-method/default.nix b/nixos/modules/i18n/input-method/default.nix
index 07fb86bcc25e8..5f803b4f2ee79 100644
--- a/nixos/modules/i18n/input-method/default.nix
+++ b/nixos/modules/i18n/input-method/default.nix
@@ -66,7 +66,7 @@ in
 
   meta = {
     maintainers = with lib.maintainers; [ ericsagnes ];
-    doc = ./default.xml;
+    doc = ./default.md;
   };
 
 }
diff --git a/nixos/modules/i18n/input-method/default.xml b/nixos/modules/i18n/input-method/default.xml
deleted file mode 100644
index 7b7907cd32a62..0000000000000
--- a/nixos/modules/i18n/input-method/default.xml
+++ /dev/null
@@ -1,275 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-input-methods">
-  <title>Input Methods</title>
-  <para>
-    Input methods are an operating system component that allows any
-    data, such as keyboard strokes or mouse movements, to be received as
-    input. In this way users can enter characters and symbols not found
-    on their input devices. Using an input method is obligatory for any
-    language that has more graphemes than there are keys on the
-    keyboard.
-  </para>
-  <para>
-    The following input methods are available in NixOS:
-  </para>
-  <itemizedlist spacing="compact">
-    <listitem>
-      <para>
-        IBus: The intelligent input bus.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Fcitx: A customizable lightweight input method.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Nabi: A Korean input method based on XIM.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Uim: The universal input method, is a library with a XIM bridge.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Hime: An extremely easy-to-use input method framework.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Kime: Korean IME
-      </para>
-    </listitem>
-  </itemizedlist>
-  <section xml:id="module-services-input-methods-ibus">
-    <title>IBus</title>
-    <para>
-      IBus is an Intelligent Input Bus. It provides full featured and
-      user friendly input method user interface.
-    </para>
-    <para>
-      The following snippet can be used to configure IBus:
-    </para>
-    <programlisting>
-i18n.inputMethod = {
-  enabled = &quot;ibus&quot;;
-  ibus.engines = with pkgs.ibus-engines; [ anthy hangul mozc ];
-};
-</programlisting>
-    <para>
-      <literal>i18n.inputMethod.ibus.engines</literal> is optional and
-      can be used to add extra IBus engines.
-    </para>
-    <para>
-      Available extra IBus engines are:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          Anthy (<literal>ibus-engines.anthy</literal>): Anthy is a
-          system for Japanese input method. It converts Hiragana text to
-          Kana Kanji mixed text.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Hangul (<literal>ibus-engines.hangul</literal>): Korean input
-          method.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          m17n (<literal>ibus-engines.m17n</literal>): m17n is an input
-          method that uses input methods and corresponding icons in the
-          m17n database.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          mozc (<literal>ibus-engines.mozc</literal>): A Japanese input
-          method from Google.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Table (<literal>ibus-engines.table</literal>): An input method
-          that load tables of input methods.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          table-others (<literal>ibus-engines.table-others</literal>):
-          Various table-based input methods. To use this, and any other
-          table-based input methods, it must appear in the list of
-          engines along with <literal>table</literal>. For example:
-        </para>
-        <programlisting>
-ibus.engines = with pkgs.ibus-engines; [ table table-others ];
-</programlisting>
-      </listitem>
-    </itemizedlist>
-    <para>
-      To use any input method, the package must be added in the
-      configuration, as shown above, and also (after running
-      <literal>nixos-rebuild</literal>) the input method must be added
-      from IBus’ preference dialog.
-    </para>
-    <section xml:id="module-services-input-methods-troubleshooting">
-      <title>Troubleshooting</title>
-      <para>
-        If IBus works in some applications but not others, a likely
-        cause of this is that IBus is depending on a different version
-        of <literal>glib</literal> to what the applications are
-        depending on. This can be checked by running
-        <literal>nix-store -q --requisites &lt;path&gt; | grep glib</literal>,
-        where <literal>&lt;path&gt;</literal> is the path of either IBus
-        or an application in the Nix store. The <literal>glib</literal>
-        packages must match exactly. If they do not, uninstalling and
-        reinstalling the application is a likely fix.
-      </para>
-    </section>
-  </section>
-  <section xml:id="module-services-input-methods-fcitx">
-    <title>Fcitx</title>
-    <para>
-      Fcitx is an input method framework with extension support. It has
-      three built-in Input Method Engine, Pinyin, QuWei and Table-based
-      input methods.
-    </para>
-    <para>
-      The following snippet can be used to configure Fcitx:
-    </para>
-    <programlisting>
-i18n.inputMethod = {
-  enabled = &quot;fcitx&quot;;
-  fcitx.engines = with pkgs.fcitx-engines; [ mozc hangul m17n ];
-};
-</programlisting>
-    <para>
-      <literal>i18n.inputMethod.fcitx.engines</literal> is optional and
-      can be used to add extra Fcitx engines.
-    </para>
-    <para>
-      Available extra Fcitx engines are:
-    </para>
-    <itemizedlist spacing="compact">
-      <listitem>
-        <para>
-          Anthy (<literal>fcitx-engines.anthy</literal>): Anthy is a
-          system for Japanese input method. It converts Hiragana text to
-          Kana Kanji mixed text.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Chewing (<literal>fcitx-engines.chewing</literal>): Chewing is
-          an intelligent Zhuyin input method. It is one of the most
-          popular input methods among Traditional Chinese Unix users.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Hangul (<literal>fcitx-engines.hangul</literal>): Korean input
-          method.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Unikey (<literal>fcitx-engines.unikey</literal>): Vietnamese
-          input method.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          m17n (<literal>fcitx-engines.m17n</literal>): m17n is an input
-          method that uses input methods and corresponding icons in the
-          m17n database.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          mozc (<literal>fcitx-engines.mozc</literal>): A Japanese input
-          method from Google.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          table-others (<literal>fcitx-engines.table-others</literal>):
-          Various table-based input methods.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="module-services-input-methods-nabi">
-    <title>Nabi</title>
-    <para>
-      Nabi is an easy to use Korean X input method. It allows you to
-      enter phonetic Korean characters (hangul) and pictographic Korean
-      characters (hanja).
-    </para>
-    <para>
-      The following snippet can be used to configure Nabi:
-    </para>
-    <programlisting>
-i18n.inputMethod = {
-  enabled = &quot;nabi&quot;;
-};
-</programlisting>
-  </section>
-  <section xml:id="module-services-input-methods-uim">
-    <title>Uim</title>
-    <para>
-      Uim (short for <quote>universal input method</quote>) is a
-      multilingual input method framework. Applications can use it
-      through so-called bridges.
-    </para>
-    <para>
-      The following snippet can be used to configure uim:
-    </para>
-    <programlisting>
-i18n.inputMethod = {
-  enabled = &quot;uim&quot;;
-};
-</programlisting>
-    <para>
-      Note: The <xref linkend="opt-i18n.inputMethod.uim.toolbar" />
-      option can be used to choose uim toolbar.
-    </para>
-  </section>
-  <section xml:id="module-services-input-methods-hime">
-    <title>Hime</title>
-    <para>
-      Hime is an extremely easy-to-use input method framework. It is
-      lightweight, stable, powerful and supports many commonly used
-      input methods, including Cangjie, Zhuyin, Dayi, Rank, Shrimp,
-      Greek, Korean Pinyin, Latin Alphabet, etc…
-    </para>
-    <para>
-      The following snippet can be used to configure Hime:
-    </para>
-    <programlisting>
-i18n.inputMethod = {
-  enabled = &quot;hime&quot;;
-};
-</programlisting>
-  </section>
-  <section xml:id="module-services-input-methods-kime">
-    <title>Kime</title>
-    <para>
-      Kime is Korean IME. it’s built with Rust language and let you get
-      simple, safe, fast Korean typing
-    </para>
-    <para>
-      The following snippet can be used to configure Kime:
-    </para>
-    <programlisting>
-i18n.inputMethod = {
-  enabled = &quot;kime&quot;;
-};
-</programlisting>
-  </section>
-</chapter>
diff --git a/nixos/modules/installer/sd-card/sd-image-powerpc64le.nix b/nixos/modules/installer/sd-card/sd-image-powerpc64le.nix
new file mode 100644
index 0000000000000..143c678e43fbc
--- /dev/null
+++ b/nixos/modules/installer/sd-card/sd-image-powerpc64le.nix
@@ -0,0 +1,49 @@
+# To build, use:
+# nix-build nixos -I nixos-config=nixos/modules/installer/sd-card/sd-image-powerpc64le.nix -A config.system.build.sdImage
+{ config, lib, pkgs, ... }:
+
+{
+  imports = [
+    ../../profiles/base.nix
+    ../../profiles/installation-device.nix
+    ./sd-image.nix
+  ];
+
+  boot.loader = {
+    # powerpc64le-linux typically uses petitboot
+    grub.enable = false;
+    generic-extlinux-compatible = {
+      # petitboot is not does not support all of the extlinux extensions to
+      # syslinux, but its parser is very forgiving; it essentially ignores
+      # whatever it doesn't understand.  See below for a filename adjustment.
+      enable = true;
+    };
+  };
+
+  boot.consoleLogLevel = lib.mkDefault 7;
+  boot.kernelParams = [ "console=hvc0" ];
+
+  sdImage = {
+    populateFirmwareCommands = "";
+    populateRootCommands = ''
+      mkdir -p ./files/boot
+      ${config.boot.loader.generic-extlinux-compatible.populateCmd} \
+        -c ${config.system.build.toplevel} \
+        -d ./files/boot
+    ''
+    # https://github.com/open-power/petitboot/blob/master/discover/syslinux-parser.c
+    # petitboot will look in these paths (plus all-caps versions of them):
+    #  /boot/syslinux/syslinux.cfg
+    #  /syslinux/syslinux.cfg
+    #  /syslinux.cfg
+    + ''
+      mv ./files/boot/extlinux ./files/boot/syslinux
+      mv ./files/boot/syslinux/extlinux.conf ./files/boot/syslinux/syslinux.cfg
+    ''
+    # petitboot does not support relative paths for LINUX or INITRD; it prepends
+    # a `/` when parsing these fields
+    + ''
+      sed -i 's_^\(\W\W*\(INITRD\|initrd\|LINUX\|linux\)\W\)\.\./_\1/boot/_' ./files/boot/syslinux/syslinux.cfg
+    '';
+  };
+}
diff --git a/nixos/modules/installer/tools/nix-fallback-paths.nix b/nixos/modules/installer/tools/nix-fallback-paths.nix
index c9cb65dbbe5a3..1058a34133b14 100644
--- a/nixos/modules/installer/tools/nix-fallback-paths.nix
+++ b/nixos/modules/installer/tools/nix-fallback-paths.nix
@@ -1,7 +1,7 @@
 {
-  x86_64-linux = "/nix/store/lsr79q5xqd9dv97wn87x12kzax8s8i1s-nix-2.13.2";
-  i686-linux = "/nix/store/wky9xjwiwzpifgk0s3f2nrg8nr67bi7x-nix-2.13.2";
-  aarch64-linux = "/nix/store/v8drr3x1ia6bdr8y4vl79mlz61xynrpm-nix-2.13.2";
-  x86_64-darwin = "/nix/store/1l14si31p4aw7c1gwgjy0nq55k38j9nj-nix-2.13.2";
-  aarch64-darwin = "/nix/store/6x7nr1r780fgn254zhkwhih3f3i8cr45-nix-2.13.2";
+  x86_64-linux = "/nix/store/mc43d38fibi94pp5crfwacl5gbslccd0-nix-2.13.3";
+  i686-linux = "/nix/store/09m966pj26cgd4ihlg8ihl1106j3vih8-nix-2.13.3";
+  aarch64-linux = "/nix/store/7f191d125akld27gc6jl0r13l8pl7x0h-nix-2.13.3";
+  x86_64-darwin = "/nix/store/1wn9jkvi2zqfjnjgg7lnp30r2q2y8whd-nix-2.13.3";
+  aarch64-darwin = "/nix/store/8w0v2mffa10chrf1h66cbvbpw86qmh85-nix-2.13.3";
 }
diff --git a/nixos/modules/installer/tools/tools.nix b/nixos/modules/installer/tools/tools.nix
index caf97f66ef31e..d1b16d042d86a 100644
--- a/nixos/modules/installer/tools/tools.nix
+++ b/nixos/modules/installer/tools/tools.nix
@@ -217,7 +217,7 @@ in
 
         # This value determines the NixOS release from which the default
         # settings for stateful data, like file locations and database versions
-        # on your system were taken. It‘s perfectly fine and recommended to leave
+        # on your system were taken. It’s perfectly fine and recommended to leave
         # this value at the release version of the first install of this system.
         # Before changing this value read the documentation for this option
         # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
diff --git a/nixos/modules/misc/meta.nix b/nixos/modules/misc/meta.nix
index e1d16f802cee9..95f2765aff1eb 100644
--- a/nixos/modules/misc/meta.nix
+++ b/nixos/modules/misc/meta.nix
@@ -47,7 +47,7 @@ in
       doc = mkOption {
         type = docFile;
         internal = true;
-        example = "./meta.chapter.xml";
+        example = "./meta.chapter.md";
         description = lib.mdDoc ''
           Documentation prologue for the set of options of each module.  This
           option should be defined at most once per module.
diff --git a/nixos/modules/misc/version.nix b/nixos/modules/misc/version.nix
index 30d11913c533b..447f8193855f1 100644
--- a/nixos/modules/misc/version.nix
+++ b/nixos/modules/misc/version.nix
@@ -130,7 +130,7 @@ in
         to be compatible. The effect is that NixOS will use
         defaults corresponding to the specified release (such as using
         an older version of PostgreSQL).
-        It‘s perfectly fine and recommended to leave this value at the
+        It’s perfectly fine and recommended to leave this value at the
         release version of the first install of this system.
         Changing this option will not upgrade your system. In fact it
         is meant to stay constant exactly when you upgrade your system.
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 5e520c8308cf1..de390d801478e 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -53,11 +53,13 @@
   ./hardware/cpu/intel-sgx.nix
   ./hardware/device-tree.nix
   ./hardware/digitalbitbox.nix
+  ./hardware/flipperzero.nix
   ./hardware/flirc.nix
   ./hardware/gkraken.nix
   ./hardware/gpgsmartcards.nix
   ./hardware/hackrf.nix
   ./hardware/i2c.nix
+  ./hardware/keyboard/qmk.nix
   ./hardware/keyboard/teck.nix
   ./hardware/keyboard/uhk.nix
   ./hardware/keyboard/zsa.nix
@@ -195,6 +197,7 @@
   ./programs/mdevctl.nix
   ./programs/mepo.nix
   ./programs/mininet.nix
+  ./programs/miriway.nix
   ./programs/mosh.nix
   ./programs/msmtp.nix
   ./programs/mtr.nix
@@ -214,6 +217,7 @@
   ./programs/partition-manager.nix
   ./programs/plotinus.nix
   ./programs/proxychains.nix
+  ./programs/qdmr.nix
   ./programs/qt5ct.nix
   ./programs/rog-control-center.nix
   ./programs/rust-motd.nix
@@ -221,6 +225,7 @@
   ./programs/seahorse.nix
   ./programs/sedutil.nix
   ./programs/shadow.nix
+  ./programs/sharing.nix
   ./programs/singularity.nix
   ./programs/skim.nix
   ./programs/slock.nix
@@ -311,6 +316,7 @@
   ./services/audio/snapserver.nix
   ./services/audio/spotifyd.nix
   ./services/audio/squeezelite.nix
+  ./services/audio/tts.nix
   ./services/audio/ympd.nix
   ./services/backup/automysqlbackup.nix
   ./services/backup/bacula.nix
@@ -695,6 +701,7 @@
   ./services/monitoring/arbtt.nix
   ./services/monitoring/bosun.nix
   ./services/monitoring/cadvisor.nix
+  ./services/monitoring/cockpit.nix
   ./services/monitoring/collectd.nix
   ./services/monitoring/das_watchdog.nix
   ./services/monitoring/datadog-agent.nix
@@ -856,6 +863,7 @@
   ./services/networking/i2pd.nix
   ./services/networking/icecream/daemon.nix
   ./services/networking/icecream/scheduler.nix
+  ./services/networking/imaginary.nix
   ./services/networking/inspircd.nix
   ./services/networking/iodine.nix
   ./services/networking/iperf3.nix
@@ -907,6 +915,7 @@
   ./services/networking/ndppd.nix
   ./services/networking/nebula.nix
   ./services/networking/netbird.nix
+  ./services/networking/networkd-dispatcher.nix
   ./services/networking/networkmanager.nix
   ./services/networking/nextdns.nix
   ./services/networking/nftables.nix
@@ -1043,6 +1052,8 @@
   ./services/search/hound.nix
   ./services/search/kibana.nix
   ./services/search/meilisearch.nix
+  ./services/search/opensearch.nix
+  ./services/search/qdrant.nix
   ./services/search/solr.nix
   ./services/security/aesmd.nix
   ./services/security/certmgr.nix
@@ -1158,6 +1169,7 @@
   ./services/web-apps/moodle.nix
   ./services/web-apps/netbox.nix
   ./services/web-apps/nextcloud.nix
+  ./services/web-apps/nextcloud-notify_push.nix
   ./services/web-apps/nexus.nix
   ./services/web-apps/nifi.nix
   ./services/web-apps/node-red.nix
@@ -1303,6 +1315,7 @@
   ./system/boot/systemd/logind.nix
   ./system/boot/systemd/nspawn.nix
   ./system/boot/systemd/oomd.nix
+  ./system/boot/systemd/repart.nix
   ./system/boot/systemd/shutdown.nix
   ./system/boot/systemd/tmpfiles.nix
   ./system/boot/systemd/user.nix
@@ -1363,6 +1376,7 @@
   ./virtualisation/lxc.nix
   ./virtualisation/lxcfs.nix
   ./virtualisation/lxd.nix
+  ./virtualisation/multipass.nix
   ./virtualisation/nixos-containers.nix
   ./virtualisation/oci-containers.nix
   ./virtualisation/openstack-options.nix
diff --git a/nixos/modules/programs/digitalbitbox/default.nix b/nixos/modules/programs/digitalbitbox/default.nix
index 054110fe5df2e..5ee6cdafe63a0 100644
--- a/nixos/modules/programs/digitalbitbox/default.nix
+++ b/nixos/modules/programs/digitalbitbox/default.nix
@@ -33,7 +33,7 @@ in
   };
 
   meta = {
-    doc = ./default.xml;
+    doc = ./default.md;
     maintainers = with lib.maintainers; [ vidbina ];
   };
 }
diff --git a/nixos/modules/programs/digitalbitbox/default.xml b/nixos/modules/programs/digitalbitbox/default.xml
deleted file mode 100644
index ee892523223c8..0000000000000
--- a/nixos/modules/programs/digitalbitbox/default.xml
+++ /dev/null
@@ -1,70 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-programs-digitalbitbox">
-  <title>Digital Bitbox</title>
-  <para>
-    Digital Bitbox is a hardware wallet and second-factor authenticator.
-  </para>
-  <para>
-    The <literal>digitalbitbox</literal> programs module may be
-    installed by setting <literal>programs.digitalbitbox</literal> to
-    <literal>true</literal> in a manner similar to
-  </para>
-  <programlisting>
-programs.digitalbitbox.enable = true;
-</programlisting>
-  <para>
-    and bundles the <literal>digitalbitbox</literal> package (see
-    <xref linkend="sec-digitalbitbox-package" />), which contains the
-    <literal>dbb-app</literal> and <literal>dbb-cli</literal> binaries,
-    along with the hardware module (see
-    <xref linkend="sec-digitalbitbox-hardware-module" />) which sets up
-    the necessary udev rules to access the device.
-  </para>
-  <para>
-    Enabling the digitalbitbox module is pretty much the easiest way to
-    get a Digital Bitbox device working on your system.
-  </para>
-  <para>
-    For more information, see
-    <link xlink:href="https://digitalbitbox.com/start_linux">https://digitalbitbox.com/start_linux</link>.
-  </para>
-  <section xml:id="sec-digitalbitbox-package">
-    <title>Package</title>
-    <para>
-      The binaries, <literal>dbb-app</literal> (a GUI tool) and
-      <literal>dbb-cli</literal> (a CLI tool), are available through the
-      <literal>digitalbitbox</literal> package which could be installed
-      as follows:
-    </para>
-    <programlisting>
-environment.systemPackages = [
-  pkgs.digitalbitbox
-];
-</programlisting>
-  </section>
-  <section xml:id="sec-digitalbitbox-hardware-module">
-    <title>Hardware</title>
-    <para>
-      The digitalbitbox hardware package enables the udev rules for
-      Digital Bitbox devices and may be installed as follows:
-    </para>
-    <programlisting>
-hardware.digitalbitbox.enable = true;
-</programlisting>
-    <para>
-      In order to alter the udev rules, one may provide different values
-      for the <literal>udevRule51</literal> and
-      <literal>udevRule52</literal> attributes by means of overriding as
-      follows:
-    </para>
-    <programlisting>
-programs.digitalbitbox = {
-  enable = true;
-  package = pkgs.digitalbitbox.override {
-    udevRule51 = &quot;something else&quot;;
-  };
-};
-</programlisting>
-  </section>
-</chapter>
diff --git a/nixos/modules/programs/flashrom.nix b/nixos/modules/programs/flashrom.nix
index 294b208a37208..9f8faff14e472 100644
--- a/nixos/modules/programs/flashrom.nix
+++ b/nixos/modules/programs/flashrom.nix
@@ -22,6 +22,5 @@ in
   config = mkIf cfg.enable {
     services.udev.packages = [ cfg.package ];
     environment.systemPackages = [ cfg.package ];
-    users.groups.flashrom = { };
   };
 }
diff --git a/nixos/modules/programs/k3b.nix b/nixos/modules/programs/k3b.nix
index cdaed3cf70fba..5d19e4f1cc4fb 100644
--- a/nixos/modules/programs/k3b.nix
+++ b/nixos/modules/programs/k3b.nix
@@ -28,7 +28,7 @@ with lib;
       k3b
       dvdplusrwtools
       cdrdao
-      cdrkit
+      cdrtools
     ];
 
     security.wrappers = {
@@ -44,7 +44,7 @@ with lib;
         owner = "root";
         group = "cdrom";
         permissions = "u+wrx,g+x";
-        source = "${pkgs.cdrkit}/bin/cdrecord";
+        source = "${pkgs.cdrtools}/bin/cdrecord";
       };
     };
 
diff --git a/nixos/modules/programs/miriway.nix b/nixos/modules/programs/miriway.nix
new file mode 100644
index 0000000000000..52b5f84762220
--- /dev/null
+++ b/nixos/modules/programs/miriway.nix
@@ -0,0 +1,60 @@
+{ config, pkgs, lib, ... }:
+
+let
+  cfg = config.programs.miriway;
+in {
+  options.programs.miriway = {
+    enable = lib.mkEnableOption (lib.mdDoc ''
+      Miriway, a Mir based Wayland compositor. You can manually launch Miriway by
+      executing "exec miriway" on a TTY, or launch it from a display manager. Copy
+      /etc/xdg/xdg-miriway/miriway-shell.config to ~/.config/miriway-shell.config
+      to modify the default configuration. See <https://github.com/Miriway/Miriway>,
+      and "miriway --help" for more information'');
+
+    config = lib.mkOption {
+      type = lib.types.lines;
+      default = ''
+        x11-window-title=Miriway (Mir-on-X)
+        idle-timeout=600
+        ctrl-alt=t:miriway-terminal # Default "terminal emulator finder"
+
+        shell-component=dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY
+      '';
+      example = ''
+        idle-timeout=300
+        ctrl-alt=t:weston-terminal
+        add-wayland-extensions=all
+
+        shell-components=dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY
+
+        shell-component=waybar
+        shell-component=wbg Pictures/wallpaper
+
+        shell-meta=a:synapse
+      '';
+      description = lib.mdDoc ''
+        Miriway's config. This will be installed system-wide.
+        The default will install the miriway package's barebones example config.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment = {
+      systemPackages = [ pkgs.miriway ];
+      etc = {
+        "xdg/xdg-miriway/miriway-shell.config".text = cfg.config;
+      };
+    };
+
+    hardware.opengl.enable = lib.mkDefault true;
+    fonts.enableDefaultFonts = lib.mkDefault true;
+    programs.dconf.enable = lib.mkDefault true;
+    programs.xwayland.enable = lib.mkDefault true;
+
+    # To make the Miriway session available if a display manager like SDDM is enabled:
+    services.xserver.displayManager.sessionPackages = [ pkgs.miriway ];
+  };
+
+  meta.maintainers = with lib.maintainers; [ OPNA2608 ];
+}
diff --git a/nixos/modules/programs/plotinus.nix b/nixos/modules/programs/plotinus.nix
index a011bb862aeaf..c2b6884d6490d 100644
--- a/nixos/modules/programs/plotinus.nix
+++ b/nixos/modules/programs/plotinus.nix
@@ -8,7 +8,7 @@ in
 {
   meta = {
     maintainers = pkgs.plotinus.meta.maintainers;
-    doc = ./plotinus.xml;
+    doc = ./plotinus.md;
   };
 
   ###### interface
diff --git a/nixos/modules/programs/plotinus.xml b/nixos/modules/programs/plotinus.xml
deleted file mode 100644
index 2d4db0285148b..0000000000000
--- a/nixos/modules/programs/plotinus.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-program-plotinus">
-  <title>Plotinus</title>
-  <para>
-    <emphasis>Source:</emphasis>
-    <filename>modules/programs/plotinus.nix</filename>
-  </para>
-  <para>
-    <emphasis>Upstream documentation:</emphasis>
-    <link xlink:href="https://github.com/p-e-w/plotinus">https://github.com/p-e-w/plotinus</link>
-  </para>
-  <para>
-    Plotinus is a searchable command palette in every modern GTK
-    application.
-  </para>
-  <para>
-    When in a GTK 3 application and Plotinus is enabled, you can press
-    <literal>Ctrl+Shift+P</literal> to open the command palette. The
-    command palette provides a searchable list of of all menu items in
-    the application.
-  </para>
-  <para>
-    To enable Plotinus, add the following to your
-    <filename>configuration.nix</filename>:
-  </para>
-  <programlisting>
-programs.plotinus.enable = true;
-</programlisting>
-</chapter>
diff --git a/nixos/modules/programs/proxychains.nix b/nixos/modules/programs/proxychains.nix
index 0771f03c77d30..a52783aa66982 100644
--- a/nixos/modules/programs/proxychains.nix
+++ b/nixos/modules/programs/proxychains.nix
@@ -86,7 +86,7 @@ in {
         description = lib.mdDoc "Proxy DNS requests - no leak for DNS data.";
       };
 
-      quietMode = mkEnableOption (lib.mdDoc "Quiet mode (no output from the library).");
+      quietMode = mkEnableOption (lib.mdDoc "Quiet mode (no output from the library)");
 
       remoteDNSSubnet = mkOption {
         type = types.enum [ 10 127 224 ];
diff --git a/nixos/modules/programs/qdmr.nix b/nixos/modules/programs/qdmr.nix
new file mode 100644
index 0000000000000..1bb81317bda8d
--- /dev/null
+++ b/nixos/modules/programs/qdmr.nix
@@ -0,0 +1,25 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  cfg = config.programs.qdmr;
+in {
+  meta.maintainers = [ lib.maintainers.janik ];
+
+  options = {
+    programs.qdmr = {
+      enable = lib.mkEnableOption (lib.mdDoc "QDMR - a GUI application and command line tool for programming DMR radios");
+      package = lib.mkPackageOptionMD pkgs "qdmr" { };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+    services.udev.packages = [ cfg.package ];
+    users.groups.dialout = {};
+  };
+}
diff --git a/nixos/modules/programs/sharing.nix b/nixos/modules/programs/sharing.nix
new file mode 100644
index 0000000000000..9ab51859dc51d
--- /dev/null
+++ b/nixos/modules/programs/sharing.nix
@@ -0,0 +1,19 @@
+{ config, pkgs, lib, ... }:
+with lib;
+{
+  options.programs.sharing = {
+    enable = mkEnableOption (lib.mdDoc ''
+      sharing, a CLI tool for sharing files.
+
+      Note that it will opens the 7478 port for TCP in the firewall, which is needed for it to function properly
+    '');
+  };
+  config =
+    let
+      cfg = config.programs.sharing;
+    in
+      mkIf cfg.enable {
+        environment.systemPackages = [ pkgs.sharing ];
+        networking.firewall.allowedTCPPorts = [ 7478 ];
+      };
+}
diff --git a/nixos/modules/programs/singularity.nix b/nixos/modules/programs/singularity.nix
index 9648d0c278740..4884e5bdf2ddd 100644
--- a/nixos/modules/programs/singularity.nix
+++ b/nixos/modules/programs/singularity.nix
@@ -3,32 +3,90 @@
 with lib;
 let
   cfg = config.programs.singularity;
-  singularity = pkgs.singularity.overrideAttrs (attrs : {
-    installPhase = attrs.installPhase + ''
-      mv $out/libexec/singularity/bin/starter-suid $out/libexec/singularity/bin/starter-suid.orig
-      ln -s /run/wrappers/bin/singularity-suid $out/libexec/singularity/bin/starter-suid
-    '';
-  });
-in {
+in
+{
+
   options.programs.singularity = {
-    enable = mkEnableOption (lib.mdDoc "Singularity");
+    enable = mkEnableOption (mdDoc "singularity") // {
+      description = mdDoc ''
+        Whether to install Singularity/Apptainer with system-level overriding such as SUID support.
+      '';
+    };
+    package = mkOption {
+      type = types.package;
+      default = pkgs.singularity;
+      defaultText = literalExpression "pkgs.singularity";
+      example = literalExpression "pkgs.apptainer";
+      description = mdDoc ''
+        Singularity/Apptainer package to override and install.
+      '';
+    };
+    packageOverriden = mkOption {
+      type = types.nullOr types.package;
+      default = null;
+      description = mdDoc ''
+        This option provides access to the overriden result of `programs.singularity.package`.
+
+        For example, the following configuration makes all the Nixpkgs packages use the overriden `singularity`:
+        ```Nix
+        { config, lib, pkgs, ... }:
+        {
+          nixpkgs.overlays = [
+            (final: prev: {
+              _singularity-orig = prev.singularity;
+              singularity = config.programs.singularity.packageOverriden;
+            })
+          ];
+          programs.singularity.enable = true;
+          programs.singularity.package = pkgs._singularity-orig;
+        }
+        ```
+
+        Use `lib.mkForce` to forcefully specify the overriden package.
+      '';
+    };
+    enableFakeroot = mkOption {
+      type = types.bool;
+      default = true;
+      example = false;
+      description = mdDoc ''
+        Whether to enable the `--fakeroot` support of Singularity/Apptainer.
+      '';
+    };
+    enableSuid = mkOption {
+      type = types.bool;
+      default = true;
+      example = false;
+      description = mdDoc ''
+        Whether to enable the SUID support of Singularity/Apptainer.
+      '';
+    };
   };
 
   config = mkIf cfg.enable {
-      environment.systemPackages = [ singularity ];
-      security.wrappers.singularity-suid =
-      { setuid = true;
-        owner = "root";
-        group = "root";
-        source = "${singularity}/libexec/singularity/bin/starter-suid.orig";
-      };
-      systemd.tmpfiles.rules = [
-        "d /var/singularity/mnt/session 0770 root root -"
-        "d /var/singularity/mnt/final 0770 root root -"
-        "d /var/singularity/mnt/overlay 0770 root root -"
-        "d /var/singularity/mnt/container 0770 root root -"
-        "d /var/singularity/mnt/source 0770 root root -"
-      ];
+    programs.singularity.packageOverriden = (cfg.package.override (
+      optionalAttrs cfg.enableFakeroot {
+        newuidmapPath = "/run/wrappers/bin/newuidmap";
+        newgidmapPath = "/run/wrappers/bin/newgidmap";
+      } // optionalAttrs cfg.enableSuid {
+        enableSuid = true;
+        starterSuidPath = "/run/wrappers/bin/${cfg.package.projectName}-suid";
+      }
+    ));
+    environment.systemPackages = [ cfg.packageOverriden ];
+    security.wrappers."${cfg.packageOverriden.projectName}-suid" = mkIf cfg.enableSuid {
+      setuid = true;
+      owner = "root";
+      group = "root";
+      source = "${cfg.packageOverriden}/libexec/${cfg.packageOverriden.projectName}/bin/starter-suid.orig";
+    };
+    systemd.tmpfiles.rules = [
+      "d /var/lib/${cfg.packageOverriden.projectName}/mnt/session 0770 root root -"
+      "d /var/lib/${cfg.packageOverriden.projectName}/mnt/final 0770 root root -"
+      "d /var/lib/${cfg.packageOverriden.projectName}/mnt/overlay 0770 root root -"
+      "d /var/lib/${cfg.packageOverriden.projectName}/mnt/container 0770 root root -"
+      "d /var/lib/${cfg.packageOverriden.projectName}/mnt/source 0770 root root -"
+    ];
   };
 
 }
diff --git a/nixos/modules/programs/ssh.nix b/nixos/modules/programs/ssh.nix
index 36b724e04bde6..3b8da78e2af5e 100644
--- a/nixos/modules/programs/ssh.nix
+++ b/nixos/modules/programs/ssh.nix
@@ -282,7 +282,7 @@ in
   config = {
 
     programs.ssh.setXAuthLocation =
-      mkDefault (config.services.xserver.enable || config.programs.ssh.forwardX11 || config.services.openssh.forwardX11);
+      mkDefault (config.services.xserver.enable || config.programs.ssh.forwardX11 || config.services.openssh.settings.X11Forwarding);
 
     assertions =
       [ { assertion = cfg.forwardX11 -> cfg.setXAuthLocation;
diff --git a/nixos/modules/programs/sway.nix b/nixos/modules/programs/sway.nix
index b0a766dd055f5..3b2e69bd37c3e 100644
--- a/nixos/modules/programs/sway.nix
+++ b/nixos/modules/programs/sway.nix
@@ -26,7 +26,7 @@ let
     };
   };
 
-  swayPackage = pkgs.sway.override {
+  defaultSwayPackage = pkgs.sway.override {
     extraSessionCommands = cfg.extraSessionCommands;
     extraOptions = cfg.extraOptions;
     withBaseWrapper = cfg.wrapperFeatures.base;
@@ -42,6 +42,19 @@ in {
       <https://github.com/swaywm/sway/wiki> and
       "man 5 sway" for more information'');
 
+    package = mkOption {
+      type = with types; nullOr package;
+      default = defaultSwayPackage;
+      defaultText = literalExpression "pkgs.sway";
+      description = lib.mdDoc ''
+        Sway package to use. Will override the options
+        'wrapperFeatures', 'extraSessionCommands', and 'extraOptions'.
+        Set to <code>null</code> to not add any Sway package to your
+        path. This should be done if you want to use the Home Manager Sway
+        module to install Sway.
+      '';
+    };
+
     wrapperFeatures = mkOption {
       type = wrapperOptions;
       default = { };
@@ -121,16 +134,17 @@ in {
       }
     ];
     environment = {
-      systemPackages = [ swayPackage ] ++ cfg.extraPackages;
+      systemPackages = optional (cfg.package != null) cfg.package ++ cfg.extraPackages;
       # Needed for the default wallpaper:
-      pathsToLink = [ "/share/backgrounds/sway" ];
+      pathsToLink = optionals (cfg.package != null) [ "/share/backgrounds/sway" ];
       etc = {
-        "sway/config".source = mkOptionDefault "${swayPackage}/etc/sway/config";
         "sway/config.d/nixos.conf".source = pkgs.writeText "nixos.conf" ''
           # Import the most important environment variables into the D-Bus and systemd
           # user environments (e.g. required for screen sharing and Pinentry prompts):
           exec dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY SWAYSOCK XDG_CURRENT_DESKTOP
         '';
+      } // optionalAttrs (cfg.package != null) {
+        "sway/config".source = mkOptionDefault "${cfg.package}/etc/sway/config";
       };
     };
     security.polkit.enable = true;
@@ -139,7 +153,7 @@ in {
     fonts.enableDefaultFonts = mkDefault true;
     programs.dconf.enable = mkDefault true;
     # To make a Sway session available if a display manager like SDDM is enabled:
-    services.xserver.displayManager.sessionPackages = [ swayPackage ];
+    services.xserver.displayManager.sessionPackages = optionals (cfg.package != null) [ cfg.package ];
     programs.xwayland.enable = mkDefault true;
     # For screen sharing (this option only has an effect with xdg.portal.enable):
     xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-wlr ];
diff --git a/nixos/modules/programs/zsh/oh-my-zsh.nix b/nixos/modules/programs/zsh/oh-my-zsh.nix
index 41ea31b0f122c..83eee1c88b3ca 100644
--- a/nixos/modules/programs/zsh/oh-my-zsh.nix
+++ b/nixos/modules/programs/zsh/oh-my-zsh.nix
@@ -142,5 +142,5 @@ in
 
     };
 
-    meta.doc = ./oh-my-zsh.xml;
+    meta.doc = ./oh-my-zsh.md;
   }
diff --git a/nixos/modules/programs/zsh/oh-my-zsh.xml b/nixos/modules/programs/zsh/oh-my-zsh.xml
deleted file mode 100644
index 2a2bba96b859c..0000000000000
--- a/nixos/modules/programs/zsh/oh-my-zsh.xml
+++ /dev/null
@@ -1,154 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-programs-zsh-ohmyzsh">
-  <title>Oh my ZSH</title>
-  <para>
-    <link xlink:href="https://ohmyz.sh/"><literal>oh-my-zsh</literal></link>
-    is a framework to manage your
-    <link xlink:href="https://www.zsh.org/">ZSH</link> configuration
-    including completion scripts for several CLI tools or custom prompt
-    themes.
-  </para>
-  <section xml:id="module-programs-oh-my-zsh-usage">
-    <title>Basic usage</title>
-    <para>
-      The module uses the <literal>oh-my-zsh</literal> package with all
-      available features. The initial setup using Nix expressions is
-      fairly similar to the configuration format of
-      <literal>oh-my-zsh</literal>.
-    </para>
-    <programlisting>
-{
-  programs.zsh.ohMyZsh = {
-    enable = true;
-    plugins = [ &quot;git&quot; &quot;python&quot; &quot;man&quot; ];
-    theme = &quot;agnoster&quot;;
-  };
-}
-</programlisting>
-    <para>
-      For a detailed explanation of these arguments please refer to the
-      <link xlink:href="https://github.com/robbyrussell/oh-my-zsh/wiki"><literal>oh-my-zsh</literal>
-      docs</link>.
-    </para>
-    <para>
-      The expression generates the needed configuration and writes it
-      into your <literal>/etc/zshrc</literal>.
-    </para>
-  </section>
-  <section xml:id="module-programs-oh-my-zsh-additions">
-    <title>Custom additions</title>
-    <para>
-      Sometimes third-party or custom scripts such as a modified theme
-      may be needed. <literal>oh-my-zsh</literal> provides the
-      <link xlink:href="https://github.com/robbyrussell/oh-my-zsh/wiki/Customization#overriding-internals"><literal>ZSH_CUSTOM</literal></link>
-      environment variable for this which points to a directory with
-      additional scripts.
-    </para>
-    <para>
-      The module can do this as well:
-    </para>
-    <programlisting>
-{
-  programs.zsh.ohMyZsh.custom = &quot;~/path/to/custom/scripts&quot;;
-}
-</programlisting>
-  </section>
-  <section xml:id="module-programs-oh-my-zsh-environments">
-    <title>Custom environments</title>
-    <para>
-      There are several extensions for <literal>oh-my-zsh</literal>
-      packaged in <literal>nixpkgs</literal>. One of them is
-      <link xlink:href="https://github.com/spwhitt/nix-zsh-completions">nix-zsh-completions</link>
-      which bundles completion scripts and a plugin for
-      <literal>oh-my-zsh</literal>.
-    </para>
-    <para>
-      Rather than using a single mutable path for
-      <literal>ZSH_CUSTOM</literal>, it’s also possible to generate this
-      path from a list of Nix packages:
-    </para>
-    <programlisting>
-{ pkgs, ... }:
-{
-  programs.zsh.ohMyZsh.customPkgs = [
-    pkgs.nix-zsh-completions
-    # and even more...
-  ];
-}
-</programlisting>
-    <para>
-      Internally a single store path will be created using
-      <literal>buildEnv</literal>. Please refer to the docs of
-      <link xlink:href="https://nixos.org/nixpkgs/manual/#sec-building-environment"><literal>buildEnv</literal></link>
-      for further reference.
-    </para>
-    <para>
-      <emphasis>Please keep in mind that this is not compatible with
-      <literal>programs.zsh.ohMyZsh.custom</literal> as it requires an
-      immutable store path while <literal>custom</literal> shall remain
-      mutable! An evaluation failure will be thrown if both
-      <literal>custom</literal> and <literal>customPkgs</literal> are
-      set.</emphasis>
-    </para>
-  </section>
-  <section xml:id="module-programs-oh-my-zsh-packaging-customizations">
-    <title>Package your own customizations</title>
-    <para>
-      If third-party customizations (e.g. new themes) are supposed to be
-      added to <literal>oh-my-zsh</literal> there are several pitfalls
-      to keep in mind:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          To comply with the default structure of <literal>ZSH</literal>
-          the entire output needs to be written to
-          <literal>$out/share/zsh.</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Completion scripts are supposed to be stored at
-          <literal>$out/share/zsh/site-functions</literal>. This
-          directory is part of the
-          <link xlink:href="http://zsh.sourceforge.net/Doc/Release/Functions.html"><literal>fpath</literal></link>
-          and the package should be compatible with pure
-          <literal>ZSH</literal> setups. The module will automatically
-          link the contents of <literal>site-functions</literal> to
-          completions directory in the proper store path.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>plugins</literal> directory needs the structure
-          <literal>pluginname/pluginname.plugin.zsh</literal> as
-          structured in the
-          <link xlink:href="https://github.com/robbyrussell/oh-my-zsh/tree/91b771914bc7c43dd7c7a43b586c5de2c225ceb7/plugins">upstream
-          repo.</link>
-        </para>
-      </listitem>
-    </itemizedlist>
-    <para>
-      A derivation for <literal>oh-my-zsh</literal> may look like this:
-    </para>
-    <programlisting>
-{ stdenv, fetchFromGitHub }:
-
-stdenv.mkDerivation rec {
-  name = &quot;exemplary-zsh-customization-${version}&quot;;
-  version = &quot;1.0.0&quot;;
-  src = fetchFromGitHub {
-    # path to the upstream repository
-  };
-
-  dontBuild = true;
-  installPhase = ''
-    mkdir -p $out/share/zsh/site-functions
-    cp {themes,plugins} $out/share/zsh
-    cp completions $out/share/zsh/site-functions
-  '';
-}
-</programlisting>
-  </section>
-</chapter>
diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix
index b92561ed48594..d8a18cfcc6dc1 100644
--- a/nixos/modules/rename.nix
+++ b/nixos/modules/rename.nix
@@ -50,7 +50,6 @@ with lib;
     (mkRemovedOptionModule [ "services" "chronos" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "couchpotato" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "dd-agent" ] "dd-agent was removed from nixpkgs in favor of the newer datadog-agent.")
-    (mkRemovedOptionModule [ "services" "deepin" ] "The corresponding packages were removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "dnscrypt-proxy" ] "Use services.dnscrypt-proxy2 instead")
     (mkRemovedOptionModule [ "services" "firefox" "syncserver" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "flashpolicyd" ] "The flashpolicyd module has been removed. Adobe Flash Player is deprecated.")
diff --git a/nixos/modules/security/acme/default.nix b/nixos/modules/security/acme/default.nix
index eb4f11f7dcdec..ef0636258994c 100644
--- a/nixos/modules/security/acme/default.nix
+++ b/nixos/modules/security/acme/default.nix
@@ -916,6 +916,6 @@ in {
 
   meta = {
     maintainers = lib.teams.acme.members;
-    doc = ./default.xml;
+    doc = ./default.md;
   };
 }
diff --git a/nixos/modules/security/acme/default.xml b/nixos/modules/security/acme/default.xml
deleted file mode 100644
index e80ce3b6a4943..0000000000000
--- a/nixos/modules/security/acme/default.xml
+++ /dev/null
@@ -1,395 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-security-acme">
-  <title>SSL/TLS Certificates with ACME</title>
-  <para>
-    NixOS supports automatic domain validation &amp; certificate
-    retrieval and renewal using the ACME protocol. Any provider can be
-    used, but by default NixOS uses Let’s Encrypt. The alternative ACME
-    client
-    <link xlink:href="https://go-acme.github.io/lego/">lego</link> is
-    used under the hood.
-  </para>
-  <para>
-    Automatic cert validation and configuration for Apache and Nginx
-    virtual hosts is included in NixOS, however if you would like to
-    generate a wildcard cert or you are not using a web server you will
-    have to configure DNS based validation.
-  </para>
-  <section xml:id="module-security-acme-prerequisites">
-    <title>Prerequisites</title>
-    <para>
-      To use the ACME module, you must accept the provider’s terms of
-      service by setting
-      <xref linkend="opt-security.acme.acceptTerms" /> to
-      <literal>true</literal>. The Let’s Encrypt ToS can be found
-      <link xlink:href="https://letsencrypt.org/repository/">here</link>.
-    </para>
-    <para>
-      You must also set an email address to be used when creating
-      accounts with Let’s Encrypt. You can set this for all certs with
-      <xref linkend="opt-security.acme.defaults.email" /> and/or on a
-      per-cert basis with
-      <xref linkend="opt-security.acme.certs._name_.email" />. This
-      address is only used for registration and renewal reminders, and
-      cannot be used to administer the certificates in any way.
-    </para>
-    <para>
-      Alternatively, you can use a different ACME server by changing the
-      <xref linkend="opt-security.acme.defaults.server" /> option to a
-      provider of your choosing, or just change the server for one cert
-      with <xref linkend="opt-security.acme.certs._name_.server" />.
-    </para>
-    <para>
-      You will need an HTTP server or DNS server for verification. For
-      HTTP, the server must have a webroot defined that can serve
-      <filename>.well-known/acme-challenge</filename>. This directory
-      must be writeable by the user that will run the ACME client. For
-      DNS, you must set up credentials with your provider/server for use
-      with lego.
-    </para>
-  </section>
-  <section xml:id="module-security-acme-nginx">
-    <title>Using ACME certificates in Nginx</title>
-    <para>
-      NixOS supports fetching ACME certificates for you by setting
-      <literal>enableACME = true;</literal> in a virtualHost config. We
-      first create self-signed placeholder certificates in place of the
-      real ACME certs. The placeholder certs are overwritten when the
-      ACME certs arrive. For <literal>foo.example.com</literal> the
-      config would look like this:
-    </para>
-    <programlisting>
-security.acme.acceptTerms = true;
-security.acme.defaults.email = &quot;admin+acme@example.com&quot;;
-services.nginx = {
-  enable = true;
-  virtualHosts = {
-    &quot;foo.example.com&quot; = {
-      forceSSL = true;
-      enableACME = true;
-      # All serverAliases will be added as extra domain names on the certificate.
-      serverAliases = [ &quot;bar.example.com&quot; ];
-      locations.&quot;/&quot; = {
-        root = &quot;/var/www&quot;;
-      };
-    };
-
-    # We can also add a different vhost and reuse the same certificate
-    # but we have to append extraDomainNames manually beforehand:
-    # security.acme.certs.&quot;foo.example.com&quot;.extraDomainNames = [ &quot;baz.example.com&quot; ];
-    &quot;baz.example.com&quot; = {
-      forceSSL = true;
-      useACMEHost = &quot;foo.example.com&quot;;
-      locations.&quot;/&quot; = {
-        root = &quot;/var/www&quot;;
-      };
-    };
-  };
-}
-</programlisting>
-  </section>
-  <section xml:id="module-security-acme-httpd">
-    <title>Using ACME certificates in Apache/httpd</title>
-    <para>
-      Using ACME certificates with Apache virtual hosts is identical to
-      using them with Nginx. The attribute names are all the same, just
-      replace <quote>nginx</quote> with <quote>httpd</quote> where
-      appropriate.
-    </para>
-  </section>
-  <section xml:id="module-security-acme-configuring">
-    <title>Manual configuration of HTTP-01 validation</title>
-    <para>
-      First off you will need to set up a virtual host to serve the
-      challenges. This example uses a vhost called
-      <literal>certs.example.com</literal>, with the intent that you
-      will generate certs for all your vhosts and redirect everyone to
-      HTTPS.
-    </para>
-    <programlisting>
-security.acme.acceptTerms = true;
-security.acme.defaults.email = &quot;admin+acme@example.com&quot;;
-
-# /var/lib/acme/.challenges must be writable by the ACME user
-# and readable by the Nginx user. The easiest way to achieve
-# this is to add the Nginx user to the ACME group.
-users.users.nginx.extraGroups = [ &quot;acme&quot; ];
-
-services.nginx = {
-  enable = true;
-  virtualHosts = {
-    &quot;acmechallenge.example.com&quot; = {
-      # Catchall vhost, will redirect users to HTTPS for all vhosts
-      serverAliases = [ &quot;*.example.com&quot; ];
-      locations.&quot;/.well-known/acme-challenge&quot; = {
-        root = &quot;/var/lib/acme/.challenges&quot;;
-      };
-      locations.&quot;/&quot; = {
-        return = &quot;301 https://$host$request_uri&quot;;
-      };
-    };
-  };
-}
-# Alternative config for Apache
-users.users.wwwrun.extraGroups = [ &quot;acme&quot; ];
-services.httpd = {
-  enable = true;
-  virtualHosts = {
-    &quot;acmechallenge.example.com&quot; = {
-      # Catchall vhost, will redirect users to HTTPS for all vhosts
-      serverAliases = [ &quot;*.example.com&quot; ];
-      # /var/lib/acme/.challenges must be writable by the ACME user and readable by the Apache user.
-      # By default, this is the case.
-      documentRoot = &quot;/var/lib/acme/.challenges&quot;;
-      extraConfig = ''
-        RewriteEngine On
-        RewriteCond %{HTTPS} off
-        RewriteCond %{REQUEST_URI} !^/\.well-known/acme-challenge [NC]
-        RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301]
-      '';
-    };
-  };
-}
-</programlisting>
-    <para>
-      Now you need to configure ACME to generate a certificate.
-    </para>
-    <programlisting>
-security.acme.certs.&quot;foo.example.com&quot; = {
-  webroot = &quot;/var/lib/acme/.challenges&quot;;
-  email = &quot;foo@example.com&quot;;
-  # Ensure that the web server you use can read the generated certs
-  # Take a look at the group option for the web server you choose.
-  group = &quot;nginx&quot;;
-  # Since we have a wildcard vhost to handle port 80,
-  # we can generate certs for anything!
-  # Just make sure your DNS resolves them.
-  extraDomainNames = [ &quot;mail.example.com&quot; ];
-};
-</programlisting>
-    <para>
-      The private key <filename>key.pem</filename> and certificate
-      <filename>fullchain.pem</filename> will be put into
-      <filename>/var/lib/acme/foo.example.com</filename>.
-    </para>
-    <para>
-      Refer to <xref linkend="ch-options" /> for all available
-      configuration options for the
-      <link linkend="opt-security.acme.certs">security.acme</link>
-      module.
-    </para>
-  </section>
-  <section xml:id="module-security-acme-config-dns">
-    <title>Configuring ACME for DNS validation</title>
-    <para>
-      This is useful if you want to generate a wildcard certificate,
-      since ACME servers will only hand out wildcard certs over DNS
-      validation. There are a number of supported DNS providers and
-      servers you can utilise, see the
-      <link xlink:href="https://go-acme.github.io/lego/dns/">lego
-      docs</link> for provider/server specific configuration values. For
-      the sake of these docs, we will provide a fully self-hosted
-      example using bind.
-    </para>
-    <programlisting>
-services.bind = {
-  enable = true;
-  extraConfig = ''
-    include &quot;/var/lib/secrets/dnskeys.conf&quot;;
-  '';
-  zones = [
-    rec {
-      name = &quot;example.com&quot;;
-      file = &quot;/var/db/bind/${name}&quot;;
-      master = true;
-      extraConfig = &quot;allow-update { key rfc2136key.example.com.; };&quot;;
-    }
-  ];
-}
-
-# Now we can configure ACME
-security.acme.acceptTerms = true;
-security.acme.defaults.email = &quot;admin+acme@example.com&quot;;
-security.acme.certs.&quot;example.com&quot; = {
-  domain = &quot;*.example.com&quot;;
-  dnsProvider = &quot;rfc2136&quot;;
-  credentialsFile = &quot;/var/lib/secrets/certs.secret&quot;;
-  # We don't need to wait for propagation since this is a local DNS server
-  dnsPropagationCheck = false;
-};
-</programlisting>
-    <para>
-      The <filename>dnskeys.conf</filename> and
-      <filename>certs.secret</filename> must be kept secure and thus you
-      should not keep their contents in your Nix config. Instead,
-      generate them one time with a systemd service:
-    </para>
-    <programlisting>
-systemd.services.dns-rfc2136-conf = {
-  requiredBy = [&quot;acme-example.com.service&quot; &quot;bind.service&quot;];
-  before = [&quot;acme-example.com.service&quot; &quot;bind.service&quot;];
-  unitConfig = {
-    ConditionPathExists = &quot;!/var/lib/secrets/dnskeys.conf&quot;;
-  };
-  serviceConfig = {
-    Type = &quot;oneshot&quot;;
-    UMask = 0077;
-  };
-  path = [ pkgs.bind ];
-  script = ''
-    mkdir -p /var/lib/secrets
-    chmod 755 /var/lib/secrets
-    tsig-keygen rfc2136key.example.com &gt; /var/lib/secrets/dnskeys.conf
-    chown named:root /var/lib/secrets/dnskeys.conf
-    chmod 400 /var/lib/secrets/dnskeys.conf
-
-    # extract secret value from the dnskeys.conf
-    while read x y; do if [ &quot;$x&quot; = &quot;secret&quot; ]; then secret=&quot;''${y:1:''${#y}-3}&quot;; fi; done &lt; /var/lib/secrets/dnskeys.conf
-
-    cat &gt; /var/lib/secrets/certs.secret &lt;&lt; EOF
-    RFC2136_NAMESERVER='127.0.0.1:53'
-    RFC2136_TSIG_ALGORITHM='hmac-sha256.'
-    RFC2136_TSIG_KEY='rfc2136key.example.com'
-    RFC2136_TSIG_SECRET='$secret'
-    EOF
-    chmod 400 /var/lib/secrets/certs.secret
-  '';
-};
-</programlisting>
-    <para>
-      Now you’re all set to generate certs! You should monitor the first
-      invocation by running
-      <literal>systemctl start acme-example.com.service &amp; journalctl -fu acme-example.com.service</literal>
-      and watching its log output.
-    </para>
-  </section>
-  <section xml:id="module-security-acme-config-dns-with-vhosts">
-    <title>Using DNS validation with web server virtual hosts</title>
-    <para>
-      It is possible to use DNS-01 validation with all certificates,
-      including those automatically configured via the Nginx/Apache
-      <link linkend="opt-services.nginx.virtualHosts._name_.enableACME"><literal>enableACME</literal></link>
-      option. This configuration pattern is fully supported and part of
-      the module’s test suite for Nginx + Apache.
-    </para>
-    <para>
-      You must follow the guide above on configuring DNS-01 validation
-      first, however instead of setting the options for one certificate
-      (e.g.
-      <xref linkend="opt-security.acme.certs._name_.dnsProvider" />) you
-      will set them as defaults (e.g.
-      <xref linkend="opt-security.acme.defaults.dnsProvider" />).
-    </para>
-    <programlisting>
-# Configure ACME appropriately
-security.acme.acceptTerms = true;
-security.acme.defaults.email = &quot;admin+acme@example.com&quot;;
-security.acme.defaults = {
-  dnsProvider = &quot;rfc2136&quot;;
-  credentialsFile = &quot;/var/lib/secrets/certs.secret&quot;;
-  # We don't need to wait for propagation since this is a local DNS server
-  dnsPropagationCheck = false;
-};
-
-# For each virtual host you would like to use DNS-01 validation with,
-# set acmeRoot = null
-services.nginx = {
-  enable = true;
-  virtualHosts = {
-    &quot;foo.example.com&quot; = {
-      enableACME = true;
-      acmeRoot = null;
-    };
-  };
-}
-</programlisting>
-    <para>
-      And that’s it! Next time your configuration is rebuilt, or when
-      you add a new virtualHost, it will be DNS-01 validated.
-    </para>
-  </section>
-  <section xml:id="module-security-acme-root-owned">
-    <title>Using ACME with services demanding root owned
-    certificates</title>
-    <para>
-      Some services refuse to start if the configured certificate files
-      are not owned by root. PostgreSQL and OpenSMTPD are examples of
-      these. There is no way to change the user the ACME module uses (it
-      will always be <literal>acme</literal>), however you can use
-      systemd’s <literal>LoadCredential</literal> feature to resolve
-      this elegantly. Below is an example configuration for OpenSMTPD,
-      but this pattern can be applied to any service.
-    </para>
-    <programlisting>
-# Configure ACME however you like (DNS or HTTP validation), adding
-# the following configuration for the relevant certificate.
-# Note: You cannot use `systemctl reload` here as that would mean
-# the LoadCredential configuration below would be skipped and
-# the service would continue to use old certificates.
-security.acme.certs.&quot;mail.example.com&quot;.postRun = ''
-  systemctl restart opensmtpd
-'';
-
-# Now you must augment OpenSMTPD's systemd service to load
-# the certificate files.
-systemd.services.opensmtpd.requires = [&quot;acme-finished-mail.example.com.target&quot;];
-systemd.services.opensmtpd.serviceConfig.LoadCredential = let
-  certDir = config.security.acme.certs.&quot;mail.example.com&quot;.directory;
-in [
-  &quot;cert.pem:${certDir}/cert.pem&quot;
-  &quot;key.pem:${certDir}/key.pem&quot;
-];
-
-# Finally, configure OpenSMTPD to use these certs.
-services.opensmtpd = let
-  credsDir = &quot;/run/credentials/opensmtpd.service&quot;;
-in {
-  enable = true;
-  setSendmail = false;
-  serverConfiguration = ''
-    pki mail.example.com cert &quot;${credsDir}/cert.pem&quot;
-    pki mail.example.com key &quot;${credsDir}/key.pem&quot;
-    listen on localhost tls pki mail.example.com
-    action act1 relay host smtp://127.0.0.1:10027
-    match for local action act1
-  '';
-};
-</programlisting>
-  </section>
-  <section xml:id="module-security-acme-regenerate">
-    <title>Regenerating certificates</title>
-    <para>
-      Should you need to regenerate a particular certificate in a hurry,
-      such as when a vulnerability is found in Let’s Encrypt, there is
-      now a convenient mechanism for doing so. Running
-      <literal>systemctl clean --what=state acme-example.com.service</literal>
-      will remove all certificate files and the account data for the
-      given domain, allowing you to then
-      <literal>systemctl start acme-example.com.service</literal> to
-      generate fresh ones.
-    </para>
-  </section>
-  <section xml:id="module-security-acme-fix-jws">
-    <title>Fixing JWS Verification error</title>
-    <para>
-      It is possible that your account credentials file may become
-      corrupt and need to be regenerated. In this scenario lego will
-      produce the error <literal>JWS verification error</literal>. The
-      solution is to simply delete the associated accounts file and
-      re-run the affected service(s).
-    </para>
-    <programlisting>
-# Find the accounts folder for the certificate
-systemctl cat acme-example.com.service | grep -Po 'accounts/[^:]*'
-export accountdir=&quot;$(!!)&quot;
-# Move this folder to some place else
-mv /var/lib/acme/.lego/$accountdir{,.bak}
-# Recreate the folder using systemd-tmpfiles
-systemd-tmpfiles --create
-# Get a new account and reissue certificates
-# Note: Do this for all certs that share the same account email address
-systemctl start acme-example.com.service
-</programlisting>
-  </section>
-</chapter>
diff --git a/nixos/modules/security/polkit.nix b/nixos/modules/security/polkit.nix
index f33898578b817..de427ccb295bb 100644
--- a/nixos/modules/security/polkit.nix
+++ b/nixos/modules/security/polkit.nix
@@ -14,7 +14,7 @@ in
 
     security.polkit.enable = mkEnableOption (lib.mdDoc "polkit");
 
-    security.polkit.debug = mkEnableOption (lib.mdDoc "debug logs from polkit. This is required in order to see log messages from rule definitions.");
+    security.polkit.debug = mkEnableOption (lib.mdDoc "debug logs from polkit. This is required in order to see log messages from rule definitions");
 
     security.polkit.extraConfig = mkOption {
       type = types.lines;
diff --git a/nixos/modules/services/audio/hqplayerd.nix b/nixos/modules/services/audio/hqplayerd.nix
index eff1549380c84..d54400b18e307 100644
--- a/nixos/modules/services/audio/hqplayerd.nix
+++ b/nixos/modules/services/audio/hqplayerd.nix
@@ -82,7 +82,6 @@ in
       etc = {
         "hqplayer/hqplayerd.xml" = mkIf (cfg.config != null) { source = pkgs.writeText "hqplayerd.xml" cfg.config; };
         "hqplayer/hqplayerd4-key.xml" = mkIf (cfg.licenseFile != null) { source = cfg.licenseFile; };
-        "modules-load.d/taudio2.conf".source = "${pkg}/etc/modules-load.d/taudio2.conf";
       };
       systemPackages = [ pkg ];
     };
@@ -91,8 +90,6 @@ in
       allowedTCPPorts = [ 8088 4321 ];
     };
 
-    services.udev.packages = [ pkg ];
-
     systemd = {
       tmpfiles.rules = [
         "d ${configDir}      0755 hqplayer hqplayer - -"
diff --git a/nixos/modules/services/audio/roon-bridge.nix b/nixos/modules/services/audio/roon-bridge.nix
index e9335091ba9a9..70392b647cc66 100644
--- a/nixos/modules/services/audio/roon-bridge.nix
+++ b/nixos/modules/services/audio/roon-bridge.nix
@@ -42,7 +42,7 @@ in {
       environment.ROON_DATAROOT = "/var/lib/${name}";
 
       serviceConfig = {
-        ExecStart = "${pkgs.roon-bridge}/start.sh";
+        ExecStart = "${pkgs.roon-bridge}/bin/RoonBridge";
         LimitNOFILE = 8192;
         User = cfg.user;
         Group = cfg.group;
diff --git a/nixos/modules/services/audio/tts.nix b/nixos/modules/services/audio/tts.nix
new file mode 100644
index 0000000000000..1a355c8ee39f5
--- /dev/null
+++ b/nixos/modules/services/audio/tts.nix
@@ -0,0 +1,151 @@
+{ config
+, lib
+, pkgs
+, ...
+}:
+
+let
+  cfg = config.services.tts;
+in
+
+{
+  options.services.tts = let
+    inherit (lib) literalExpression mkOption mdDoc mkEnableOption types;
+  in  {
+    servers = mkOption {
+      type = types.attrsOf (types.submodule (
+        { ... }: {
+          options = {
+            enable = mkEnableOption (mdDoc "Coqui TTS server");
+
+            port = mkOption {
+              type = types.port;
+              example = 5000;
+              description = mdDoc ''
+                Port to bind the TTS server to.
+              '';
+            };
+
+            model = mkOption {
+              type = types.nullOr types.str;
+              default = "tts_models/en/ljspeech/tacotron2-DDC";
+              example = null;
+              description = mdDoc ''
+                Name of the model to download and use for speech synthesis.
+
+                Check `tts-server --list_models` for possible values.
+
+                Set to `null` to use a custom model.
+              '';
+            };
+
+            useCuda = mkOption {
+              type = types.bool;
+              default = false;
+              example = true;
+              description = mdDoc ''
+                Whether to offload computation onto a CUDA compatible GPU.
+              '';
+            };
+
+            extraArgs = mkOption {
+              type = types.listOf types.str;
+              default = [];
+              description = mdDoc ''
+                Extra arguments to pass to the server commandline.
+              '';
+            };
+          };
+        }
+      ));
+      default = {};
+      example = literalExpression ''
+        {
+          english = {
+            port = 5300;
+            model = "tts_models/en/ljspeech/tacotron2-DDC";
+          };
+          german = {
+            port = 5301;
+            model = "tts_models/de/thorsten/tacotron2-DDC";
+          };
+          dutch = {
+            port = 5302;
+            model = "tts_models/nl/mai/tacotron2-DDC";
+          };
+        }
+      '';
+      description = mdDoc ''
+        TTS server instances.
+      '';
+    };
+  };
+
+  config = let
+    inherit (lib) mkIf mapAttrs' nameValuePair optionalString concatMapStringsSep escapeShellArgs;
+  in mkIf (cfg.servers != {}) {
+    systemd.services = mapAttrs' (server: options:
+      nameValuePair "tts-${server}" {
+        description = "Coqui TTS server instance ${server}";
+        after = [
+          "network-online.target"
+        ];
+        wantedBy = [
+          "multi-user.target"
+        ];
+        path = with pkgs; [
+          espeak-ng
+        ];
+        environment.HOME = "/var/lib/tts";
+        serviceConfig = {
+          DynamicUser = true;
+          User = "tts";
+          StateDirectory = "tts";
+          ExecStart = "${pkgs.tts}/bin/tts-server --port ${toString options.port}"
+            + optionalString (options.model != null) " --model_name ${options.model}"
+            + optionalString (options.useCuda) " --use_cuda"
+            + (concatMapStringsSep " " escapeShellArgs options.extraArgs);
+          CapabilityBoundingSet = "";
+          DeviceAllow = if options.useCuda then [
+            # https://docs.nvidia.com/dgx/pdf/dgx-os-5-user-guide.pdf
+            "/dev/nvidia1"
+            "/dev/nvidia2"
+            "/dev/nvidia3"
+            "/dev/nvidia4"
+            "/dev/nvidia-caps/nvidia-cap1"
+            "/dev/nvidia-caps/nvidia-cap2"
+            "/dev/nvidiactl"
+            "/dev/nvidia-modeset"
+            "/dev/nvidia-uvm"
+            "/dev/nvidia-uvm-tools"
+          ] else "";
+          DevicePolicy = "closed";
+          LockPersonality = true;
+          # jit via numba->llvmpipe
+          MemoryDenyWriteExecute = false;
+          PrivateDevices = true;
+          PrivateUsers = true;
+          ProtectHome = true;
+          ProtectHostname = true;
+          ProtectKernelLogs = true;
+          ProtectKernelModules = true;
+          ProtectKernelTunables = true;
+          ProtectControlGroups = true;
+          ProtectProc = "invisible";
+          ProcSubset = "pid";
+          RestrictAddressFamilies = [
+            "AF_INET"
+            "AF_INET6"
+          ];
+          RestrictNamespaces = true;
+          RestrictRealtime = true;
+          SystemCallArchitectures = "native";
+          SystemCallFilter = [
+            "@system-service"
+            "~@privileged"
+          ];
+          UMask = "0077";
+        };
+      }) cfg.servers;
+  };
+}
diff --git a/nixos/modules/services/audio/ympd.nix b/nixos/modules/services/audio/ympd.nix
index 811b81030efcf..b74cc3f9c0b41 100644
--- a/nixos/modules/services/audio/ympd.nix
+++ b/nixos/modules/services/audio/ympd.nix
@@ -48,8 +48,46 @@ in {
 
     systemd.services.ympd = {
       description = "Standalone MPD Web GUI written in C";
+
       wantedBy = [ "multi-user.target" ];
-      serviceConfig.ExecStart = "${pkgs.ympd}/bin/ympd --host ${cfg.mpd.host} --port ${toString cfg.mpd.port} --webport ${toString cfg.webPort} --user nobody";
+      after = [ "network-online.target" ];
+
+      serviceConfig = {
+        ExecStart = ''
+          ${pkgs.ympd}/bin/ympd \
+            --host ${cfg.mpd.host} \
+            --port ${toString cfg.mpd.port} \
+            --webport ${toString cfg.webPort}
+        '';
+
+        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"
+          "~@process"
+          "~@setuid"
+        ];
+      };
     };
 
   };
diff --git a/nixos/modules/services/backup/borgbackup.md b/nixos/modules/services/backup/borgbackup.md
index e86ae593bbd62..39141f6ec8587 100644
--- a/nixos/modules/services/backup/borgbackup.md
+++ b/nixos/modules/services/backup/borgbackup.md
@@ -128,7 +128,7 @@ To backup your home directory to borgbase you have to:
   - Initialize the repository on the server. Eg.
 
         sudo borg init --encryption=repokey-blake2  \
-            -rsh "ssh -i /run/keys/id_ed25519_borgbase" \
+            --rsh "ssh -i /run/keys/id_ed25519_borgbase" \
             zzz2aaaaa@zzz2aaaaa.repo.borgbase.com:repo
 
   - Add it to your NixOS configuration, e.g.
diff --git a/nixos/modules/services/backup/borgbackup.nix b/nixos/modules/services/backup/borgbackup.nix
index c5fc09dcea028..bc2d79ac10ac6 100644
--- a/nixos/modules/services/backup/borgbackup.nix
+++ b/nixos/modules/services/backup/borgbackup.nix
@@ -226,7 +226,7 @@ let
 
 in {
   meta.maintainers = with maintainers; [ dotlambda ];
-  meta.doc = ./borgbackup.xml;
+  meta.doc = ./borgbackup.md;
 
   ###### interface
 
diff --git a/nixos/modules/services/backup/borgbackup.xml b/nixos/modules/services/backup/borgbackup.xml
deleted file mode 100644
index 2b9e0baa6d09a..0000000000000
--- a/nixos/modules/services/backup/borgbackup.xml
+++ /dev/null
@@ -1,215 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-borgbase">
-  <title>BorgBackup</title>
-  <para>
-    <emphasis>Source:</emphasis>
-    <filename>modules/services/backup/borgbackup.nix</filename>
-  </para>
-  <para>
-    <emphasis>Upstream documentation:</emphasis>
-    <link xlink:href="https://borgbackup.readthedocs.io/">https://borgbackup.readthedocs.io/</link>
-  </para>
-  <para>
-    <link xlink:href="https://www.borgbackup.org/">BorgBackup</link>
-    (short: Borg) is a deduplicating backup program. Optionally, it
-    supports compression and authenticated encryption.
-  </para>
-  <para>
-    The main goal of Borg is to provide an efficient and secure way to
-    backup data. The data deduplication technique used makes Borg
-    suitable for daily backups since only changes are stored. The
-    authenticated encryption technique makes it suitable for backups to
-    not fully trusted targets.
-  </para>
-  <section xml:id="module-services-backup-borgbackup-configuring">
-    <title>Configuring</title>
-    <para>
-      A complete list of options for the Borgbase module may be found
-      <link linkend="opt-services.borgbackup.jobs">here</link>.
-    </para>
-  </section>
-  <section xml:id="opt-services-backup-borgbackup-local-directory">
-    <title>Basic usage for a local backup</title>
-    <para>
-      A very basic configuration for backing up to a locally accessible
-      directory is:
-    </para>
-    <programlisting>
-{
-    opt.services.borgbackup.jobs = {
-      { rootBackup = {
-          paths = &quot;/&quot;;
-          exclude = [ &quot;/nix&quot; &quot;/path/to/local/repo&quot; ];
-          repo = &quot;/path/to/local/repo&quot;;
-          doInit = true;
-          encryption = {
-            mode = &quot;repokey&quot;;
-            passphrase = &quot;secret&quot;;
-          };
-          compression = &quot;auto,lzma&quot;;
-          startAt = &quot;weekly&quot;;
-        };
-      }
-    };
-}
-</programlisting>
-    <warning>
-      <para>
-        If you do not want the passphrase to be stored in the
-        world-readable Nix store, use passCommand. You find an example
-        below.
-      </para>
-    </warning>
-  </section>
-  <section xml:id="opt-services-backup-create-server">
-    <title>Create a borg backup server</title>
-    <para>
-      You should use a different SSH key for each repository you write
-      to, because the specified keys are restricted to running borg
-      serve and can only access this single repository. You need the
-      output of the generate pub file.
-    </para>
-    <programlisting>
-# sudo ssh-keygen -N '' -t ed25519 -f /run/keys/id_ed25519_my_borg_repo
-# cat /run/keys/id_ed25519_my_borg_repo
-ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID78zmOyA+5uPG4Ot0hfAy+sLDPU1L4AiIoRYEIVbbQ/ root@nixos
-</programlisting>
-    <para>
-      Add the following snippet to your NixOS configuration:
-    </para>
-    <programlisting>
-{
-  services.borgbackup.repos = {
-    my_borg_repo = {
-      authorizedKeys = [
-        &quot;ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID78zmOyA+5uPG4Ot0hfAy+sLDPU1L4AiIoRYEIVbbQ/ root@nixos&quot;
-      ] ;
-      path = &quot;/var/lib/my_borg_repo&quot; ;
-    };
-  };
-}
-</programlisting>
-  </section>
-  <section xml:id="opt-services-backup-borgbackup-remote-server">
-    <title>Backup to the borg repository server</title>
-    <para>
-      The following NixOS snippet creates an hourly backup to the
-      service (on the host nixos) as created in the section above. We
-      assume that you have stored a secret passphrasse in the file
-      <filename>/run/keys/borgbackup_passphrase</filename>, which should
-      be only accessible by root
-    </para>
-    <programlisting>
-{
-  services.borgbackup.jobs = {
-    backupToLocalServer = {
-      paths = [ &quot;/etc/nixos&quot; ];
-      doInit = true;
-      repo =  &quot;borg@nixos:.&quot; ;
-      encryption = {
-        mode = &quot;repokey-blake2&quot;;
-        passCommand = &quot;cat /run/keys/borgbackup_passphrase&quot;;
-      };
-      environment = { BORG_RSH = &quot;ssh -i /run/keys/id_ed25519_my_borg_repo&quot;; };
-      compression = &quot;auto,lzma&quot;;
-      startAt = &quot;hourly&quot;;
-    };
-  };
-};
-</programlisting>
-    <para>
-      The following few commands (run as root) let you test your backup.
-    </para>
-    <programlisting>
-&gt; nixos-rebuild switch
-...restarting the following units: polkit.service
-&gt; systemctl restart borgbackup-job-backupToLocalServer
-&gt; sleep 10
-&gt; systemctl restart borgbackup-job-backupToLocalServer
-&gt; export BORG_PASSPHRASE=topSecrect
-&gt; borg list --rsh='ssh -i /run/keys/id_ed25519_my_borg_repo' borg@nixos:.
-nixos-backupToLocalServer-2020-03-30T21:46:17 Mon, 2020-03-30 21:46:19 [84feb97710954931ca384182f5f3cb90665f35cef214760abd7350fb064786ac]
-nixos-backupToLocalServer-2020-03-30T21:46:30 Mon, 2020-03-30 21:46:32 [e77321694ecd160ca2228611747c6ad1be177d6e0d894538898de7a2621b6e68]
-</programlisting>
-  </section>
-  <section xml:id="opt-services-backup-borgbackup-borgbase">
-    <title>Backup to a hosting service</title>
-    <para>
-      Several companies offer
-      <link xlink:href="https://www.borgbackup.org/support/commercial.html">(paid)
-      hosting services</link> for Borg repositories.
-    </para>
-    <para>
-      To backup your home directory to borgbase you have to:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          Generate a SSH key without a password, to access the remote
-          server. E.g.
-        </para>
-        <programlisting>
-sudo ssh-keygen -N '' -t ed25519 -f /run/keys/id_ed25519_borgbase
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          Create the repository on the server by following the
-          instructions for your hosting server.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Initialize the repository on the server. Eg.
-        </para>
-        <programlisting>
-sudo borg init --encryption=repokey-blake2  \
-    -rsh &quot;ssh -i /run/keys/id_ed25519_borgbase&quot; \
-    zzz2aaaaa@zzz2aaaaa.repo.borgbase.com:repo
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          Add it to your NixOS configuration, e.g.
-        </para>
-        <programlisting>
-{
-    services.borgbackup.jobs = {
-    my_Remote_Backup = {
-        paths = [ &quot;/&quot; ];
-        exclude = [ &quot;/nix&quot; &quot;'**/.cache'&quot; ];
-        repo =  &quot;zzz2aaaaa@zzz2aaaaa.repo.borgbase.com:repo&quot;;
-          encryption = {
-          mode = &quot;repokey-blake2&quot;;
-          passCommand = &quot;cat /run/keys/borgbackup_passphrase&quot;;
-        };
-        environment = { BORG_RSH = &quot;ssh -i /run/keys/id_ed25519_borgbase&quot;; };
-        compression = &quot;auto,lzma&quot;;
-        startAt = &quot;daily&quot;;
-    };
-  };
-}}
-</programlisting>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="opt-services-backup-borgbackup-vorta">
-    <title>Vorta backup client for the desktop</title>
-    <para>
-      Vorta is a backup client for macOS and Linux desktops. It
-      integrates the mighty BorgBackup with your desktop environment to
-      protect your data from disk failure, ransomware and theft.
-    </para>
-    <para>
-      It can be installed in NixOS e.g. by adding
-      <literal>pkgs.vorta</literal> to
-      <xref linkend="opt-environment.systemPackages" />.
-    </para>
-    <para>
-      Details about using Vorta can be found under
-      <link xlink:href="https://vorta.borgbase.com/usage">https://vorta.borgbase.com</link>
-      .
-    </para>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/backup/zfs-replication.nix b/nixos/modules/services/backup/zfs-replication.nix
index ce914003c6222..8e7059e5b59d0 100644
--- a/nixos/modules/services/backup/zfs-replication.nix
+++ b/nixos/modules/services/backup/zfs-replication.nix
@@ -9,7 +9,7 @@ let
 in {
   options = {
     services.zfs.autoReplication = {
-      enable = mkEnableOption (lib.mdDoc "ZFS snapshot replication.");
+      enable = mkEnableOption (lib.mdDoc "ZFS snapshot replication");
 
       followDelete = mkOption {
         description = lib.mdDoc "Remove remote snapshots that don't have a local correspondent.";
diff --git a/nixos/modules/services/cluster/k3s/default.nix b/nixos/modules/services/cluster/k3s/default.nix
index 693f388de14a6..97c1e57f9b579 100644
--- a/nixos/modules/services/cluster/k3s/default.nix
+++ b/nixos/modules/services/cluster/k3s/default.nix
@@ -106,6 +106,14 @@ in
       description = lib.mdDoc "Only run the server. This option only makes sense for a server.";
     };
 
+    environmentFile = mkOption {
+      type = types.nullOr types.path;
+      description = lib.mdDoc ''
+        File path containing environment variables for configuring the k3s service in the format of an EnvironmentFile. See systemd.exec(5).
+      '';
+      default = null;
+    };
+
     configPath = mkOption {
       type = types.nullOr types.path;
       default = null;
@@ -154,6 +162,7 @@ in
         LimitNPROC = "infinity";
         LimitCORE = "infinity";
         TasksMax = "infinity";
+        EnvironmentFile = cfg.environmentFile;
         ExecStart = concatStringsSep " \\\n " (
           [
             "${cfg.package}/bin/k3s ${cfg.role}"
diff --git a/nixos/modules/services/cluster/kubernetes/addon-manager.nix b/nixos/modules/services/cluster/kubernetes/addon-manager.nix
index 7aa2a8323b1d7..dc851688fbecd 100644
--- a/nixos/modules/services/cluster/kubernetes/addon-manager.nix
+++ b/nixos/modules/services/cluster/kubernetes/addon-manager.nix
@@ -62,7 +62,7 @@ in
       '';
     };
 
-    enable = mkEnableOption (lib.mdDoc "Kubernetes addon manager.");
+    enable = mkEnableOption (lib.mdDoc "Kubernetes addon manager");
   };
 
   ###### implementation
diff --git a/nixos/modules/services/cluster/kubernetes/kubelet.nix b/nixos/modules/services/cluster/kubernetes/kubelet.nix
index 3ede1cb80e85a..8e935d621be4e 100644
--- a/nixos/modules/services/cluster/kubernetes/kubelet.nix
+++ b/nixos/modules/services/cluster/kubernetes/kubelet.nix
@@ -146,7 +146,7 @@ in
       default = "unix:///run/containerd/containerd.sock";
     };
 
-    enable = mkEnableOption (lib.mdDoc "Kubernetes kubelet.");
+    enable = mkEnableOption (lib.mdDoc "Kubernetes kubelet");
 
     extraOpts = mkOption {
       description = lib.mdDoc "Kubernetes kubelet extra command line options.";
diff --git a/nixos/modules/services/computing/slurm/slurm.nix b/nixos/modules/services/computing/slurm/slurm.nix
index 0c80e79d4b797..344c43a429b32 100644
--- a/nixos/modules/services/computing/slurm/slurm.nix
+++ b/nixos/modules/services/computing/slurm/slurm.nix
@@ -383,7 +383,7 @@ in
       "d /var/spool/slurmd 755 root root -"
     ];
 
-    services.openssh.forwardX11 = mkIf cfg.client.enable (mkDefault true);
+    services.openssh.settings.X11Forwarding = mkIf cfg.client.enable (mkDefault true);
 
     systemd.services.slurmctld = mkIf (cfg.server.enable) {
       path = with pkgs; [ wrappedSlurm munge coreutils ]
diff --git a/nixos/modules/services/continuous-integration/github-runner/options.nix b/nixos/modules/services/continuous-integration/github-runner/options.nix
index 6ce08aaaece84..ce88092137241 100644
--- a/nixos/modules/services/continuous-integration/github-runner/options.nix
+++ b/nixos/modules/services/continuous-integration/github-runner/options.nix
@@ -41,17 +41,42 @@ with lib;
   tokenFile = mkOption {
     type = types.path;
     description = lib.mdDoc ''
-      The full path to a file which contains either a runner registration token or a
-      (fine-grained) personal access token (PAT).
+      The full path to a file which contains either
+
+      * a fine-grained personal access token (PAT),
+      * a classic PAT
+      * or a runner registration token
+
+      Changing this option or the `tokenFile`’s content triggers a new runner registration.
+
+      We suggest using the fine-grained PATs. A runner registration token is valid
+      only for 1 hour after creation, so the next time the runner configuration changes
+      this will give you hard-to-debug HTTP 404 errors in the configure step.
+
       The file should contain exactly one line with the token without any newline.
+      (Use `echo -n '…token…' > …token file…` to make sure no newlines sneak in.)
+
+      If the file contains a PAT, the service creates a new registration token
+      on startup as needed.
       If a registration token is given, it can be used to re-register a runner of the same
-      name but is time-limited. If the file contains a PAT, the service creates a new
-      registration token on startup as needed. Make sure the PAT has a scope of
-      `admin:org` for organization-wide registrations or a scope of
-      `repo` for a single repository. Fine-grained PATs need read and write permission
-      to the "Administration" resources.
+      name but is time-limited as noted above.
+
+      For fine-grained PATs:
+
+      Give it "Read and Write access to organization/repository self hosted runners",
+      depending on whether it is organization wide or per-repository. You might have to
+      experiment a little, fine-grained PATs are a `beta` Github feature and still subject
+      to change; nonetheless they are the best option at the moment.
+
+      For classic PATs:
+
+      Make sure the PAT has a scope of `admin:org` for organization-wide registrations
+      or a scope of `repo` for a single repository.
+
+      For runner registration tokens:
 
-      Changing this option or the file's content triggers a new runner registration.
+      Nothing special needs to be done, but updating will break after one hour,
+      so these are not recommended.
     '';
     example = "/run/secrets/github-runner/nixos.token";
   };
diff --git a/nixos/modules/services/continuous-integration/github-runner/service.nix b/nixos/modules/services/continuous-integration/github-runner/service.nix
index db9a19815ec1b..3d11728ebfdd7 100644
--- a/nixos/modules/services/continuous-integration/github-runner/service.nix
+++ b/nixos/modules/services/continuous-integration/github-runner/service.nix
@@ -124,6 +124,8 @@ in
               # The state directory is entirely empty which indicates a first start
               copy_tokens
             fi
+            # Always clean workDir
+            find -H "$WORK_DIRECTORY" -mindepth 1 -delete
           '';
           configureRunner = writeScript "configure" ''
             if [[ -e "${newConfigTokenPath}" ]]; then
@@ -159,9 +161,6 @@ in
             fi
           '';
           setupWorkDir = writeScript "setup-work-dirs" ''
-            # Cleanup previous service
-            ${pkgs.findutils}/bin/find -H "$WORK_DIRECTORY" -mindepth 1 -delete
-
             # Link _diag dir
             ln -s "$LOGS_DIRECTORY" "$WORK_DIRECTORY/_diag"
 
diff --git a/nixos/modules/services/continuous-integration/gitlab-runner.nix b/nixos/modules/services/continuous-integration/gitlab-runner.nix
index 3e6dba16e8ace..53f39f40daa5a 100644
--- a/nixos/modules/services/continuous-integration/gitlab-runner.nix
+++ b/nixos/modules/services/continuous-integration/gitlab-runner.nix
@@ -34,7 +34,7 @@ let
     text = if (cfg.configFile != null) then ''
       cp ${cfg.configFile} ${configPath}
       # make config file readable by service
-      chown -R --reference=$HOME $(dirname ${configPath})
+      chown -R --reference="$HOME" "$(dirname ${configPath})"
     '' else ''
       export CONFIG_FILE=${configPath}
 
@@ -577,7 +577,7 @@ in {
       };
     };
     # Enable periodic clear-docker-cache script
-    systemd.services.gitlab-runner-clear-docker-cache = {
+    systemd.services.gitlab-runner-clear-docker-cache = mkIf (cfg.clear-docker-cache.enable && (any (s: s.executor == "docker") (attrValues cfg.services))) {
       description = "Prune gitlab-runner docker resources";
       restartIfChanged = false;
       unitConfig.X-StopOnRemoval = false;
@@ -590,7 +590,7 @@ in {
         ${pkgs.gitlab-runner}/bin/clear-docker-cache ${toString cfg.clear-docker-cache.flags}
       '';
 
-      startAt = optional cfg.clear-docker-cache.enable cfg.clear-docker-cache.dates;
+      startAt = cfg.clear-docker-cache.dates;
     };
     # Enable docker if `docker` executor is used in any service
     virtualisation.docker.enable = mkIf (
diff --git a/nixos/modules/services/databases/clickhouse.nix b/nixos/modules/services/databases/clickhouse.nix
index 04dd20b5f14d1..1f4a39765cd77 100644
--- a/nixos/modules/services/databases/clickhouse.nix
+++ b/nixos/modules/services/databases/clickhouse.nix
@@ -54,7 +54,7 @@ with lib;
         AmbientCapabilities = "CAP_SYS_NICE";
         StateDirectory = "clickhouse";
         LogsDirectory = "clickhouse";
-        ExecStart = "${cfg.package}/bin/clickhouse-server --config-file=${cfg.package}/etc/clickhouse-server/config.xml";
+        ExecStart = "${cfg.package}/bin/clickhouse-server --config-file=/etc/clickhouse-server/config.xml";
       };
     };
 
diff --git a/nixos/modules/services/databases/foundationdb.nix b/nixos/modules/services/databases/foundationdb.nix
index 16d539b661eb4..48e9898a68c2b 100644
--- a/nixos/modules/services/databases/foundationdb.nix
+++ b/nixos/modules/services/databases/foundationdb.nix
@@ -424,6 +424,6 @@ in
     };
   };
 
-  meta.doc         = ./foundationdb.xml;
+  meta.doc         = ./foundationdb.md;
   meta.maintainers = with lib.maintainers; [ thoughtpolice ];
 }
diff --git a/nixos/modules/services/databases/foundationdb.xml b/nixos/modules/services/databases/foundationdb.xml
deleted file mode 100644
index 611535a9eb8a0..0000000000000
--- a/nixos/modules/services/databases/foundationdb.xml
+++ /dev/null
@@ -1,425 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-foundationdb">
-  <title>FoundationDB</title>
-  <para>
-    <emphasis>Source:</emphasis>
-    <filename>modules/services/databases/foundationdb.nix</filename>
-  </para>
-  <para>
-    <emphasis>Upstream documentation:</emphasis>
-    <link xlink:href="https://apple.github.io/foundationdb/">https://apple.github.io/foundationdb/</link>
-  </para>
-  <para>
-    <emphasis>Maintainer:</emphasis> Austin Seipp
-  </para>
-  <para>
-    <emphasis>Available version(s):</emphasis> 5.1.x, 5.2.x, 6.0.x
-  </para>
-  <para>
-    FoundationDB (or <quote>FDB</quote>) is an open source, distributed,
-    transactional key-value store.
-  </para>
-  <section xml:id="module-services-foundationdb-configuring">
-    <title>Configuring and basic setup</title>
-    <para>
-      To enable FoundationDB, add the following to your
-      <filename>configuration.nix</filename>:
-    </para>
-    <programlisting>
-services.foundationdb.enable = true;
-services.foundationdb.package = pkgs.foundationdb52; # FoundationDB 5.2.x
-</programlisting>
-    <para>
-      The <option>services.foundationdb.package</option> option is
-      required, and must always be specified. Due to the fact
-      FoundationDB network protocols and on-disk storage formats may
-      change between (major) versions, and upgrades must be explicitly
-      handled by the user, you must always manually specify this
-      yourself so that the NixOS module will use the proper version.
-      Note that minor, bugfix releases are always compatible.
-    </para>
-    <para>
-      After running <command>nixos-rebuild</command>, you can verify
-      whether FoundationDB is running by executing
-      <command>fdbcli</command> (which is added to
-      <option>environment.systemPackages</option>):
-    </para>
-    <programlisting>
-$ sudo -u foundationdb fdbcli
-Using cluster file `/etc/foundationdb/fdb.cluster'.
-
-The database is available.
-
-Welcome to the fdbcli. For help, type `help'.
-fdb&gt; status
-
-Using cluster file `/etc/foundationdb/fdb.cluster'.
-
-Configuration:
-  Redundancy mode        - single
-  Storage engine         - memory
-  Coordinators           - 1
-
-Cluster:
-  FoundationDB processes - 1
-  Machines               - 1
-  Memory availability    - 5.4 GB per process on machine with least available
-  Fault Tolerance        - 0 machines
-  Server time            - 04/20/18 15:21:14
-
-...
-
-fdb&gt;
-</programlisting>
-    <para>
-      You can also write programs using the available client libraries.
-      For example, the following Python program can be run in order to
-      grab the cluster status, as a quick example. (This example uses
-      <command>nix-shell</command> shebang support to automatically
-      supply the necessary Python modules).
-    </para>
-    <programlisting>
-a@link&gt; cat fdb-status.py
-#! /usr/bin/env nix-shell
-#! nix-shell -i python -p python pythonPackages.foundationdb52
-
-import fdb
-import json
-
-def main():
-    fdb.api_version(520)
-    db = fdb.open()
-
-    @fdb.transactional
-    def get_status(tr):
-        return str(tr['\xff\xff/status/json'])
-
-    obj = json.loads(get_status(db))
-    print('FoundationDB available: %s' % obj['client']['database_status']['available'])
-
-if __name__ == &quot;__main__&quot;:
-    main()
-a@link&gt; chmod +x fdb-status.py
-a@link&gt; ./fdb-status.py
-FoundationDB available: True
-a@link&gt;
-</programlisting>
-    <para>
-      FoundationDB is run under the <command>foundationdb</command> user
-      and group by default, but this may be changed in the NixOS
-      configuration. The systemd unit
-      <command>foundationdb.service</command> controls the
-      <command>fdbmonitor</command> process.
-    </para>
-    <para>
-      By default, the NixOS module for FoundationDB creates a single
-      SSD-storage based database for development and basic usage. This
-      storage engine is designed for SSDs and will perform poorly on
-      HDDs; however it can handle far more data than the alternative
-      <quote>memory</quote> engine and is a better default choice for
-      most deployments. (Note that you can change the storage backend
-      on-the-fly for a given FoundationDB cluster using
-      <command>fdbcli</command>.)
-    </para>
-    <para>
-      Furthermore, only 1 server process and 1 backup agent are started
-      in the default configuration. See below for more on scaling to
-      increase this.
-    </para>
-    <para>
-      FoundationDB stores all data for all server processes under
-      <filename>/var/lib/foundationdb</filename>. You can override this
-      using <option>services.foundationdb.dataDir</option>, e.g.
-    </para>
-    <programlisting>
-services.foundationdb.dataDir = &quot;/data/fdb&quot;;
-</programlisting>
-    <para>
-      Similarly, logs are stored under
-      <filename>/var/log/foundationdb</filename> by default, and there
-      is a corresponding <option>services.foundationdb.logDir</option>
-      as well.
-    </para>
-  </section>
-  <section xml:id="module-services-foundationdb-scaling">
-    <title>Scaling processes and backup agents</title>
-    <para>
-      Scaling the number of server processes is quite easy; simply
-      specify <option>services.foundationdb.serverProcesses</option> to
-      be the number of FoundationDB worker processes that should be
-      started on the machine.
-    </para>
-    <para>
-      FoundationDB worker processes typically require 4GB of RAM
-      per-process at minimum for good performance, so this option is set
-      to 1 by default since the maximum amount of RAM is unknown. You’re
-      advised to abide by this restriction, so pick a number of
-      processes so that each has 4GB or more.
-    </para>
-    <para>
-      A similar option exists in order to scale backup agent processes,
-      <option>services.foundationdb.backupProcesses</option>. Backup
-      agents are not as performance/RAM sensitive, so feel free to
-      experiment with the number of available backup processes.
-    </para>
-  </section>
-  <section xml:id="module-services-foundationdb-clustering">
-    <title>Clustering</title>
-    <para>
-      FoundationDB on NixOS works similarly to other Linux systems, so
-      this section will be brief. Please refer to the full FoundationDB
-      documentation for more on clustering.
-    </para>
-    <para>
-      FoundationDB organizes clusters using a set of
-      <emphasis>coordinators</emphasis>, which are just
-      specially-designated worker processes. By default, every
-      installation of FoundationDB on NixOS will start as its own
-      individual cluster, with a single coordinator: the first worker
-      process on <command>localhost</command>.
-    </para>
-    <para>
-      Coordinators are specified globally using the
-      <command>/etc/foundationdb/fdb.cluster</command> file, which all
-      servers and client applications will use to find and join
-      coordinators. Note that this file <emphasis>can not</emphasis> be
-      managed by NixOS so easily: FoundationDB is designed so that it
-      will rewrite the file at runtime for all clients and nodes when
-      cluster coordinators change, with clients transparently handling
-      this without intervention. It is fundamentally a mutable file, and
-      you should not try to manage it in any way in NixOS.
-    </para>
-    <para>
-      When dealing with a cluster, there are two main things you want to
-      do:
-    </para>
-    <itemizedlist spacing="compact">
-      <listitem>
-        <para>
-          Add a node to the cluster for storage/compute.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Promote an ordinary worker to a coordinator.
-        </para>
-      </listitem>
-    </itemizedlist>
-    <para>
-      A node must already be a member of the cluster in order to
-      properly be promoted to a coordinator, so you must always add it
-      first if you wish to promote it.
-    </para>
-    <para>
-      To add a machine to a FoundationDB cluster:
-    </para>
-    <itemizedlist spacing="compact">
-      <listitem>
-        <para>
-          Choose one of the servers to start as the initial coordinator.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Copy the <command>/etc/foundationdb/fdb.cluster</command> file
-          from this server to all the other servers. Restart
-          FoundationDB on all of these other servers, so they join the
-          cluster.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          All of these servers are now connected and working together in
-          the cluster, under the chosen coordinator.
-        </para>
-      </listitem>
-    </itemizedlist>
-    <para>
-      At this point, you can add as many nodes as you want by just
-      repeating the above steps. By default there will still be a single
-      coordinator: you can use <command>fdbcli</command> to change this
-      and add new coordinators.
-    </para>
-    <para>
-      As a convenience, FoundationDB can automatically assign
-      coordinators based on the redundancy mode you wish to achieve for
-      the cluster. Once all the nodes have been joined, simply set the
-      replication policy, and then issue the
-      <command>coordinators auto</command> command
-    </para>
-    <para>
-      For example, assuming we have 3 nodes available, we can enable
-      double redundancy mode, then auto-select coordinators. For double
-      redundancy, 3 coordinators is ideal: therefore FoundationDB will
-      make <emphasis>every</emphasis> node a coordinator automatically:
-    </para>
-    <programlisting>
-fdbcli&gt; configure double ssd
-fdbcli&gt; coordinators auto
-</programlisting>
-    <para>
-      This will transparently update all the servers within seconds, and
-      appropriately rewrite the <command>fdb.cluster</command> file, as
-      well as informing all client processes to do the same.
-    </para>
-  </section>
-  <section xml:id="module-services-foundationdb-connectivity">
-    <title>Client connectivity</title>
-    <para>
-      By default, all clients must use the current
-      <command>fdb.cluster</command> file to access a given FoundationDB
-      cluster. This file is located by default in
-      <command>/etc/foundationdb/fdb.cluster</command> on all machines
-      with the FoundationDB service enabled, so you may copy the active
-      one from your cluster to a new node in order to connect, if it is
-      not part of the cluster.
-    </para>
-  </section>
-  <section xml:id="module-services-foundationdb-authorization">
-    <title>Client authorization and TLS</title>
-    <para>
-      By default, any user who can connect to a FoundationDB process
-      with the correct cluster configuration can access anything.
-      FoundationDB uses a pluggable design to transport security, and
-      out of the box it supports a LibreSSL-based plugin for TLS
-      support. This plugin not only does in-flight encryption, but also
-      performs client authorization based on the given endpoint’s
-      certificate chain. For example, a FoundationDB server may be
-      configured to only accept client connections over TLS, where the
-      client TLS certificate is from organization <emphasis>Acme
-      Co</emphasis> in the <emphasis>Research and Development</emphasis>
-      unit.
-    </para>
-    <para>
-      Configuring TLS with FoundationDB is done using the
-      <option>services.foundationdb.tls</option> options in order to
-      control the peer verification string, as well as the certificate
-      and its private key.
-    </para>
-    <para>
-      Note that the certificate and its private key must be accessible
-      to the FoundationDB user account that the server runs under. These
-      files are also NOT managed by NixOS, as putting them into the
-      store may reveal private information.
-    </para>
-    <para>
-      After you have a key and certificate file in place, it is not
-      enough to simply set the NixOS module options – you must also
-      configure the <command>fdb.cluster</command> file to specify that
-      a given set of coordinators use TLS. This is as simple as adding
-      the suffix <command>:tls</command> to your cluster coordinator
-      configuration, after the port number. For example, assuming you
-      have a coordinator on localhost with the default configuration,
-      simply specifying:
-    </para>
-    <programlisting>
-XXXXXX:XXXXXX@127.0.0.1:4500:tls
-</programlisting>
-    <para>
-      will configure all clients and server processes to use TLS from
-      now on.
-    </para>
-  </section>
-  <section xml:id="module-services-foundationdb-disaster-recovery">
-    <title>Backups and Disaster Recovery</title>
-    <para>
-      The usual rules for doing FoundationDB backups apply on NixOS as
-      written in the FoundationDB manual. However, one important
-      difference is the security profile for NixOS: by default, the
-      <command>foundationdb</command> systemd unit uses <emphasis>Linux
-      namespaces</emphasis> to restrict write access to the system,
-      except for the log directory, data directory, and the
-      <command>/etc/foundationdb/</command> directory. This is enforced
-      by default and cannot be disabled.
-    </para>
-    <para>
-      However, a side effect of this is that the
-      <command>fdbbackup</command> command doesn’t work properly for
-      local filesystem backups: FoundationDB uses a server process
-      alongside the database processes to perform backups and copy the
-      backups to the filesystem. As a result, this process is put under
-      the restricted namespaces above: the backup process can only write
-      to a limited number of paths.
-    </para>
-    <para>
-      In order to allow flexible backup locations on local disks, the
-      FoundationDB NixOS module supports a
-      <option>services.foundationdb.extraReadWritePaths</option> option.
-      This option takes a list of paths, and adds them to the systemd
-      unit, allowing the processes inside the service to write (and
-      read) the specified directories.
-    </para>
-    <para>
-      For example, to create backups in
-      <command>/opt/fdb-backups</command>, first set up the paths in the
-      module options:
-    </para>
-    <programlisting>
-services.foundationdb.extraReadWritePaths = [ &quot;/opt/fdb-backups&quot; ];
-</programlisting>
-    <para>
-      Restart the FoundationDB service, and it will now be able to write
-      to this directory (even if it does not yet exist.) Note: this path
-      <emphasis>must</emphasis> exist before restarting the unit.
-      Otherwise, systemd will not include it in the private FoundationDB
-      namespace (and it will not add it dynamically at runtime).
-    </para>
-    <para>
-      You can now perform a backup:
-    </para>
-    <programlisting>
-$ sudo -u foundationdb fdbbackup start  -t default -d file:///opt/fdb-backups
-$ sudo -u foundationdb fdbbackup status -t default
-</programlisting>
-  </section>
-  <section xml:id="module-services-foundationdb-limitations">
-    <title>Known limitations</title>
-    <para>
-      The FoundationDB setup for NixOS should currently be considered
-      beta. FoundationDB is not new software, but the NixOS compilation
-      and integration has only undergone fairly basic testing of all the
-      available functionality.
-    </para>
-    <itemizedlist spacing="compact">
-      <listitem>
-        <para>
-          There is no way to specify individual parameters for
-          individual <command>fdbserver</command> processes. Currently,
-          all server processes inherit all the global
-          <command>fdbmonitor</command> settings.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Ruby bindings are not currently installed.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Go bindings are not currently installed.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="module-services-foundationdb-options">
-    <title>Options</title>
-    <para>
-      NixOS’s FoundationDB module allows you to configure all of the
-      most relevant configuration options for
-      <command>fdbmonitor</command>, matching it quite closely. A
-      complete list of options for the FoundationDB module may be found
-      <link linkend="opt-services.foundationdb.enable">here</link>. You
-      should also read the FoundationDB documentation as well.
-    </para>
-  </section>
-  <section xml:id="module-services-foundationdb-full-docs">
-    <title>Full documentation</title>
-    <para>
-      FoundationDB is a complex piece of software, and requires careful
-      administration to properly use. Full documentation for
-      administration can be found here:
-      <link xlink:href="https://apple.github.io/foundationdb/">https://apple.github.io/foundationdb/</link>.
-    </para>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/databases/postgresql.nix b/nixos/modules/services/databases/postgresql.nix
index 6665e7a088fc1..7bbe1ad225955 100644
--- a/nixos/modules/services/databases/postgresql.nix
+++ b/nixos/modules/services/databases/postgresql.nix
@@ -585,6 +585,6 @@ in
 
   };
 
-  meta.doc = ./postgresql.xml;
+  meta.doc = ./postgresql.md;
   meta.maintainers = with lib.maintainers; [ thoughtpolice danbst ];
 }
diff --git a/nixos/modules/services/databases/postgresql.xml b/nixos/modules/services/databases/postgresql.xml
deleted file mode 100644
index 2f62d5d80b192..0000000000000
--- a/nixos/modules/services/databases/postgresql.xml
+++ /dev/null
@@ -1,250 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-postgresql">
-  <title>PostgreSQL</title>
-  <para>
-    <emphasis>Source:</emphasis>
-    <filename>modules/services/databases/postgresql.nix</filename>
-  </para>
-  <para>
-    <emphasis>Upstream documentation:</emphasis>
-    <link xlink:href="http://www.postgresql.org/docs/">http://www.postgresql.org/docs/</link>
-  </para>
-  <para>
-    PostgreSQL is an advanced, free relational database.
-  </para>
-  <section xml:id="module-services-postgres-configuring">
-    <title>Configuring</title>
-    <para>
-      To enable PostgreSQL, add the following to your
-      <filename>configuration.nix</filename>:
-    </para>
-    <programlisting>
-services.postgresql.enable = true;
-services.postgresql.package = pkgs.postgresql_11;
-</programlisting>
-    <para>
-      Note that you are required to specify the desired version of
-      PostgreSQL (e.g. <literal>pkgs.postgresql_11</literal>). Since
-      upgrading your PostgreSQL version requires a database dump and
-      reload (see below), NixOS cannot provide a default value for
-      <xref linkend="opt-services.postgresql.package" /> such as the
-      most recent release of PostgreSQL.
-    </para>
-    <para>
-      By default, PostgreSQL stores its databases in
-      <filename>/var/lib/postgresql/$psqlSchema</filename>. You can
-      override this using
-      <xref linkend="opt-services.postgresql.dataDir" />, e.g.
-    </para>
-    <programlisting>
-services.postgresql.dataDir = &quot;/data/postgresql&quot;;
-</programlisting>
-  </section>
-  <section xml:id="module-services-postgres-upgrading">
-    <title>Upgrading</title>
-    <note>
-      <para>
-        The steps below demonstrate how to upgrade from an older version
-        to <literal>pkgs.postgresql_13</literal>. These instructions are
-        also applicable to other versions.
-      </para>
-    </note>
-    <para>
-      Major PostgreSQL upgrades require a downtime and a few imperative
-      steps to be called. This is the case because each major version
-      has some internal changes in the databases’ state during major
-      releases. Because of that, NixOS places the state into
-      <filename>/var/lib/postgresql/&lt;version&gt;</filename> where
-      each <literal>version</literal> can be obtained like this:
-    </para>
-    <programlisting>
-$ nix-instantiate --eval -A postgresql_13.psqlSchema
-&quot;13&quot;
-</programlisting>
-    <para>
-      For an upgrade, a script like this can be used to simplify the
-      process:
-    </para>
-    <programlisting>
-{ config, pkgs, ... }:
-{
-  environment.systemPackages = [
-    (let
-      # XXX specify the postgresql package you'd like to upgrade to.
-      # Do not forget to list the extensions you need.
-      newPostgres = pkgs.postgresql_13.withPackages (pp: [
-        # pp.plv8
-      ]);
-    in pkgs.writeScriptBin &quot;upgrade-pg-cluster&quot; ''
-      set -eux
-      # XXX it's perhaps advisable to stop all services that depend on postgresql
-      systemctl stop postgresql
-
-      export NEWDATA=&quot;/var/lib/postgresql/${newPostgres.psqlSchema}&quot;
-
-      export NEWBIN=&quot;${newPostgres}/bin&quot;
-
-      export OLDDATA=&quot;${config.services.postgresql.dataDir}&quot;
-      export OLDBIN=&quot;${config.services.postgresql.package}/bin&quot;
-
-      install -d -m 0700 -o postgres -g postgres &quot;$NEWDATA&quot;
-      cd &quot;$NEWDATA&quot;
-      sudo -u postgres $NEWBIN/initdb -D &quot;$NEWDATA&quot;
-
-      sudo -u postgres $NEWBIN/pg_upgrade \
-        --old-datadir &quot;$OLDDATA&quot; --new-datadir &quot;$NEWDATA&quot; \
-        --old-bindir $OLDBIN --new-bindir $NEWBIN \
-        &quot;$@&quot;
-    '')
-  ];
-}
-</programlisting>
-    <para>
-      The upgrade process is:
-    </para>
-    <orderedlist numeration="arabic">
-      <listitem>
-        <para>
-          Rebuild nixos configuration with the configuration above added
-          to your <filename>configuration.nix</filename>. Alternatively,
-          add that into separate file and reference it in
-          <literal>imports</literal> list.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Login as root (<literal>sudo su -</literal>)
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Run <literal>upgrade-pg-cluster</literal>. It will stop old
-          postgresql, initialize a new one and migrate the old one to
-          the new one. You may supply arguments like
-          <literal>--jobs 4</literal> and <literal>--link</literal> to
-          speedup migration process. See
-          <link xlink:href="https://www.postgresql.org/docs/current/pgupgrade.html">https://www.postgresql.org/docs/current/pgupgrade.html</link>
-          for details.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Change postgresql package in NixOS configuration to the one
-          you were upgrading to via
-          <xref linkend="opt-services.postgresql.package" />. Rebuild
-          NixOS. This should start new postgres using upgraded data
-          directory and all services you stopped during the upgrade.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          After the upgrade it’s advisable to analyze the new cluster.
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              For PostgreSQL ≥ 14, use the <literal>vacuumdb</literal>
-              command printed by the upgrades script.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              For PostgreSQL &lt; 14, run (as
-              <literal>su -l postgres</literal> in the
-              <xref linkend="opt-services.postgresql.dataDir" />, in
-              this example <filename>/var/lib/postgresql/13</filename>):
-            </para>
-            <programlisting>
-$ ./analyze_new_cluster.sh
-</programlisting>
-          </listitem>
-        </itemizedlist>
-        <warning>
-          <para>
-            The next step removes the old state-directory!
-          </para>
-        </warning>
-        <programlisting>
-$ ./delete_old_cluster.sh
-</programlisting>
-      </listitem>
-    </orderedlist>
-  </section>
-  <section xml:id="module-services-postgres-options">
-    <title>Options</title>
-    <para>
-      A complete list of options for the PostgreSQL module may be found
-      <link linkend="opt-services.postgresql.enable">here</link>.
-    </para>
-  </section>
-  <section xml:id="module-services-postgres-plugins">
-    <title>Plugins</title>
-    <para>
-      Plugins collection for each PostgreSQL version can be accessed
-      with <literal>.pkgs</literal>. For example, for
-      <literal>pkgs.postgresql_11</literal> package, its plugin
-      collection is accessed by
-      <literal>pkgs.postgresql_11.pkgs</literal>:
-    </para>
-    <programlisting>
-$ nix repl '&lt;nixpkgs&gt;'
-
-Loading '&lt;nixpkgs&gt;'...
-Added 10574 variables.
-
-nix-repl&gt; postgresql_11.pkgs.&lt;TAB&gt;&lt;TAB&gt;
-postgresql_11.pkgs.cstore_fdw        postgresql_11.pkgs.pg_repack
-postgresql_11.pkgs.pg_auto_failover  postgresql_11.pkgs.pg_safeupdate
-postgresql_11.pkgs.pg_bigm           postgresql_11.pkgs.pg_similarity
-postgresql_11.pkgs.pg_cron           postgresql_11.pkgs.pg_topn
-postgresql_11.pkgs.pg_hll            postgresql_11.pkgs.pgjwt
-postgresql_11.pkgs.pg_partman        postgresql_11.pkgs.pgroonga
-...
-</programlisting>
-    <para>
-      To add plugins via NixOS configuration, set
-      <literal>services.postgresql.extraPlugins</literal>:
-    </para>
-    <programlisting>
-services.postgresql.package = pkgs.postgresql_11;
-services.postgresql.extraPlugins = with pkgs.postgresql_11.pkgs; [
-  pg_repack
-  postgis
-];
-</programlisting>
-    <para>
-      You can build custom PostgreSQL-with-plugins (to be used outside
-      of NixOS) using function <literal>.withPackages</literal>. For
-      example, creating a custom PostgreSQL package in an overlay can
-      look like:
-    </para>
-    <programlisting>
-self: super: {
-  postgresql_custom = self.postgresql_11.withPackages (ps: [
-    ps.pg_repack
-    ps.postgis
-  ]);
-}
-</programlisting>
-    <para>
-      Here’s a recipe on how to override a particular plugin through an
-      overlay:
-    </para>
-    <programlisting>
-self: super: {
-  postgresql_11 = super.postgresql_11.override { this = self.postgresql_11; } // {
-    pkgs = super.postgresql_11.pkgs // {
-      pg_repack = super.postgresql_11.pkgs.pg_repack.overrideAttrs (_: {
-        name = &quot;pg_repack-v20181024&quot;;
-        src = self.fetchzip {
-          url = &quot;https://github.com/reorg/pg_repack/archive/923fa2f3c709a506e111cc963034bf2fd127aa00.tar.gz&quot;;
-          sha256 = &quot;17k6hq9xaax87yz79j773qyigm4fwk8z4zh5cyp6z0sxnwfqxxw5&quot;;
-        };
-      });
-    };
-  };
-}
-</programlisting>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/desktops/flatpak.nix b/nixos/modules/services/desktops/flatpak.nix
index 3b14ad75ab300..d99faf381e019 100644
--- a/nixos/modules/services/desktops/flatpak.nix
+++ b/nixos/modules/services/desktops/flatpak.nix
@@ -7,7 +7,7 @@ let
   cfg = config.services.flatpak;
 in {
   meta = {
-    doc = ./flatpak.xml;
+    doc = ./flatpak.md;
     maintainers = pkgs.flatpak.meta.maintainers;
   };
 
diff --git a/nixos/modules/services/desktops/flatpak.xml b/nixos/modules/services/desktops/flatpak.xml
deleted file mode 100644
index cdc3278fa9963..0000000000000
--- a/nixos/modules/services/desktops/flatpak.xml
+++ /dev/null
@@ -1,59 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-flatpak">
-  <title>Flatpak</title>
-  <para>
-    <emphasis>Source:</emphasis>
-    <filename>modules/services/desktop/flatpak.nix</filename>
-  </para>
-  <para>
-    <emphasis>Upstream documentation:</emphasis>
-    <link xlink:href="https://github.com/flatpak/flatpak/wiki">https://github.com/flatpak/flatpak/wiki</link>
-  </para>
-  <para>
-    Flatpak is a system for building, distributing, and running
-    sandboxed desktop applications on Linux.
-  </para>
-  <para>
-    To enable Flatpak, add the following to your
-    <filename>configuration.nix</filename>:
-  </para>
-  <programlisting>
-  services.flatpak.enable = true;
-</programlisting>
-  <para>
-    For the sandboxed apps to work correctly, desktop integration
-    portals need to be installed. If you run GNOME, this will be handled
-    automatically for you; in other cases, you will need to add
-    something like the following to your
-    <filename>configuration.nix</filename>:
-  </para>
-  <programlisting>
-  xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-gtk ];
-</programlisting>
-  <para>
-    Then, you will need to add a repository, for example,
-    <link xlink:href="https://github.com/flatpak/flatpak/wiki">Flathub</link>,
-    either using the following commands:
-  </para>
-  <programlisting>
-$ flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
-$ flatpak update
-</programlisting>
-  <para>
-    or by opening the
-    <link xlink:href="https://flathub.org/repo/flathub.flatpakrepo">repository
-    file</link> in GNOME Software.
-  </para>
-  <para>
-    Finally, you can search and install programs:
-  </para>
-  <programlisting>
-$ flatpak search bustle
-$ flatpak install flathub org.freedesktop.Bustle
-$ flatpak run org.freedesktop.Bustle
-</programlisting>
-  <para>
-    Again, GNOME Software offers graphical interface for these tasks.
-  </para>
-</chapter>
diff --git a/nixos/modules/services/desktops/gnome/evolution-data-server.nix b/nixos/modules/services/desktops/gnome/evolution-data-server.nix
index 0006ba1a7bad5..a8db7dce8fdf2 100644
--- a/nixos/modules/services/desktops/gnome/evolution-data-server.nix
+++ b/nixos/modules/services/desktops/gnome/evolution-data-server.nix
@@ -27,7 +27,7 @@ with lib;
   options = {
 
     services.gnome.evolution-data-server = {
-      enable = mkEnableOption (lib.mdDoc "Evolution Data Server, a collection of services for storing addressbooks and calendars.");
+      enable = mkEnableOption (lib.mdDoc "Evolution Data Server, a collection of services for storing addressbooks and calendars");
       plugins = mkOption {
         type = types.listOf types.package;
         default = [ ];
@@ -35,7 +35,7 @@ with lib;
       };
     };
     programs.evolution = {
-      enable = mkEnableOption (lib.mdDoc "Evolution, a Personal information management application that provides integrated mail, calendaring and address book functionality.");
+      enable = mkEnableOption (lib.mdDoc "Evolution, a Personal information management application that provides integrated mail, calendaring and address book functionality");
       plugins = mkOption {
         type = types.listOf types.package;
         default = [ ];
diff --git a/nixos/modules/services/desktops/pipewire/daemon/client-rt.conf.json b/nixos/modules/services/desktops/pipewire/daemon/client-rt.conf.json
index 9aa51b61431db..c204606193af5 100644
--- a/nixos/modules/services/desktops/pipewire/daemon/client-rt.conf.json
+++ b/nixos/modules/services/desktops/pipewire/daemon/client-rt.conf.json
@@ -35,5 +35,20 @@
     }
   ],
   "filter.properties": {},
-  "stream.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/jack.conf.json b/nixos/modules/services/desktops/pipewire/daemon/jack.conf.json
index 4a173f7322972..f2e396dd28d76 100644
--- a/nixos/modules/services/desktops/pipewire/daemon/jack.conf.json
+++ b/nixos/modules/services/desktops/pipewire/daemon/jack.conf.json
@@ -58,6 +58,18 @@
           "node.passive": true
         }
       }
+    },
+    {
+      "matches": [
+        {
+          "client.name": "Mixxx"
+        }
+      ],
+      "actions": {
+        "update-props": {
+          "jack.merge-monitor": false
+        }
+      }
     }
   ]
 }
diff --git a/nixos/modules/services/desktops/pipewire/daemon/pipewire-aes67.conf.json b/nixos/modules/services/desktops/pipewire/daemon/pipewire-aes67.conf.json
new file mode 100644
index 0000000000000..aaffa93ca964c
--- /dev/null
+++ b/nixos/modules/services/desktops/pipewire/daemon/pipewire-aes67.conf.json
@@ -0,0 +1,38 @@
+{
+  "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.conf.json b/nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json
index 53fc103d22144..a47abe2213d94 100644
--- a/nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json
+++ b/nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json
@@ -3,10 +3,10 @@
     "link.max-buffers": 16,
     "core.daemon": true,
     "core.name": "pipewire-0",
-    "default.clock.min-quantum": 16,
     "vm.overrides": {
       "default.clock.min-quantum": 1024
-    }
+    },
+    "module.x11.bell": true
   },
   "context.spa-libs": {
     "audio.convert.*": "audioconvert/libspa-audioconvert",
@@ -77,6 +77,11 @@
       "flags": [
         "ifexists",
         "nofail"
+      ],
+      "condition": [
+        {
+          "module.x11.bell": true
+        }
       ]
     }
   ],
diff --git a/nixos/modules/services/desktops/pipewire/pipewire.nix b/nixos/modules/services/desktops/pipewire/pipewire.nix
index a4ef88a45ad08..09cec9a791091 100644
--- a/nixos/modules/services/desktops/pipewire/pipewire.nix
+++ b/nixos/modules/services/desktops/pipewire/pipewire.nix
@@ -42,7 +42,7 @@ let
 in {
 
   meta = {
-    maintainers = teams.freedesktop.members;
+    maintainers = teams.freedesktop.members ++ [ lib.maintainers.k900 ];
     # uses attributes of the linked package
     buildDocsInSandbox = false;
   };
diff --git a/nixos/modules/services/development/blackfire.nix b/nixos/modules/services/development/blackfire.nix
index 054cef9ae80b2..3c98d7a281c63 100644
--- a/nixos/modules/services/development/blackfire.nix
+++ b/nixos/modules/services/development/blackfire.nix
@@ -11,7 +11,7 @@ let
 in {
   meta = {
     maintainers = pkgs.blackfire.meta.maintainers;
-    doc = ./blackfire.xml;
+    doc = ./blackfire.md;
   };
 
   options = {
diff --git a/nixos/modules/services/development/blackfire.xml b/nixos/modules/services/development/blackfire.xml
deleted file mode 100644
index 842e5bec97d51..0000000000000
--- a/nixos/modules/services/development/blackfire.xml
+++ /dev/null
@@ -1,61 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-blackfire">
-  <title>Blackfire profiler</title>
-  <para>
-    <emphasis>Source:</emphasis>
-    <filename>modules/services/development/blackfire.nix</filename>
-  </para>
-  <para>
-    <emphasis>Upstream documentation:</emphasis>
-    <link xlink:href="https://blackfire.io/docs/introduction">https://blackfire.io/docs/introduction</link>
-  </para>
-  <para>
-    <link xlink:href="https://blackfire.io">Blackfire</link> is a
-    proprietary tool for profiling applications. There are several
-    languages supported by the product but currently only PHP support is
-    packaged in Nixpkgs. The back-end consists of a module that is
-    loaded into the language runtime (called <emphasis>probe</emphasis>)
-    and a service (<emphasis>agent</emphasis>) that the probe connects
-    to and that sends the profiles to the server.
-  </para>
-  <para>
-    To use it, you will need to enable the agent and the probe on your
-    server. The exact method will depend on the way you use PHP but here
-    is an example of NixOS configuration for PHP-FPM:
-  </para>
-  <programlisting>
-let
-  php = pkgs.php.withExtensions ({ enabled, all }: enabled ++ (with all; [
-    blackfire
-  ]));
-in {
-  # Enable the probe extension for PHP-FPM.
-  services.phpfpm = {
-    phpPackage = php;
-  };
-
-  # Enable and configure the agent.
-  services.blackfire-agent = {
-    enable = true;
-    settings = {
-      # You will need to get credentials at https://blackfire.io/my/settings/credentials
-      # You can also use other options described in https://blackfire.io/docs/up-and-running/configuration/agent
-      server-id = &quot;XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX&quot;;
-      server-token = &quot;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&quot;;
-    };
-  };
-
-  # Make the agent run on start-up.
-  # (WantedBy= from the upstream unit not respected: https://github.com/NixOS/nixpkgs/issues/81138)
-  # Alternately, you can start it manually with `systemctl start blackfire-agent`.
-  systemd.services.blackfire-agent.wantedBy = [ &quot;phpfpm-foo.service&quot; ];
-}
-</programlisting>
-  <para>
-    On your developer machine, you will also want to install
-    <link xlink:href="https://blackfire.io/docs/up-and-running/installation#install-a-profiling-client">the
-    client</link> (see <literal>blackfire</literal> package) or the
-    browser extension to actually trigger the profiling.
-  </para>
-</chapter>
diff --git a/nixos/modules/services/development/zammad.nix b/nixos/modules/services/development/zammad.nix
index 7de11b08b7e77..0faeb4c0e9fab 100644
--- a/nixos/modules/services/development/zammad.nix
+++ b/nixos/modules/services/development/zammad.nix
@@ -28,7 +28,7 @@ in
 
   options = {
     services.zammad = {
-      enable = mkEnableOption (lib.mdDoc "Zammad, a web-based, open source user support/ticketing solution.");
+      enable = mkEnableOption (lib.mdDoc "Zammad, a web-based, open source user support/ticketing solution");
 
       package = mkOption {
         type = types.package;
diff --git a/nixos/modules/services/editors/emacs.nix b/nixos/modules/services/editors/emacs.nix
index 5ae28cd9bbb31..2be46e47d64cf 100644
--- a/nixos/modules/services/editors/emacs.nix
+++ b/nixos/modules/services/editors/emacs.nix
@@ -99,5 +99,5 @@ in
     environment.variables.EDITOR = mkIf cfg.defaultEditor (mkOverride 900 "${editorScript}/bin/emacseditor");
   };
 
-  meta.doc = ./emacs.xml;
+  meta.doc = ./emacs.md;
 }
diff --git a/nixos/modules/services/editors/emacs.xml b/nixos/modules/services/editors/emacs.xml
deleted file mode 100644
index 37d7a93a12b36..0000000000000
--- a/nixos/modules/services/editors/emacs.xml
+++ /dev/null
@@ -1,490 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-emacs">
-  <title>Emacs</title>
-  <para>
-    <link xlink:href="https://www.gnu.org/software/emacs/">Emacs</link>
-    is an extensible, customizable, self-documenting real-time display
-    editor — and more. At its core is an interpreter for Emacs Lisp, a
-    dialect of the Lisp programming language with extensions to support
-    text editing.
-  </para>
-  <para>
-    Emacs runs within a graphical desktop environment using the X Window
-    System, but works equally well on a text terminal. Under macOS, a
-    <quote>Mac port</quote> edition is available, which uses Apple’s
-    native GUI frameworks.
-  </para>
-  <para>
-    Nixpkgs provides a superior environment for running Emacs. It’s
-    simple to create custom builds by overriding the default packages.
-    Chaotic collections of Emacs Lisp code and extensions can be brought
-    under control using declarative package management. NixOS even
-    provides a <command>systemd</command> user service for automatically
-    starting the Emacs daemon.
-  </para>
-  <section xml:id="module-services-emacs-installing">
-    <title>Installing Emacs</title>
-    <para>
-      Emacs can be installed in the normal way for Nix (see
-      <xref linkend="sec-package-management" />). In addition, a NixOS
-      <emphasis>service</emphasis> can be enabled.
-    </para>
-    <section xml:id="module-services-emacs-releases">
-      <title>The Different Releases of Emacs</title>
-      <para>
-        Nixpkgs defines several basic Emacs packages. The following are
-        attributes belonging to the <varname>pkgs</varname> set:
-      </para>
-      <variablelist spacing="compact">
-        <varlistentry>
-          <term>
-            <varname>emacs</varname>
-          </term>
-          <listitem>
-            <para>
-              The latest stable version of Emacs using the
-              <link xlink:href="http://www.gtk.org">GTK 2</link> widget
-              toolkit.
-            </para>
-          </listitem>
-        </varlistentry>
-        <varlistentry>
-          <term>
-            <varname>emacs-nox</varname>
-          </term>
-          <listitem>
-            <para>
-              Emacs built without any dependency on X11 libraries.
-            </para>
-          </listitem>
-        </varlistentry>
-        <varlistentry>
-          <term>
-            <varname>emacsMacport</varname>
-          </term>
-          <listitem>
-            <para>
-              Emacs with the <quote>Mac port</quote> patches, providing
-              a more native look and feel under macOS.
-            </para>
-          </listitem>
-        </varlistentry>
-      </variablelist>
-      <para>
-        If those aren’t suitable, then the following imitation Emacs
-        editors are also available in Nixpkgs:
-        <link xlink:href="https://www.gnu.org/software/zile/">Zile</link>,
-        <link xlink:href="http://homepage.boetes.org/software/mg/">mg</link>,
-        <link xlink:href="http://yi-editor.github.io/">Yi</link>,
-        <link xlink:href="https://joe-editor.sourceforge.io/">jmacs</link>.
-      </para>
-    </section>
-    <section xml:id="module-services-emacs-adding-packages">
-      <title>Adding Packages to Emacs</title>
-      <para>
-        Emacs includes an entire ecosystem of functionality beyond text
-        editing, including a project planner, mail and news reader,
-        debugger interface, calendar, and more.
-      </para>
-      <para>
-        Most extensions are gotten with the Emacs packaging system
-        (<filename>package.el</filename>) from
-        <link xlink:href="https://elpa.gnu.org/">Emacs Lisp Package
-        Archive (ELPA)</link>,
-        <link xlink:href="https://melpa.org/">MELPA</link>,
-        <link xlink:href="https://stable.melpa.org/">MELPA
-        Stable</link>, and
-        <link xlink:href="http://orgmode.org/elpa.html">Org ELPA</link>.
-        Nixpkgs is regularly updated to mirror all these archives.
-      </para>
-      <para>
-        Under NixOS, you can continue to use
-        <literal>package-list-packages</literal> and
-        <literal>package-install</literal> to install packages. You can
-        also declare the set of Emacs packages you need using the
-        derivations from Nixpkgs. The rest of this section discusses
-        declarative installation of Emacs packages through nixpkgs.
-      </para>
-      <para>
-        The first step to declare the list of packages you want in your
-        Emacs installation is to create a dedicated derivation. This can
-        be done in a dedicated <filename>emacs.nix</filename> file such
-        as:
-      </para>
-      <para>
-        <anchor xml:id="ex-emacsNix" />
-      </para>
-      <programlisting language="nix">
-/*
-This is a nix expression to build Emacs and some Emacs packages I like
-from source on any distribution where Nix is installed. This will install
-all the dependencies from the nixpkgs repository and build the binary files
-without interfering with the host distribution.
-
-To build the project, type the following from the current directory:
-
-$ nix-build emacs.nix
-
-To run the newly compiled executable:
-
-$ ./result/bin/emacs
-*/
-
-# The first non-comment line in this file indicates that
-# the whole file represents a function.
-{ pkgs ? import &lt;nixpkgs&gt; {} }:
-
-let
-  # The let expression below defines a myEmacs binding pointing to the
-  # current stable version of Emacs. This binding is here to separate
-  # the choice of the Emacs binary from the specification of the
-  # required packages.
-  myEmacs = pkgs.emacs;
-  # This generates an emacsWithPackages function. It takes a single
-  # argument: a function from a package set to a list of packages
-  # (the packages that will be available in Emacs).
-  emacsWithPackages = (pkgs.emacsPackagesFor myEmacs).emacsWithPackages;
-in
-  # The rest of the file specifies the list of packages to install. In the
-  # example, two packages (magit and zerodark-theme) are taken from
-  # MELPA stable.
-  emacsWithPackages (epkgs: (with epkgs.melpaStablePackages; [
-    magit          # ; Integrate git &lt;C-x g&gt;
-    zerodark-theme # ; Nicolas' theme
-  ])
-  # Two packages (undo-tree and zoom-frm) are taken from MELPA.
-  ++ (with epkgs.melpaPackages; [
-    undo-tree      # ; &lt;C-x u&gt; to show the undo tree
-    zoom-frm       # ; increase/decrease font size for all buffers %lt;C-x C-+&gt;
-  ])
-  # Three packages are taken from GNU ELPA.
-  ++ (with epkgs.elpaPackages; [
-    auctex         # ; LaTeX mode
-    beacon         # ; highlight my cursor when scrolling
-    nameless       # ; hide current package name everywhere in elisp code
-  ])
-  # notmuch is taken from a nixpkgs derivation which contains an Emacs mode.
-  ++ [
-    pkgs.notmuch   # From main packages set
-  ])
-</programlisting>
-      <para>
-        The result of this configuration will be an
-        <command>emacs</command> command which launches Emacs with all
-        of your chosen packages in the <varname>load-path</varname>.
-      </para>
-      <para>
-        You can check that it works by executing this in a terminal:
-      </para>
-      <programlisting>
-$ nix-build emacs.nix
-$ ./result/bin/emacs -q
-</programlisting>
-      <para>
-        and then typing <literal>M-x package-initialize</literal>. Check
-        that you can use all the packages you want in this Emacs
-        instance. For example, try switching to the zerodark theme
-        through
-        <literal>M-x load-theme &lt;RET&gt; zerodark &lt;RET&gt; y</literal>.
-      </para>
-      <tip>
-        <para>
-          A few popular extensions worth checking out are: auctex,
-          company, edit-server, flycheck, helm, iedit, magit,
-          multiple-cursors, projectile, and yasnippet.
-        </para>
-      </tip>
-      <para>
-        The list of available packages in the various ELPA repositories
-        can be seen with the following commands:
-        <anchor xml:id="module-services-emacs-querying-packages" />
-      </para>
-      <programlisting>
-nix-env -f &quot;&lt;nixpkgs&gt;&quot; -qaP -A emacs.pkgs.elpaPackages
-nix-env -f &quot;&lt;nixpkgs&gt;&quot; -qaP -A emacs.pkgs.melpaPackages
-nix-env -f &quot;&lt;nixpkgs&gt;&quot; -qaP -A emacs.pkgs.melpaStablePackages
-nix-env -f &quot;&lt;nixpkgs&gt;&quot; -qaP -A emacs.pkgs.orgPackages
-</programlisting>
-      <para>
-        If you are on NixOS, you can install this particular Emacs for
-        all users by adding it to the list of system packages (see
-        <xref linkend="sec-declarative-package-mgmt" />). Simply modify
-        your file <filename>configuration.nix</filename> to make it
-        contain:
-        <anchor xml:id="module-services-emacs-configuration-nix" />
-      </para>
-      <programlisting>
-{
- environment.systemPackages = [
-   # [...]
-   (import /path/to/emacs.nix { inherit pkgs; })
-  ];
-}
-</programlisting>
-      <para>
-        In this case, the next <command>nixos-rebuild switch</command>
-        will take care of adding your <command>emacs</command> to the
-        <varname>PATH</varname> environment variable (see
-        <xref linkend="sec-changing-config" />).
-      </para>
-      <para>
-        If you are not on NixOS or want to install this particular Emacs
-        only for yourself, you can do so by adding it to your
-        <filename>~/.config/nixpkgs/config.nix</filename> (see
-        <link xlink:href="https://nixos.org/nixpkgs/manual/#sec-modify-via-packageOverrides">Nixpkgs
-        manual</link>):
-        <anchor xml:id="module-services-emacs-config-nix" />
-      </para>
-      <programlisting>
-{
-  packageOverrides = super: let self = super.pkgs; in {
-    myemacs = import /path/to/emacs.nix { pkgs = self; };
-  };
-}
-</programlisting>
-      <para>
-        In this case, the next
-        <literal>nix-env -f '&lt;nixpkgs&gt;' -iA myemacs</literal> will
-        take care of adding your emacs to the <varname>PATH</varname>
-        environment variable.
-      </para>
-    </section>
-    <section xml:id="module-services-emacs-advanced">
-      <title>Advanced Emacs Configuration</title>
-      <para>
-        If you want, you can tweak the Emacs package itself from your
-        <filename>emacs.nix</filename>. For example, if you want to have
-        a GTK 3-based Emacs instead of the default GTK 2-based binary
-        and remove the automatically generated
-        <filename>emacs.desktop</filename> (useful if you only use
-        <command>emacsclient</command>), you can change your file
-        <filename>emacs.nix</filename> in this way:
-      </para>
-      <para>
-        <anchor xml:id="ex-emacsGtk3Nix" />
-      </para>
-      <programlisting>
-{ pkgs ? import &lt;nixpkgs&gt; {} }:
-let
-  myEmacs = (pkgs.emacs.override {
-    # Use gtk3 instead of the default gtk2
-    withGTK3 = true;
-    withGTK2 = false;
-  }).overrideAttrs (attrs: {
-    # I don't want emacs.desktop file because I only use
-    # emacsclient.
-    postInstall = (attrs.postInstall or &quot;&quot;) + ''
-      rm $out/share/applications/emacs.desktop
-    '';
-  });
-in [...]
-</programlisting>
-      <para>
-        After building this file as shown in
-        <link linkend="ex-emacsNix">the example above</link>, you will
-        get an GTK 3-based Emacs binary pre-loaded with your favorite
-        packages.
-      </para>
-    </section>
-  </section>
-  <section xml:id="module-services-emacs-running">
-    <title>Running Emacs as a Service</title>
-    <para>
-      NixOS provides an optional <command>systemd</command> service
-      which launches
-      <link xlink:href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Server.html">Emacs
-      daemon</link> with the user’s login session.
-    </para>
-    <para>
-      <emphasis>Source:</emphasis>
-      <filename>modules/services/editors/emacs.nix</filename>
-    </para>
-    <section xml:id="module-services-emacs-enabling">
-      <title>Enabling the Service</title>
-      <para>
-        To install and enable the <command>systemd</command> user
-        service for Emacs daemon, add the following to your
-        <filename>configuration.nix</filename>:
-      </para>
-      <programlisting>
-services.emacs.enable = true;
-services.emacs.package = import /home/cassou/.emacs.d { pkgs = pkgs; };
-</programlisting>
-      <para>
-        The <varname>services.emacs.package</varname> option allows a
-        custom derivation to be used, for example, one created by
-        <literal>emacsWithPackages</literal>.
-      </para>
-      <para>
-        Ensure that the Emacs server is enabled for your user’s Emacs
-        configuration, either by customizing the
-        <varname>server-mode</varname> variable, or by adding
-        <literal>(server-start)</literal> to
-        <filename>~/.emacs.d/init.el</filename>.
-      </para>
-      <para>
-        To start the daemon, execute the following:
-      </para>
-      <programlisting>
-$ nixos-rebuild switch  # to activate the new configuration.nix
-$ systemctl --user daemon-reload        # to force systemd reload
-$ systemctl --user start emacs.service  # to start the Emacs daemon
-</programlisting>
-      <para>
-        The server should now be ready to serve Emacs clients.
-      </para>
-    </section>
-    <section xml:id="module-services-emacs-starting-client">
-      <title>Starting the client</title>
-      <para>
-        Ensure that the emacs server is enabled, either by customizing
-        the <varname>server-mode</varname> variable, or by adding
-        <literal>(server-start)</literal> to
-        <filename>~/.emacs</filename>.
-      </para>
-      <para>
-        To connect to the emacs daemon, run one of the following:
-      </para>
-      <programlisting>
-emacsclient FILENAME
-emacsclient --create-frame  # opens a new frame (window)
-emacsclient --create-frame --tty  # opens a new frame on the current terminal
-</programlisting>
-    </section>
-    <section xml:id="module-services-emacs-editor-variable">
-      <title>Configuring the <varname>EDITOR</varname> variable</title>
-      <para>
-        If <xref linkend="opt-services.emacs.defaultEditor" /> is
-        <literal>true</literal>, the <varname>EDITOR</varname> variable
-        will be set to a wrapper script which launches
-        <command>emacsclient</command>.
-      </para>
-      <para>
-        Any setting of <varname>EDITOR</varname> in the shell config
-        files will override
-        <varname>services.emacs.defaultEditor</varname>. To make sure
-        <varname>EDITOR</varname> refers to the Emacs wrapper script,
-        remove any existing <varname>EDITOR</varname> assignment from
-        <filename>.profile</filename>, <filename>.bashrc</filename>,
-        <filename>.zshenv</filename> or any other shell config file.
-      </para>
-      <para>
-        If you have formed certain bad habits when editing files, these
-        can be corrected with a shell alias to the wrapper script:
-      </para>
-      <programlisting>
-alias vi=$EDITOR
-</programlisting>
-    </section>
-    <section xml:id="module-services-emacs-per-user">
-      <title>Per-User Enabling of the Service</title>
-      <para>
-        In general, <command>systemd</command> user services are
-        globally enabled by symlinks in
-        <filename>/etc/systemd/user</filename>. In the case where Emacs
-        daemon is not wanted for all users, it is possible to install
-        the service but not globally enable it:
-      </para>
-      <programlisting>
-services.emacs.enable = false;
-services.emacs.install = true;
-</programlisting>
-      <para>
-        To enable the <command>systemd</command> user service for just
-        the currently logged in user, run:
-      </para>
-      <programlisting>
-systemctl --user enable emacs
-</programlisting>
-      <para>
-        This will add the symlink
-        <filename>~/.config/systemd/user/emacs.service</filename>.
-      </para>
-    </section>
-  </section>
-  <section xml:id="module-services-emacs-configuring">
-    <title>Configuring Emacs</title>
-    <para>
-      The Emacs init file should be changed to load the extension
-      packages at startup:
-      <anchor xml:id="module-services-emacs-package-initialisation" />
-    </para>
-    <programlisting>
-(require 'package)
-
-;; optional. makes unpure packages archives unavailable
-(setq package-archives nil)
-
-(setq package-enable-at-startup nil)
-(package-initialize)
-</programlisting>
-    <para>
-      After the declarative emacs package configuration has been tested,
-      previously downloaded packages can be cleaned up by removing
-      <filename>~/.emacs.d/elpa</filename> (do make a backup first, in
-      case you forgot a package).
-    </para>
-    <section xml:id="module-services-emacs-major-mode">
-      <title>A Major Mode for Nix Expressions</title>
-      <para>
-        Of interest may be <varname>melpaPackages.nix-mode</varname>,
-        which provides syntax highlighting for the Nix language. This is
-        particularly convenient if you regularly edit Nix files.
-      </para>
-    </section>
-    <section xml:id="module-services-emacs-man-pages">
-      <title>Accessing man pages</title>
-      <para>
-        You can use <literal>woman</literal> to get completion of all
-        available man pages. For example, type
-        <literal>M-x woman &lt;RET&gt; nixos-rebuild &lt;RET&gt;.</literal>
-      </para>
-    </section>
-    <section xml:id="sec-emacs-docbook-xml">
-      <title>Editing DocBook 5 XML Documents</title>
-      <para>
-        Emacs includes
-        <link xlink:href="https://www.gnu.org/software/emacs/manual/html_node/nxml-mode/Introduction.html">nXML</link>,
-        a major-mode for validating and editing XML documents. When
-        editing DocBook 5.0 documents, such as
-        <link linkend="book-nixos-manual">this one</link>, nXML needs to
-        be configured with the relevant schema, which is not included.
-      </para>
-      <para>
-        To install the DocBook 5.0 schemas, either add
-        <varname>pkgs.docbook5</varname> to
-        <xref linkend="opt-environment.systemPackages" />
-        (<link linkend="sec-declarative-package-mgmt">NixOS</link>), or
-        run <literal>nix-env -f '&lt;nixpkgs&gt;' -iA docbook5</literal>
-        (<link linkend="sec-ad-hoc-packages">Nix</link>).
-      </para>
-      <para>
-        Then customize the variable
-        <varname>rng-schema-locating-files</varname> to include
-        <filename>~/.emacs.d/schemas.xml</filename> and put the
-        following text into that file:
-        <anchor xml:id="ex-emacs-docbook-xml" />
-      </para>
-      <programlisting language="xml">
-&lt;?xml version=&quot;1.0&quot;?&gt;
-&lt;!--
-  To let emacs find this file, evaluate:
-  (add-to-list 'rng-schema-locating-files &quot;~/.emacs.d/schemas.xml&quot;)
---&gt;
-&lt;locatingRules xmlns=&quot;http://thaiopensource.com/ns/locating-rules/1.0&quot;&gt;
-  &lt;!--
-    Use this variation if pkgs.docbook5 is added to environment.systemPackages
-  --&gt;
-  &lt;namespace ns=&quot;http://docbook.org/ns/docbook&quot;
-             uri=&quot;/run/current-system/sw/share/xml/docbook-5.0/rng/docbookxi.rnc&quot;/&gt;
-  &lt;!--
-    Use this variation if installing schema with &quot;nix-env -iA pkgs.docbook5&quot;.
-  &lt;namespace ns=&quot;http://docbook.org/ns/docbook&quot;
-             uri=&quot;../.nix-profile/share/xml/docbook-5.0/rng/docbookxi.rnc&quot;/&gt;
-  --&gt;
-&lt;/locatingRules&gt;
-</programlisting>
-    </section>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/games/freeciv.nix b/nixos/modules/services/games/freeciv.nix
index 8b340bb161a56..f33ea5c08a270 100644
--- a/nixos/modules/services/games/freeciv.nix
+++ b/nixos/modules/services/games/freeciv.nix
@@ -54,7 +54,7 @@ in
             default = 0;
             description = lib.mdDoc "Set debug log level.";
           };
-          options.exit-on-end = mkEnableOption (lib.mdDoc "exit instead of restarting when a game ends.");
+          options.exit-on-end = mkEnableOption (lib.mdDoc "exit instead of restarting when a game ends");
           options.Guests = mkEnableOption (lib.mdDoc "guests to login if auth is enabled");
           options.Newusers = mkEnableOption (lib.mdDoc "new users to login if auth is enabled");
           options.port = mkOption {
diff --git a/nixos/modules/services/hardware/bluetooth.nix b/nixos/modules/services/hardware/bluetooth.nix
index 6453e6968dccb..2a58be51bb023 100644
--- a/nixos/modules/services/hardware/bluetooth.nix
+++ b/nixos/modules/services/hardware/bluetooth.nix
@@ -71,6 +71,29 @@ in
         };
         description = lib.mdDoc "Set configuration for system-wide bluetooth (/etc/bluetooth/main.conf).";
       };
+
+      input = mkOption {
+        type = cfgFmt.type;
+        default = { };
+        example = {
+          General = {
+            IdleTimeout = 30;
+            ClassicBondedOnly = true;
+          };
+        };
+        description = lib.mdDoc "Set configuration for the input service (/etc/bluetooth/input.conf).";
+      };
+
+      network = mkOption {
+        type = cfgFmt.type;
+        default = { };
+        example = {
+          General = {
+            DisableSecurity = true;
+          };
+        };
+        description = lib.mdDoc "Set configuration for the network service (/etc/bluetooth/network.conf).";
+      };
     };
   };
 
@@ -80,6 +103,10 @@ in
     environment.systemPackages = [ package ]
       ++ optional cfg.hsphfpd.enable pkgs.hsphfpd;
 
+    environment.etc."bluetooth/input.conf".source =
+      cfgFmt.generate "input.conf" cfg.input;
+    environment.etc."bluetooth/network.conf".source =
+      cfgFmt.generate "network.conf" cfg.network;
     environment.etc."bluetooth/main.conf".source =
       cfgFmt.generate "main.conf" (recursiveUpdate defaults cfg.settings);
     services.udev.packages = [ package ];
diff --git a/nixos/modules/services/hardware/fwupd.nix b/nixos/modules/services/hardware/fwupd.nix
index a3bb61a6cb0c8..4e7d730d127b7 100644
--- a/nixos/modules/services/hardware/fwupd.nix
+++ b/nixos/modules/services/hardware/fwupd.nix
@@ -18,12 +18,6 @@ let
         fwupd = cfg.daemonSettings;
       };
     };
-    "fwupd/uefi_capsule.conf" = {
-      source = pkgs.writeText "uefi_capsule.conf" ''
-        [uefi_capsule]
-        OverrideESPMountPoint=${config.boot.loader.efi.efiSysMountPoint}
-      '';
-    };
   };
 
   originalEtc =
@@ -127,6 +121,16 @@ in {
                 List of plugins to be disabled.
               '';
             };
+
+            EspLocation = mkOption {
+              type = types.path;
+              default = config.boot.loader.efi.efiSysMountPoint;
+              defaultText = lib.literalExpression "config.boot.loader.efi.efiSysMountPoint";
+              description = lib.mdDoc ''
+                The EFI system partition (ESP) path used if UDisks is not available
+                or if this partition is not mounted at /boot/efi, /boot, or /efi
+              '';
+            };
           };
         };
         default = {};
@@ -147,7 +151,10 @@ in {
   ###### implementation
   config = mkIf cfg.enable {
     # Disable test related plug-ins implicitly so that users do not have to care about them.
-    services.fwupd.daemonSettings.DisabledPlugins = cfg.package.defaultDisabledPlugins;
+    services.fwupd.daemonSettings = {
+      DisabledPlugins = cfg.package.defaultDisabledPlugins;
+      EspLocation = config.boot.loader.efi.efiSysMountPoint;
+    };
 
     environment.systemPackages = [ cfg.package ];
 
diff --git a/nixos/modules/services/hardware/kanata.nix b/nixos/modules/services/hardware/kanata.nix
index 84265eb8f947c..bb730037277b8 100644
--- a/nixos/modules/services/hardware/kanata.nix
+++ b/nixos/modules/services/hardware/kanata.nix
@@ -8,19 +8,9 @@ let
   keyboard = {
     options = {
       devices = mkOption {
-        type = types.addCheck (types.listOf types.str)
-          (devices: (length devices) > 0);
+        type = types.listOf types.str;
         example = [ "/dev/input/by-id/usb-0000_0000-event-kbd" ];
-        # TODO replace note with tip, which has not been implemented yet in
-        # nixos/lib/make-options-doc/mergeJSON.py
-        description = mdDoc ''
-          Paths to keyboard devices.
-
-          ::: {.note}
-          To avoid unnecessary triggers of the service unit, unplug devices in
-          the order of the list.
-          :::
-        '';
+        description = mdDoc "Paths to keyboard devices.";
       };
       config = mkOption {
         type = types.lines;
@@ -44,8 +34,10 @@ let
             cap (tap-hold 100 100 caps lctl))
         '';
         description = mdDoc ''
-          Configuration other than `defcfg`. See [example config
-          files](https://github.com/jtroo/kanata) for more information.
+          Configuration other than `defcfg`.
+
+          See [example config files](https://github.com/jtroo/kanata)
+          for more information.
         '';
       };
       extraDefCfg = mkOption {
@@ -53,8 +45,12 @@ let
         default = "";
         example = "danger-enable-cmd yes";
         description = mdDoc ''
-          Configuration of `defcfg` other than `linux-dev`. See [example
-          config files](https://github.com/jtroo/kanata) for more information.
+          Configuration of `defcfg` other than `linux-dev` (generated
+          from the devices option) and
+          `linux-continue-if-no-devs-found` (hardcoded to be yes).
+
+          See [example config files](https://github.com/jtroo/kanata)
+          for more information.
         '';
       };
       extraArgs = mkOption {
@@ -67,8 +63,7 @@ let
         default = null;
         example = 6666;
         description = mdDoc ''
-          Port to run the notification server on. `null` will not run the
-          server.
+          Port to run the TCP server on. `null` will not run the server.
         '';
       };
     };
@@ -76,28 +71,23 @@ let
 
   mkName = name: "kanata-${name}";
 
-  mkDevices = devices: concatStringsSep ":" devices;
+  mkDevices = devices:
+    optionalString ((length devices) > 0) "linux-dev ${concatStringsSep ":" devices}";
 
   mkConfig = name: keyboard: pkgs.writeText "${mkName name}-config.kdb" ''
     (defcfg
       ${keyboard.extraDefCfg}
-      linux-dev ${mkDevices keyboard.devices})
+      ${mkDevices keyboard.devices}
+      linux-continue-if-no-devs-found yes)
 
     ${keyboard.config}
   '';
 
   mkService = name: keyboard: nameValuePair (mkName name) {
-    description = "kanata for ${mkDevices keyboard.devices}";
-
-    # Because path units are used to activate service units, which
-    # will start the old stopped services during "nixos-rebuild
-    # switch", stopIfChanged here is a workaround to make sure new
-    # services are running after "nixos-rebuild switch".
-    stopIfChanged = false;
-
+    wantedBy = [ "multi-user.target" ];
     serviceConfig = {
       ExecStart = ''
-        ${cfg.package}/bin/kanata \
+        ${getExe cfg.package} \
           --cfg ${mkConfig name keyboard} \
           --symlink-path ''${RUNTIME_DIRECTORY}/${name} \
           ${optionalString (keyboard.port != null) "--port ${toString keyboard.port}"} \
@@ -146,37 +136,10 @@ let
       UMask = "0077";
     };
   };
-
-  mkPathName = i: name: "${mkName name}-${toString i}";
-
-  mkPath = name: n: i: device:
-    nameValuePair (mkPathName i name) {
-      description =
-        "${toString (i+1)}/${toString n} kanata trigger for ${name}, watching ${device}";
-      wantedBy = optional (i == 0) "multi-user.target";
-      pathConfig = {
-        PathExists = device;
-        # (ab)use systemd.path to construct a trigger chain so that the
-        # service unit is only started when all paths exist
-        # however, manual of systemd.path says Unit's suffix is not ".path"
-        Unit =
-          if (i + 1) == n
-          then "${mkName name}.service"
-          else "${mkPathName (i + 1) name}.path";
-      };
-      unitConfig.StopPropagatedFrom = optional (i > 0) "${mkName name}.service";
-    };
-
-  mkPaths = name: keyboard:
-    let
-      n = length keyboard.devices;
-    in
-    imap0 (mkPath name n) keyboard.devices
-  ;
 in
 {
   options.services.kanata = {
-    enable = mkEnableOption (lib.mdDoc "kanata");
+    enable = mkEnableOption (mdDoc "kanata");
     package = mkOption {
       type = types.package;
       default = pkgs.kanata;
@@ -201,14 +164,7 @@ in
   config = mkIf cfg.enable {
     hardware.uinput.enable = true;
 
-    systemd = {
-      paths = trivial.pipe cfg.keyboards [
-        (mapAttrsToList mkPaths)
-        concatLists
-        listToAttrs
-      ];
-      services = mapAttrs' mkService cfg.keyboards;
-    };
+    systemd.services = mapAttrs' mkService cfg.keyboards;
   };
 
   meta.maintainers = with maintainers; [ linj ];
diff --git a/nixos/modules/services/hardware/throttled.nix b/nixos/modules/services/hardware/throttled.nix
index 2d801a7e838ff..afca24d976e1a 100644
--- a/nixos/modules/services/hardware/throttled.nix
+++ b/nixos/modules/services/hardware/throttled.nix
@@ -20,7 +20,7 @@ in {
   config = mkIf cfg.enable {
     systemd.packages = [ pkgs.throttled ];
     # The upstream package has this in Install, but that's not enough, see the NixOS manual
-    systemd.services.lenovo_fix.wantedBy = [ "multi-user.target" ];
+    systemd.services.throttled.wantedBy = [ "multi-user.target" ];
 
     environment.etc."throttled.conf".source =
       if cfg.extraConfig != ""
diff --git a/nixos/modules/services/hardware/trezord.nix b/nixos/modules/services/hardware/trezord.nix
index 70c1fd09860e8..b2217fc971241 100644
--- a/nixos/modules/services/hardware/trezord.nix
+++ b/nixos/modules/services/hardware/trezord.nix
@@ -8,7 +8,7 @@ in {
   ### docs
 
   meta = {
-    doc = ./trezord.xml;
+    doc = ./trezord.md;
   };
 
   ### interface
diff --git a/nixos/modules/services/hardware/trezord.xml b/nixos/modules/services/hardware/trezord.xml
deleted file mode 100644
index 1ba9dc1f18873..0000000000000
--- a/nixos/modules/services/hardware/trezord.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="trezor">
-  <title>Trezor</title>
-  <para>
-    Trezor is an open-source cryptocurrency hardware wallet and security
-    token allowing secure storage of private keys.
-  </para>
-  <para>
-    It offers advanced features such U2F two-factor authorization, SSH
-    login through
-    <link xlink:href="https://wiki.trezor.io/Apps:SSH_agent">Trezor SSH
-    agent</link>,
-    <link xlink:href="https://wiki.trezor.io/GPG">GPG</link> and a
-    <link xlink:href="https://wiki.trezor.io/Trezor_Password_Manager">password
-    manager</link>. For more information, guides and documentation, see
-    <link xlink:href="https://wiki.trezor.io">https://wiki.trezor.io</link>.
-  </para>
-  <para>
-    To enable Trezor support, add the following to your
-    <filename>configuration.nix</filename>:
-  </para>
-  <programlisting>
-services.trezord.enable = true;
-</programlisting>
-  <para>
-    This will add all necessary udev rules and start Trezor Bridge.
-  </para>
-</chapter>
diff --git a/nixos/modules/services/hardware/udisks2.nix b/nixos/modules/services/hardware/udisks2.nix
index 7368845dafd55..c53dbf477742d 100644
--- a/nixos/modules/services/hardware/udisks2.nix
+++ b/nixos/modules/services/hardware/udisks2.nix
@@ -1,10 +1,9 @@
 # Udisks daemon.
-
 { config, lib, pkgs, ... }:
-
 with lib;
 
 let
+  cfg = config.services.udisks2;
   settingsFormat = pkgs.formats.ini {
     listToValue = concatMapStringsSep "," (generators.mkValueStringDefault {});
   };
@@ -19,7 +18,17 @@ in
 
     services.udisks2 = {
 
-      enable = mkEnableOption (lib.mdDoc "udisks2, a DBus service that allows applications to query and manipulate storage devices.");
+      enable = mkEnableOption (mdDoc "udisks2, a DBus service that allows applications to query and manipulate storage devices");
+
+      mountOnMedia = mkOption {
+        type = types.bool;
+        default = false;
+        description = mdDoc ''
+          When enabled, instructs udisks2 to mount removable drives under `/media/` directory, instead of the
+          default, ACL-controlled `/run/media/$USER/`. Since `/media/` is not mounted as tmpfs by default, it
+          requires cleanup to get rid of stale mountpoints; enabling this option will take care of this at boot.
+        '';
+      };
 
       settings = mkOption rec {
         type = types.attrsOf settingsFormat.type;
@@ -44,7 +53,7 @@ in
           };
         };
         '';
-        description = lib.mdDoc ''
+        description = mdDoc ''
           Options passed to udisksd.
           See [here](http://manpages.ubuntu.com/manpages/latest/en/man5/udisks2.conf.5.html) and
           drive configuration in [here](http://manpages.ubuntu.com/manpages/latest/en/man8/udisks.8.html) for supported options.
@@ -73,10 +82,15 @@ in
 
     services.dbus.packages = [ pkgs.udisks2 ];
 
-    systemd.tmpfiles.rules = [ "d /var/lib/udisks2 0755 root root -" ];
+    systemd.tmpfiles.rules = [ "d /var/lib/udisks2 0755 root root -" ]
+      ++ optional cfg.mountOnMedia "D! /media 0755 root root -";
 
     services.udev.packages = [ pkgs.udisks2 ];
 
+    services.udev.extraRules = optionalString cfg.mountOnMedia ''
+      ENV{ID_FS_USAGE}=="filesystem", ENV{UDISKS_FILESYSTEM_SHARED}="1"
+    '';
+
     systemd.packages = [ pkgs.udisks2 ];
   };
 
diff --git a/nixos/modules/services/home-automation/home-assistant.nix b/nixos/modules/services/home-automation/home-assistant.nix
index fa06e5391bbfe..6adc58ec58ec4 100644
--- a/nixos/modules/services/home-automation/home-assistant.nix
+++ b/nixos/modules/services/home-automation/home-assistant.nix
@@ -409,6 +409,7 @@ in {
         (optionalString (cfg.config != null) copyConfig) +
         (optionalString (cfg.lovelaceConfig != null) copyLovelaceConfig)
       ;
+      environment.PYTHONPATH = package.pythonPath;
       serviceConfig = let
         # List of capabilities to equip home-assistant with, depending on configured components
         capabilities = lib.unique ([
@@ -438,11 +439,13 @@ in {
           "aranet"
           "bluemaestro"
           "bluetooth"
+          "bluetooth_adapters"
           "bluetooth_le_tracker"
           "bluetooth_tracker"
           "bthome"
           "default_config"
           "eq3btsmart"
+          "eufylife_ble"
           "esphome"
           "fjaraskupan"
           "govee_ble"
@@ -452,8 +455,10 @@ in {
           "led_ble"
           "melnor"
           "moat"
+          "mopeka"
           "oralb"
           "qingping"
+          "ruuvi_gateway"
           "ruuvitag_ble"
           "sensirion_ble"
           "sensorpro"
diff --git a/nixos/modules/services/mail/dovecot.nix b/nixos/modules/services/mail/dovecot.nix
index f6a167572f722..21bafd859c3c2 100644
--- a/nixos/modules/services/mail/dovecot.nix
+++ b/nixos/modules/services/mail/dovecot.nix
@@ -171,11 +171,11 @@ in
   options.services.dovecot2 = {
     enable = mkEnableOption (lib.mdDoc "the dovecot 2.x POP3/IMAP server");
 
-    enablePop3 = mkEnableOption (lib.mdDoc "starting the POP3 listener (when Dovecot is enabled).");
+    enablePop3 = mkEnableOption (lib.mdDoc "starting the POP3 listener (when Dovecot is enabled)");
 
-    enableImap = mkEnableOption (lib.mdDoc "starting the IMAP listener (when Dovecot is enabled).") // { default = true; };
+    enableImap = mkEnableOption (lib.mdDoc "starting the IMAP listener (when Dovecot is enabled)") // { default = true; };
 
-    enableLmtp = mkEnableOption (lib.mdDoc "starting the LMTP listener (when Dovecot is enabled).");
+    enableLmtp = mkEnableOption (lib.mdDoc "starting the LMTP listener (when Dovecot is enabled)");
 
     protocols = mkOption {
       type = types.listOf types.str;
@@ -300,9 +300,9 @@ in
       description = lib.mdDoc "Path to the server's private key.";
     };
 
-    enablePAM = mkEnableOption (lib.mdDoc "creating a own Dovecot PAM service and configure PAM user logins.") // { default = true; };
+    enablePAM = mkEnableOption (lib.mdDoc "creating a own Dovecot PAM service and configure PAM user logins") // { default = true; };
 
-    enableDHE = mkEnableOption (lib.mdDoc "enable ssl_dh and generation of primes for the key exchange.") // { default = true; };
+    enableDHE = mkEnableOption (lib.mdDoc "enable ssl_dh and generation of primes for the key exchange") // { default = true; };
 
     sieveScripts = mkOption {
       type = types.attrsOf types.path;
@@ -310,7 +310,7 @@ in
       description = lib.mdDoc "Sieve scripts to be executed. Key is a sequence, e.g. 'before2', 'after' etc.";
     };
 
-    showPAMFailure = mkEnableOption (lib.mdDoc "showing the PAM failure message on authentication error (useful for OTPW).");
+    showPAMFailure = mkEnableOption (lib.mdDoc "showing the PAM failure message on authentication error (useful for OTPW)");
 
     mailboxes = mkOption {
       type = with types; coercedTo
@@ -326,7 +326,7 @@ in
       description = lib.mdDoc "Configure mailboxes and auto create or subscribe them.";
     };
 
-    enableQuota = mkEnableOption (lib.mdDoc "the dovecot quota service.");
+    enableQuota = mkEnableOption (lib.mdDoc "the dovecot quota service");
 
     quotaPort = mkOption {
       type = types.str;
diff --git a/nixos/modules/services/mail/maddy.nix b/nixos/modules/services/mail/maddy.nix
index eeb113e204c6e..5f3a9b56292d2 100644
--- a/nixos/modules/services/mail/maddy.nix
+++ b/nixos/modules/services/mail/maddy.nix
@@ -223,22 +223,59 @@ in {
         '';
       };
 
+      ensureAccounts = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = lib.mdDoc ''
+          List of IMAP accounts which get automatically created. Note that for
+          a complete setup, user credentials for these accounts are required too
+          and can be created using the command `maddyctl creds`.
+          This option does not delete accounts which are not (anymore) listed.
+        '';
+        example = [
+          "user1@localhost"
+          "user2@localhost"
+        ];
+      };
+
     };
   };
 
   config = mkIf cfg.enable {
 
     systemd = {
+
       packages = [ pkgs.maddy ];
-      services.maddy = {
-        serviceConfig = {
-          User = cfg.user;
-          Group = cfg.group;
-          StateDirectory = [ "maddy" ];
+      services = {
+        maddy = {
+          serviceConfig = {
+            User = cfg.user;
+            Group = cfg.group;
+            StateDirectory = [ "maddy" ];
+          };
+          restartTriggers = [ config.environment.etc."maddy/maddy.conf".source ];
+          wantedBy = [ "multi-user.target" ];
+        };
+        maddy-ensure-accounts = {
+          script = ''
+            ${optionalString (cfg.ensureAccounts != []) ''
+              ${concatMapStrings (account: ''
+                if ! ${pkgs.maddy}/bin/maddyctl imap-acct list | grep "${account}"; then
+                  ${pkgs.maddy}/bin/maddyctl imap-acct create ${account}
+                fi
+              '') cfg.ensureAccounts}
+            ''}
+          '';
+          serviceConfig = {
+            Type = "oneshot";
+            User= "maddy";
+          };
+          after = [ "maddy.service" ];
+          wantedBy = [ "multi-user.target" ];
         };
-        restartTriggers = [ config.environment.etc."maddy/maddy.conf".source ];
-        wantedBy = [ "multi-user.target" ];
+
       };
+
     };
 
     environment.etc."maddy/maddy.conf" = {
diff --git a/nixos/modules/services/mail/mailman.nix b/nixos/modules/services/mail/mailman.nix
index 2adc7427abf49..9273f71db7d56 100644
--- a/nixos/modules/services/mail/mailman.nix
+++ b/nixos/modules/services/mail/mailman.nix
@@ -642,7 +642,7 @@ in {
 
   meta = {
     maintainers = with lib.maintainers; [ lheckemann qyliss ma27 ];
-    doc = ./mailman.xml;
+    doc = ./mailman.md;
   };
 
 }
diff --git a/nixos/modules/services/mail/mailman.xml b/nixos/modules/services/mail/mailman.xml
deleted file mode 100644
index 23b0d0b7da4c8..0000000000000
--- a/nixos/modules/services/mail/mailman.xml
+++ /dev/null
@@ -1,112 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-mailman">
-  <title>Mailman</title>
-  <para>
-    <link xlink:href="https://www.list.org">Mailman</link> is free
-    software for managing electronic mail discussion and e-newsletter
-    lists. Mailman and its web interface can be configured using the
-    corresponding NixOS module. Note that this service is best used with
-    an existing, securely configured Postfix setup, as it does not
-    automatically configure this.
-  </para>
-  <section xml:id="module-services-mailman-basic-usage">
-    <title>Basic usage with Postfix</title>
-    <para>
-      For a basic configuration with Postfix as the MTA, the following
-      settings are suggested:
-    </para>
-    <programlisting>
-{ config, ... }: {
-  services.postfix = {
-    enable = true;
-    relayDomains = [&quot;hash:/var/lib/mailman/data/postfix_domains&quot;];
-    sslCert = config.security.acme.certs.&quot;lists.example.org&quot;.directory + &quot;/full.pem&quot;;
-    sslKey = config.security.acme.certs.&quot;lists.example.org&quot;.directory + &quot;/key.pem&quot;;
-    config = {
-      transport_maps = [&quot;hash:/var/lib/mailman/data/postfix_lmtp&quot;];
-      local_recipient_maps = [&quot;hash:/var/lib/mailman/data/postfix_lmtp&quot;];
-    };
-  };
-  services.mailman = {
-    enable = true;
-    serve.enable = true;
-    hyperkitty.enable = true;
-    webHosts = [&quot;lists.example.org&quot;];
-    siteOwner = &quot;mailman@example.org&quot;;
-  };
-  services.nginx.virtualHosts.&quot;lists.example.org&quot;.enableACME = true;
-  networking.firewall.allowedTCPPorts = [ 25 80 443 ];
-}
-</programlisting>
-    <para>
-      DNS records will also be required:
-    </para>
-    <itemizedlist spacing="compact">
-      <listitem>
-        <para>
-          <literal>AAAA</literal> and <literal>A</literal> records
-          pointing to the host in question, in order for browsers to be
-          able to discover the address of the web server;
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          An <literal>MX</literal> record pointing to a domain name at
-          which the host is reachable, in order for other mail servers
-          to be able to deliver emails to the mailing lists it hosts.
-        </para>
-      </listitem>
-    </itemizedlist>
-    <para>
-      After this has been done and appropriate DNS records have been set
-      up, the Postorius mailing list manager and the Hyperkitty archive
-      browser will be available at https://lists.example.org/. Note that
-      this setup is not sufficient to deliver emails to most email
-      providers nor to avoid spam – a number of additional measures for
-      authenticating incoming and outgoing mails, such as SPF, DMARC and
-      DKIM are necessary, but outside the scope of the Mailman module.
-    </para>
-  </section>
-  <section xml:id="module-services-mailman-other-mtas">
-    <title>Using with other MTAs</title>
-    <para>
-      Mailman also supports other MTA, though with a little bit more
-      configuration. For example, to use Mailman with Exim, you can use
-      the following settings:
-    </para>
-    <programlisting>
-{ config, ... }: {
-  services = {
-    mailman = {
-      enable = true;
-      siteOwner = &quot;mailman@example.org&quot;;
-      enablePostfix = false;
-      settings.mta = {
-        incoming = &quot;mailman.mta.exim4.LMTP&quot;;
-        outgoing = &quot;mailman.mta.deliver.deliver&quot;;
-        lmtp_host = &quot;localhost&quot;;
-        lmtp_port = &quot;8024&quot;;
-        smtp_host = &quot;localhost&quot;;
-        smtp_port = &quot;25&quot;;
-        configuration = &quot;python:mailman.config.exim4&quot;;
-      };
-    };
-    exim = {
-      enable = true;
-      # You can configure Exim in a separate file to reduce configuration.nix clutter
-      config = builtins.readFile ./exim.conf;
-    };
-  };
-}
-</programlisting>
-    <para>
-      The exim config needs some special additions to work with Mailman.
-      Currently NixOS can’t manage Exim config with such granularity.
-      Please refer to
-      <link xlink:href="https://mailman.readthedocs.io/en/latest/src/mailman/docs/mta.html">Mailman
-      documentation</link> for more info on configuring Mailman for
-      working with Exim.
-    </para>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/mail/postfix.nix b/nixos/modules/services/mail/postfix.nix
index d01734d61e87e..852340c05aa7a 100644
--- a/nixos/modules/services/mail/postfix.nix
+++ b/nixos/modules/services/mail/postfix.nix
@@ -809,7 +809,7 @@ in
       // optionalAttrs (cfg.relayHost != "") { relayhost = if cfg.lookupMX
                                                            then "${cfg.relayHost}:${toString cfg.relayPort}"
                                                            else "[${cfg.relayHost}]:${toString cfg.relayPort}"; }
-      // optionalAttrs config.networking.enableIPv6 { inet_protocols = mkDefault "all"; }
+      // optionalAttrs (!config.networking.enableIPv6) { inet_protocols = mkDefault "ipv4"; }
       // optionalAttrs (cfg.networks != null) { mynetworks = cfg.networks; }
       // optionalAttrs (cfg.networksStyle != "") { mynetworks_style = cfg.networksStyle; }
       // optionalAttrs (cfg.hostname != "") { myhostname = cfg.hostname; }
diff --git a/nixos/modules/services/mail/roundcube.nix b/nixos/modules/services/mail/roundcube.nix
index e05820fb87cf1..95dc2f6aa2c92 100644
--- a/nixos/modules/services/mail/roundcube.nix
+++ b/nixos/modules/services/mail/roundcube.nix
@@ -150,9 +150,13 @@ in
             root = cfg.package;
             index = "index.php";
             extraConfig = ''
-              location ~* \.php$ {
+              location ~* \.php(/|$) {
                 fastcgi_split_path_info ^(.+\.php)(/.+)$;
                 fastcgi_pass unix:${fpm.socket};
+
+                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+                fastcgi_param PATH_INFO       $fastcgi_path_info;
+
                 include ${config.services.nginx.package}/conf/fastcgi_params;
                 include ${pkgs.nginx}/conf/fastcgi.conf;
               }
diff --git a/nixos/modules/services/matrix/appservice-discord.nix b/nixos/modules/services/matrix/appservice-discord.nix
index 15f0f0cc0cdbf..f579c2529c0a5 100644
--- a/nixos/modules/services/matrix/appservice-discord.nix
+++ b/nixos/modules/services/matrix/appservice-discord.nix
@@ -5,7 +5,6 @@ with lib;
 let
   dataDir = "/var/lib/matrix-appservice-discord";
   registrationFile = "${dataDir}/discord-registration.yaml";
-  appDir = "${pkgs.matrix-appservice-discord}/${pkgs.matrix-appservice-discord.passthru.nodeAppDir}";
   cfg = config.services.matrix-appservice-discord;
   opt = options.services.matrix-appservice-discord;
   # TODO: switch to configGen.json once RFC42 is implemented
@@ -16,6 +15,15 @@ in {
     services.matrix-appservice-discord = {
       enable = mkEnableOption (lib.mdDoc "a bridge between Matrix and Discord");
 
+      package = mkOption {
+        type = types.package;
+        default = pkgs.matrix-appservice-discord;
+        defaultText = literalExpression "pkgs.matrix-appservice-discord";
+        description = lib.mdDoc ''
+          Which package of matrix-appservice-discord to use.
+        '';
+      };
+
       settings = mkOption rec {
         # TODO: switch to types.config.json as prescribed by RFC42 once it's implemented
         type = types.attrs;
@@ -114,7 +122,7 @@ in {
 
       preStart = ''
         if [ ! -f '${registrationFile}' ]; then
-          ${pkgs.matrix-appservice-discord}/bin/matrix-appservice-discord \
+          ${cfg.package}/bin/matrix-appservice-discord \
             --generate-registration \
             --url=${escapeShellArg cfg.url} \
             ${optionalString (cfg.localpart != null) "--localpart=${escapeShellArg cfg.localpart}"} \
@@ -135,13 +143,13 @@ in {
 
         DynamicUser = true;
         PrivateTmp = true;
-        WorkingDirectory = appDir;
+        WorkingDirectory = "${cfg.package}/${cfg.package.passthru.nodeAppDir}";
         StateDirectory = baseNameOf dataDir;
         UMask = "0027";
         EnvironmentFile = cfg.environmentFile;
 
         ExecStart = ''
-          ${pkgs.matrix-appservice-discord}/bin/matrix-appservice-discord \
+          ${cfg.package}/bin/matrix-appservice-discord \
             --file='${registrationFile}' \
             --config='${settingsFile}' \
             --port='${toString cfg.port}'
diff --git a/nixos/modules/services/matrix/mautrix-facebook.nix b/nixos/modules/services/matrix/mautrix-facebook.nix
index e74f25df764db..e995f1aecf27d 100644
--- a/nixos/modules/services/matrix/mautrix-facebook.nix
+++ b/nixos/modules/services/matrix/mautrix-facebook.nix
@@ -96,7 +96,7 @@ in {
         type = types.nullOr types.path;
         default = null;
         description = lib.mdDoc ''
-          File containing environment variables to be passed to the mautrix-telegram service.
+          File containing environment variables to be passed to the mautrix-facebook service.
 
           Any config variable can be overridden by setting `MAUTRIX_FACEBOOK_SOME_KEY` to override the `some.key` variable.
         '';
diff --git a/nixos/modules/services/matrix/mautrix-telegram.nix b/nixos/modules/services/matrix/mautrix-telegram.nix
index 5a632fd27e80d..b64cc71d98735 100644
--- a/nixos/modules/services/matrix/mautrix-telegram.nix
+++ b/nixos/modules/services/matrix/mautrix-telegram.nix
@@ -137,7 +137,7 @@ in {
       wantedBy = [ "multi-user.target" ];
       wants = [ "network-online.target" ] ++ cfg.serviceDependencies;
       after = [ "network-online.target" ] ++ cfg.serviceDependencies;
-      path = [ pkgs.lottieconverter ];
+      path = [ pkgs.lottieconverter pkgs.ffmpeg-full ];
 
       # mautrix-telegram tries to generate a dotfile in the home directory of
       # the running user if using a postgresql database:
diff --git a/nixos/modules/services/matrix/mjolnir.nix b/nixos/modules/services/matrix/mjolnir.nix
index cbf7b93329d7a..b6a3e5e8c7308 100644
--- a/nixos/modules/services/matrix/mjolnir.nix
+++ b/nixos/modules/services/matrix/mjolnir.nix
@@ -236,7 +236,7 @@ in
   };
 
   meta = {
-    doc = ./mjolnir.xml;
+    doc = ./mjolnir.md;
     maintainers = with maintainers; [ jojosch ];
   };
 }
diff --git a/nixos/modules/services/matrix/mjolnir.xml b/nixos/modules/services/matrix/mjolnir.xml
deleted file mode 100644
index 5bd2919e437c6..0000000000000
--- a/nixos/modules/services/matrix/mjolnir.xml
+++ /dev/null
@@ -1,148 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-mjolnir">
-  <title>Mjolnir (Matrix Moderation Tool)</title>
-  <para>
-    This chapter will show you how to set up your own, self-hosted
-    <link xlink:href="https://github.com/matrix-org/mjolnir">Mjolnir</link>
-    instance.
-  </para>
-  <para>
-    As an all-in-one moderation tool, it can protect your server from
-    malicious invites, spam messages, and whatever else you don’t want.
-    In addition to server-level protection, Mjolnir is great for
-    communities wanting to protect their rooms without having to use
-    their personal accounts for moderation.
-  </para>
-  <para>
-    The bot by default includes support for bans, redactions, anti-spam,
-    server ACLs, room directory changes, room alias transfers, account
-    deactivation, room shutdown, and more.
-  </para>
-  <para>
-    See the
-    <link xlink:href="https://github.com/matrix-org/mjolnir#readme">README</link>
-    page and the
-    <link xlink:href="https://github.com/matrix-org/mjolnir/blob/main/docs/moderators.md">Moderator’s
-    guide</link> for additional instructions on how to setup and use
-    Mjolnir.
-  </para>
-  <para>
-    For <link linkend="opt-services.mjolnir.settings">additional
-    settings</link> see
-    <link xlink:href="https://github.com/matrix-org/mjolnir/blob/main/config/default.yaml">the
-    default configuration</link>.
-  </para>
-  <section xml:id="module-services-mjolnir-setup">
-    <title>Mjolnir Setup</title>
-    <para>
-      First create a new Room which will be used as a management room
-      for Mjolnir. In this room, Mjolnir will log possible errors and
-      debugging information. You’ll need to set this Room-ID in
-      <link linkend="opt-services.mjolnir.managementRoom">services.mjolnir.managementRoom</link>.
-    </para>
-    <para>
-      Next, create a new user for Mjolnir on your homeserver, if not
-      present already.
-    </para>
-    <para>
-      The Mjolnir Matrix user expects to be free of any rate limiting.
-      See
-      <link xlink:href="https://github.com/matrix-org/synapse/issues/6286">Synapse
-      #6286</link> for an example on how to achieve this.
-    </para>
-    <para>
-      If you want Mjolnir to be able to deactivate users, move room
-      aliases, shutdown rooms, etc. you’ll need to make the Mjolnir user
-      a Matrix server admin.
-    </para>
-    <para>
-      Now invite the Mjolnir user to the management room.
-    </para>
-    <para>
-      It is recommended to use
-      <link xlink:href="https://github.com/matrix-org/pantalaimon">Pantalaimon</link>,
-      so your management room can be encrypted. This also applies if you
-      are looking to moderate an encrypted room.
-    </para>
-    <para>
-      To enable the Pantalaimon E2E Proxy for mjolnir, enable
-      <link linkend="opt-services.mjolnir.pantalaimon.enable">services.mjolnir.pantalaimon</link>.
-      This will autoconfigure a new Pantalaimon instance, which will
-      connect to the homeserver set in
-      <link linkend="opt-services.mjolnir.homeserverUrl">services.mjolnir.homeserverUrl</link>
-      and Mjolnir itself will be configured to connect to the new
-      Pantalaimon instance.
-    </para>
-    <programlisting>
-{
-  services.mjolnir = {
-    enable = true;
-    homeserverUrl = &quot;https://matrix.domain.tld&quot;;
-    pantalaimon = {
-       enable = true;
-       username = &quot;mjolnir&quot;;
-       passwordFile = &quot;/run/secrets/mjolnir-password&quot;;
-    };
-    protectedRooms = [
-      &quot;https://matrix.to/#/!xxx:domain.tld&quot;
-    ];
-    managementRoom = &quot;!yyy:domain.tld&quot;;
-  };
-}
-</programlisting>
-    <section xml:id="module-services-mjolnir-setup-ems">
-      <title>Element Matrix Services (EMS)</title>
-      <para>
-        If you are using a managed
-        <link xlink:href="https://ems.element.io/"><quote>Element Matrix
-        Services (EMS)</quote></link> server, you will need to consent
-        to the terms and conditions. Upon startup, an error log entry
-        with a URL to the consent page will be generated.
-      </para>
-    </section>
-  </section>
-  <section xml:id="module-services-mjolnir-matrix-synapse-antispam">
-    <title>Synapse Antispam Module</title>
-    <para>
-      A Synapse module is also available to apply the same rulesets the
-      bot uses across an entire homeserver.
-    </para>
-    <para>
-      To use the Antispam Module, add
-      <literal>matrix-synapse-plugins.matrix-synapse-mjolnir-antispam</literal>
-      to the Synapse plugin list and enable the
-      <literal>mjolnir.Module</literal> module.
-    </para>
-    <programlisting>
-{
-  services.matrix-synapse = {
-    plugins = with pkgs; [
-      matrix-synapse-plugins.matrix-synapse-mjolnir-antispam
-    ];
-    extraConfig = ''
-      modules:
-        - module: mjolnir.Module
-          config:
-            # Prevent servers/users in the ban lists from inviting users on this
-            # server to rooms. Default true.
-            block_invites: true
-            # Flag messages sent by servers/users in the ban lists as spam. Currently
-            # this means that spammy messages will appear as empty to users. Default
-            # false.
-            block_messages: false
-            # Remove users from the user directory search by filtering matrix IDs and
-            # display names by the entries in the user ban list. Default false.
-            block_usernames: false
-            # The room IDs of the ban lists to honour. Unlike other parts of Mjolnir,
-            # this list cannot be room aliases or permalinks. This server is expected
-            # to already be joined to the room - Mjolnir will not automatically join
-            # these rooms.
-            ban_lists:
-              - &quot;!roomid:example.org&quot;
-    '';
-  };
-}
-</programlisting>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/matrix/synapse.md b/nixos/modules/services/matrix/synapse.md
index 22f3bce64a40c..7a9ddf8c9daf3 100644
--- a/nixos/modules/services/matrix/synapse.md
+++ b/nixos/modules/services/matrix/synapse.md
@@ -31,7 +31,7 @@ let
     "m.homeserver".base_url = "https://${fqdn}";
     "m.identity_server" = {};
   };
-  serverConfig."m.server" = "${config.services.matrix-synapse.settings.server_name}:443";
+  serverConfig."m.server" = "${fqdn}:443";
   mkWellKnown = data: ''
     add_header Content-Type application/json;
     add_header Access-Control-Allow-Origin *;
diff --git a/nixos/modules/services/matrix/synapse.nix b/nixos/modules/services/matrix/synapse.nix
index 3087d879b9d2b..aee275dab1ec1 100644
--- a/nixos/modules/services/matrix/synapse.nix
+++ b/nixos/modules/services/matrix/synapse.nix
@@ -801,7 +801,7 @@ in {
 
   meta = {
     buildDocsInSandbox = false;
-    doc = ./synapse.xml;
+    doc = ./synapse.md;
     maintainers = teams.matrix.members;
   };
 
diff --git a/nixos/modules/services/matrix/synapse.xml b/nixos/modules/services/matrix/synapse.xml
deleted file mode 100644
index 686aec93ab675..0000000000000
--- a/nixos/modules/services/matrix/synapse.xml
+++ /dev/null
@@ -1,263 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-matrix">
-  <title>Matrix</title>
-  <para>
-    <link xlink:href="https://matrix.org/">Matrix</link> is an open
-    standard for interoperable, decentralised, real-time communication
-    over IP. It can be used to power Instant Messaging, VoIP/WebRTC
-    signalling, Internet of Things communication - or anywhere you need
-    a standard HTTP API for publishing and subscribing to data whilst
-    tracking the conversation history.
-  </para>
-  <para>
-    This chapter will show you how to set up your own, self-hosted
-    Matrix homeserver using the Synapse reference homeserver, and how to
-    serve your own copy of the Element web client. See the
-    <link xlink:href="https://matrix.org/docs/projects/try-matrix-now.html">Try
-    Matrix Now!</link> overview page for links to Element Apps for
-    Android and iOS, desktop clients, as well as bridges to other
-    networks and other projects around Matrix.
-  </para>
-  <section xml:id="module-services-matrix-synapse">
-    <title>Synapse Homeserver</title>
-    <para>
-      <link xlink:href="https://github.com/matrix-org/synapse">Synapse</link>
-      is the reference homeserver implementation of Matrix from the core
-      development team at matrix.org. The following configuration
-      example will set up a synapse server for the
-      <literal>example.org</literal> domain, served from the host
-      <literal>myhostname.example.org</literal>. For more information,
-      please refer to the
-      <link xlink:href="https://matrix-org.github.io/synapse/latest/setup/installation.html">installation
-      instructions of Synapse</link> .
-    </para>
-    <programlisting>
-{ pkgs, lib, config, ... }:
-let
-  fqdn = &quot;${config.networking.hostName}.${config.networking.domain}&quot;;
-  clientConfig = {
-    &quot;m.homeserver&quot;.base_url = &quot;https://${fqdn}&quot;;
-    &quot;m.identity_server&quot; = {};
-  };
-  serverConfig.&quot;m.server&quot; = &quot;${config.services.matrix-synapse.settings.server_name}:443&quot;;
-  mkWellKnown = data: ''
-    add_header Content-Type application/json;
-    add_header Access-Control-Allow-Origin *;
-    return 200 '${builtins.toJSON data}';
-  '';
-in {
-  networking.hostName = &quot;myhostname&quot;;
-  networking.domain = &quot;example.org&quot;;
-  networking.firewall.allowedTCPPorts = [ 80 443 ];
-
-  services.postgresql.enable = true;
-  services.postgresql.initialScript = pkgs.writeText &quot;synapse-init.sql&quot; ''
-    CREATE ROLE &quot;matrix-synapse&quot; WITH LOGIN PASSWORD 'synapse';
-    CREATE DATABASE &quot;matrix-synapse&quot; WITH OWNER &quot;matrix-synapse&quot;
-      TEMPLATE template0
-      LC_COLLATE = &quot;C&quot;
-      LC_CTYPE = &quot;C&quot;;
-  '';
-
-  services.nginx = {
-    enable = true;
-    recommendedTlsSettings = true;
-    recommendedOptimisation = true;
-    recommendedGzipSettings = true;
-    recommendedProxySettings = true;
-    virtualHosts = {
-      # If the A and AAAA DNS records on example.org do not point on the same host as the
-      # records for myhostname.example.org, you can easily move the /.well-known
-      # virtualHost section of the code to the host that is serving example.org, while
-      # the rest stays on myhostname.example.org with no other changes required.
-      # This pattern also allows to seamlessly move the homeserver from
-      # myhostname.example.org to myotherhost.example.org by only changing the
-      # /.well-known redirection target.
-      &quot;${config.networking.domain}&quot; = {
-        enableACME = true;
-        forceSSL = true;
-        # This section is not needed if the server_name of matrix-synapse is equal to
-        # the domain (i.e. example.org from @foo:example.org) and the federation port
-        # is 8448.
-        # Further reference can be found in the docs about delegation under
-        # https://matrix-org.github.io/synapse/latest/delegate.html
-        locations.&quot;= /.well-known/matrix/server&quot;.extraConfig = mkWellKnown serverConfig;
-        # This is usually needed for homeserver discovery (from e.g. other Matrix clients).
-        # Further reference can be found in the upstream docs at
-        # https://spec.matrix.org/latest/client-server-api/#getwell-knownmatrixclient
-        locations.&quot;= /.well-known/matrix/client&quot;.extraConfig = mkWellKnown clientConfig;
-      };
-      &quot;${fqdn}&quot; = {
-        enableACME = true;
-        forceSSL = true;
-        # It's also possible to do a redirect here or something else, this vhost is not
-        # needed for Matrix. It's recommended though to *not put* element
-        # here, see also the section about Element.
-        locations.&quot;/&quot;.extraConfig = ''
-          return 404;
-        '';
-        # Forward all Matrix API calls to the synapse Matrix homeserver. A trailing slash
-        # *must not* be used here.
-        locations.&quot;/_matrix&quot;.proxyPass = &quot;http://[::1]:8008&quot;;
-        # Forward requests for e.g. SSO and password-resets.
-        locations.&quot;/_synapse/client&quot;.proxyPass = &quot;http://[::1]:8008&quot;;
-      };
-    };
-  };
-
-  services.matrix-synapse = {
-    enable = true;
-    settings.server_name = config.networking.domain;
-    settings.listeners = [
-      { port = 8008;
-        bind_addresses = [ &quot;::1&quot; ];
-        type = &quot;http&quot;;
-        tls = false;
-        x_forwarded = true;
-        resources = [ {
-          names = [ &quot;client&quot; &quot;federation&quot; ];
-          compress = true;
-        } ];
-      }
-    ];
-  };
-}
-</programlisting>
-  </section>
-  <section xml:id="module-services-matrix-register-users">
-    <title>Registering Matrix users</title>
-    <para>
-      If you want to run a server with public registration by anybody,
-      you can then enable
-      <literal>services.matrix-synapse.settings.enable_registration = true;</literal>.
-      Otherwise, or you can generate a registration secret with
-      <command>pwgen -s 64 1</command> and set it with
-      <xref linkend="opt-services.matrix-synapse.settings.registration_shared_secret" />.
-      To create a new user or admin, run the following after you have
-      set the secret and have rebuilt NixOS:
-    </para>
-    <programlisting>
-$ nix-shell -p matrix-synapse
-$ register_new_matrix_user -k your-registration-shared-secret http://localhost:8008
-New user localpart: your-username
-Password:
-Confirm password:
-Make admin [no]:
-Success!
-</programlisting>
-    <para>
-      In the example, this would create a user with the Matrix
-      Identifier <literal>@your-username:example.org</literal>.
-    </para>
-    <warning>
-      <para>
-        When using
-        <xref linkend="opt-services.matrix-synapse.settings.registration_shared_secret" />,
-        the secret will end up in the world-readable store. Instead it’s
-        recommended to deploy the secret in an additional file like
-        this:
-      </para>
-      <itemizedlist>
-        <listitem>
-          <para>
-            Create a file with the following contents:
-          </para>
-          <programlisting>
-registration_shared_secret: your-very-secret-secret
-</programlisting>
-        </listitem>
-        <listitem>
-          <para>
-            Deploy the file with a secret-manager such as
-            <link xlink:href="https://nixops.readthedocs.io/en/latest/overview.html#managing-keys"><option>deployment.keys</option></link>
-            from
-            <citerefentry><refentrytitle>nixops</refentrytitle><manvolnum>1</manvolnum></citerefentry>
-            or
-            <link xlink:href="https://github.com/Mic92/sops-nix/">sops-nix</link>
-            to e.g.
-            <filename>/run/secrets/matrix-shared-secret</filename> and
-            ensure that it’s readable by
-            <literal>matrix-synapse</literal>.
-          </para>
-        </listitem>
-        <listitem>
-          <para>
-            Include the file like this in your configuration:
-          </para>
-          <programlisting>
-{
-  services.matrix-synapse.extraConfigFiles = [
-    &quot;/run/secrets/matrix-shared-secret&quot;
-  ];
-}
-</programlisting>
-        </listitem>
-      </itemizedlist>
-    </warning>
-    <note>
-      <para>
-        It’s also possible to user alternative authentication mechanism
-        such as
-        <link xlink:href="https://github.com/matrix-org/matrix-synapse-ldap3">LDAP
-        (via <literal>matrix-synapse-ldap3</literal>)</link> or
-        <link xlink:href="https://matrix-org.github.io/synapse/latest/openid.html">OpenID</link>.
-      </para>
-    </note>
-  </section>
-  <section xml:id="module-services-matrix-element-web">
-    <title>Element (formerly known as Riot) Web Client</title>
-    <para>
-      <link xlink:href="https://github.com/vector-im/riot-web/">Element
-      Web</link> is the reference web client for Matrix and developed by
-      the core team at matrix.org. Element was formerly known as
-      Riot.im, see the
-      <link xlink:href="https://element.io/blog/welcome-to-element/">Element
-      introductory blog post</link> for more information. The following
-      snippet can be optionally added to the code before to complete the
-      synapse installation with a web client served at
-      <literal>https://element.myhostname.example.org</literal> and
-      <literal>https://element.example.org</literal>. Alternatively, you
-      can use the hosted copy at
-      <link xlink:href="https://app.element.io/">https://app.element.io/</link>,
-      or use other web clients or native client applications. Due to the
-      <literal>/.well-known</literal> urls set up done above, many
-      clients should fill in the required connection details
-      automatically when you enter your Matrix Identifier. See
-      <link xlink:href="https://matrix.org/docs/projects/try-matrix-now.html">Try
-      Matrix Now!</link> for a list of existing clients and their
-      supported featureset.
-    </para>
-    <programlisting>
-{
-  services.nginx.virtualHosts.&quot;element.${fqdn}&quot; = {
-    enableACME = true;
-    forceSSL = true;
-    serverAliases = [
-      &quot;element.${config.networking.domain}&quot;
-    ];
-
-    root = pkgs.element-web.override {
-      conf = {
-        default_server_config = clientConfig; # see `clientConfig` from the snippet above.
-      };
-    };
-  };
-}
-</programlisting>
-    <note>
-      <para>
-        The Element developers do not recommend running Element and your
-        Matrix homeserver on the same fully-qualified domain name for
-        security reasons. In the example, this means that you should not
-        reuse the <literal>myhostname.example.org</literal> virtualHost
-        to also serve Element, but instead serve it on a different
-        subdomain, like <literal>element.example.org</literal> in the
-        example. See the
-        <link xlink:href="https://github.com/vector-im/element-web/tree/v1.10.0#important-security-notes">Element
-        Important Security Notes</link> for more information on this
-        subject.
-      </para>
-    </note>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/misc/atuin.nix b/nixos/modules/services/misc/atuin.nix
index c94852e3aad9c..508e2862b63f5 100644
--- a/nixos/modules/services/misc/atuin.nix
+++ b/nixos/modules/services/misc/atuin.nix
@@ -8,7 +8,7 @@ in
 {
   options = {
     services.atuin = {
-      enable = mkEnableOption (mdDoc "Enable server for shell history sync with atuin.");
+      enable = mkEnableOption (mdDoc "Enable server for shell history sync with atuin");
 
       openRegistration = mkOption {
         type = types.bool;
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index e6689217ad9ad..179359c97a3af 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -1504,6 +1504,6 @@ in {
 
   };
 
-  meta.doc = ./gitlab.xml;
+  meta.doc = ./gitlab.md;
 
 }
diff --git a/nixos/modules/services/misc/gitlab.xml b/nixos/modules/services/misc/gitlab.xml
deleted file mode 100644
index a193657b0b761..0000000000000
--- a/nixos/modules/services/misc/gitlab.xml
+++ /dev/null
@@ -1,143 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-gitlab">
-  <title>GitLab</title>
-  <para>
-    GitLab is a feature-rich git hosting service.
-  </para>
-  <section xml:id="module-services-gitlab-prerequisites">
-    <title>Prerequisites</title>
-    <para>
-      The <literal>gitlab</literal> service exposes only an Unix socket
-      at <literal>/run/gitlab/gitlab-workhorse.socket</literal>. You
-      need to configure a webserver to proxy HTTP requests to the
-      socket.
-    </para>
-    <para>
-      For instance, the following configuration could be used to use
-      nginx as frontend proxy:
-    </para>
-    <programlisting>
-services.nginx = {
-  enable = true;
-  recommendedGzipSettings = true;
-  recommendedOptimisation = true;
-  recommendedProxySettings = true;
-  recommendedTlsSettings = true;
-  virtualHosts.&quot;git.example.com&quot; = {
-    enableACME = true;
-    forceSSL = true;
-    locations.&quot;/&quot;.proxyPass = &quot;http://unix:/run/gitlab/gitlab-workhorse.socket&quot;;
-  };
-};
-</programlisting>
-  </section>
-  <section xml:id="module-services-gitlab-configuring">
-    <title>Configuring</title>
-    <para>
-      GitLab depends on both PostgreSQL and Redis and will automatically
-      enable both services. In the case of PostgreSQL, a database and a
-      role will be created.
-    </para>
-    <para>
-      The default state dir is <literal>/var/gitlab/state</literal>.
-      This is where all data like the repositories and uploads will be
-      stored.
-    </para>
-    <para>
-      A basic configuration with some custom settings could look like
-      this:
-    </para>
-    <programlisting>
-services.gitlab = {
-  enable = true;
-  databasePasswordFile = &quot;/var/keys/gitlab/db_password&quot;;
-  initialRootPasswordFile = &quot;/var/keys/gitlab/root_password&quot;;
-  https = true;
-  host = &quot;git.example.com&quot;;
-  port = 443;
-  user = &quot;git&quot;;
-  group = &quot;git&quot;;
-  smtp = {
-    enable = true;
-    address = &quot;localhost&quot;;
-    port = 25;
-  };
-  secrets = {
-    dbFile = &quot;/var/keys/gitlab/db&quot;;
-    secretFile = &quot;/var/keys/gitlab/secret&quot;;
-    otpFile = &quot;/var/keys/gitlab/otp&quot;;
-    jwsFile = &quot;/var/keys/gitlab/jws&quot;;
-  };
-  extraConfig = {
-    gitlab = {
-      email_from = &quot;gitlab-no-reply@example.com&quot;;
-      email_display_name = &quot;Example GitLab&quot;;
-      email_reply_to = &quot;gitlab-no-reply@example.com&quot;;
-      default_projects_features = { builds = false; };
-    };
-  };
-};
-</programlisting>
-    <para>
-      If you’re setting up a new GitLab instance, generate new secrets.
-      You for instance use
-      <literal>tr -dc A-Za-z0-9 &lt; /dev/urandom | head -c 128 &gt; /var/keys/gitlab/db</literal>
-      to generate a new db secret. Make sure the files can be read by,
-      and only by, the user specified by
-      <link linkend="opt-services.gitlab.user">services.gitlab.user</link>.
-      GitLab encrypts sensitive data stored in the database. If you’re
-      restoring an existing GitLab instance, you must specify the
-      secrets secret from <literal>config/secrets.yml</literal> located
-      in your GitLab state folder.
-    </para>
-    <para>
-      When <literal>incoming_mail.enabled</literal> is set to
-      <literal>true</literal> in
-      <link linkend="opt-services.gitlab.extraConfig">extraConfig</link>
-      an additional service called <literal>gitlab-mailroom</literal> is
-      enabled for fetching incoming mail.
-    </para>
-    <para>
-      Refer to <xref linkend="ch-options" /> for all available
-      configuration options for the
-      <link linkend="opt-services.gitlab.enable">services.gitlab</link>
-      module.
-    </para>
-  </section>
-  <section xml:id="module-services-gitlab-maintenance">
-    <title>Maintenance</title>
-    <section xml:id="module-services-gitlab-maintenance-backups">
-      <title>Backups</title>
-      <para>
-        Backups can be configured with the options in
-        <link linkend="opt-services.gitlab.backup.keepTime">services.gitlab.backup</link>.
-        Use the
-        <link linkend="opt-services.gitlab.backup.startAt">services.gitlab.backup.startAt</link>
-        option to configure regular backups.
-      </para>
-      <para>
-        To run a manual backup, start the
-        <literal>gitlab-backup</literal> service:
-      </para>
-      <programlisting>
-$ systemctl start gitlab-backup.service
-</programlisting>
-    </section>
-    <section xml:id="module-services-gitlab-maintenance-rake">
-      <title>Rake tasks</title>
-      <para>
-        You can run GitLab’s rake tasks with
-        <literal>gitlab-rake</literal> which will be available on the
-        system when GitLab is enabled. You will have to run the command
-        as the user that you configured to run GitLab with.
-      </para>
-      <para>
-        A list of all available rake tasks can be obtained by running:
-      </para>
-      <programlisting>
-$ sudo -u git -H gitlab-rake -T
-</programlisting>
-    </section>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/misc/input-remapper.nix b/nixos/modules/services/misc/input-remapper.nix
index 6353966f5c3ff..3f6d97f857381 100644
--- a/nixos/modules/services/misc/input-remapper.nix
+++ b/nixos/modules/services/misc/input-remapper.nix
@@ -6,7 +6,7 @@ let cfg = config.services.input-remapper; in
 {
   options = {
     services.input-remapper = {
-      enable = mkEnableOption (lib.mdDoc "input-remapper, an easy to use tool to change the mapping of your input device buttons.");
+      enable = mkEnableOption (lib.mdDoc "input-remapper, an easy to use tool to change the mapping of your input device buttons");
       package = mkPackageOptionMD pkgs "input-remapper" { };
       enableUdevRules = mkEnableOption (lib.mdDoc "udev rules added by input-remapper to handle hotplugged devices. Currently disabled by default due to https://github.com/sezanzeb/input-remapper/issues/140");
       serviceWantedBy = mkOption {
diff --git a/nixos/modules/services/misc/klipper.nix b/nixos/modules/services/misc/klipper.nix
index a2158e9461bce..9f8539980aaab 100644
--- a/nixos/modules/services/misc/klipper.nix
+++ b/nixos/modules/services/misc/klipper.nix
@@ -135,7 +135,7 @@ in
       }
       {
         assertion = (cfg.configFile != null) != (cfg.settings != null);
-        message = "You need to either specify services.klipper.settings or services.klipper.defaultConfig.";
+        message = "You need to either specify services.klipper.settings or services.klipper.configFile.";
       }
     ];
 
diff --git a/nixos/modules/services/misc/mbpfan.nix b/nixos/modules/services/misc/mbpfan.nix
index d467aa8797678..1a6b54854d1cd 100644
--- a/nixos/modules/services/misc/mbpfan.nix
+++ b/nixos/modules/services/misc/mbpfan.nix
@@ -1,5 +1,4 @@
 { config, lib, pkgs, ... }:
-
 with lib;
 
 let
@@ -16,17 +15,19 @@ in {
       type = types.package;
       default = pkgs.mbpfan;
       defaultText = literalExpression "pkgs.mbpfan";
-      description = lib.mdDoc ''
-        The package used for the mbpfan daemon.
-      '';
+      description = lib.mdDoc "The package used for the mbpfan daemon.";
     };
 
     verbose = mkOption {
       type = types.bool;
       default = false;
-      description = lib.mdDoc ''
-        If true, sets the log level to verbose.
-      '';
+      description = lib.mdDoc "If true, sets the log level to verbose.";
+    };
+
+    aggressive = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc "If true, favors higher default fan speeds.";
     };
 
     settings = mkOption {
@@ -35,24 +36,14 @@ in {
       type = types.submodule {
         freeformType = settingsFormat.type;
 
-        options.general.min_fan1_speed = mkOption {
-          type = types.nullOr types.int;
-          default = 2000;
-          description = lib.mdDoc ''
-            You can check minimum and maximum fan limits with
-            `cat /sys/devices/platform/applesmc.768/fan*_min` and
-            `cat /sys/devices/platform/applesmc.768/fan*_max` respectively.
-            Setting to null implies using default value from applesmc.
-          '';
-        };
         options.general.low_temp = mkOption {
           type = types.int;
-          default = 55;
+          default = 63;
           description = lib.mdDoc "If temperature is below this, fans will run at minimum speed.";
         };
         options.general.high_temp = mkOption {
           type = types.int;
-          default = 58;
+          default = 66;
           description = lib.mdDoc "If temperature is above this, fan speed will gradually increase.";
         };
         options.general.max_temp = mkOption {
@@ -79,10 +70,16 @@ in {
   ];
 
   config = mkIf cfg.enable {
-    boot.kernelModules = [ "coretemp" "applesmc" ];
+    services.mbpfan.settings = mkIf cfg.aggressive {
+      general.min_fan1_speed = mkDefault 2000;
+      general.low_temp = mkDefault 55;
+      general.high_temp = mkDefault 58;
+      general.max_temp = mkDefault 70;
+    };
 
-    environment.etc."mbpfan.conf".source = settingsFile;
+    boot.kernelModules = [ "coretemp" "applesmc" ];
     environment.systemPackages = [ cfg.package ];
+    environment.etc."mbpfan.conf".source = settingsFile;
 
     systemd.services.mbpfan = {
       description = "A fan manager daemon for MacBook Pro";
diff --git a/nixos/modules/services/misc/moonraker.nix b/nixos/modules/services/misc/moonraker.nix
index 62064b5d90fb3..53638ded29634 100644
--- a/nixos/modules/services/misc/moonraker.nix
+++ b/nixos/modules/services/misc/moonraker.nix
@@ -11,6 +11,8 @@ let
       else lib.concatMapStrings (s: "\n  ${generators.mkValueStringDefault {} s}") l;
     mkKeyValue = generators.mkKeyValueDefault {} ":";
   };
+
+  unifiedConfigDir = cfg.stateDir + "/config";
 in {
   options = {
     services.moonraker = {
@@ -30,11 +32,10 @@ in {
       };
 
       configDir = mkOption {
-        type = types.path;
-        default = cfg.stateDir + "/config";
-        defaultText = literalExpression ''config.${opt.stateDir} + "/config"'';
+        type = types.nullOr types.path;
+        default = null;
         description = lib.mdDoc ''
-          The directory containing client-writable configuration files.
+          Deprecated directory containing client-writable configuration files.
 
           Clients will be able to edit files in this directory via the API. This directory must be writable.
         '';
@@ -96,8 +97,18 @@ in {
   };
 
   config = mkIf cfg.enable {
-    warnings = optional (cfg.settings ? update_manager)
-      ''Enabling update_manager is not supported on NixOS and will lead to non-removable warnings in some clients.'';
+    warnings = []
+      ++ optional (cfg.settings ? update_manager)
+        ''Enabling update_manager is not supported on NixOS and will lead to non-removable warnings in some clients.''
+      ++ optional (cfg.configDir != null)
+        ''
+          services.moonraker.configDir has been deprecated upstream and will be removed.
+
+          Action: ${
+            if cfg.configDir == unifiedConfigDir then "Simply remove services.moonraker.configDir from your config."
+            else "Move files from `${cfg.configDir}` to `${unifiedConfigDir}` then remove services.moonraker.configDir from your config."
+          }
+        '';
 
     assertions = [
       {
@@ -124,20 +135,20 @@ in {
           port = cfg.port;
           klippy_uds_address = cfg.klipperSocket;
         };
+        machine = {
+          validate_service = false;
+        };
+      } // (lib.optionalAttrs (cfg.configDir != null) {
         file_manager = {
           config_path = cfg.configDir;
         };
-        database = {
-          database_path = "${cfg.stateDir}/database";
-        };
-      };
+      });
       fullConfig = recursiveUpdate cfg.settings forcedConfig;
     in format.generate "moonraker.cfg" fullConfig;
 
     systemd.tmpfiles.rules = [
       "d '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -"
-      "d '${cfg.configDir}' - ${cfg.user} ${cfg.group} - -"
-    ];
+    ] ++ lib.optional (cfg.configDir != null) "d '${cfg.configDir}' - ${cfg.user} ${cfg.group} - -";
 
     systemd.services.moonraker = {
       description = "Moonraker, an API web server for Klipper";
@@ -147,9 +158,16 @@ in {
 
       # Moonraker really wants its own config to be writable...
       script = ''
-        cp /etc/moonraker.cfg ${cfg.configDir}/moonraker-temp.cfg
-        chmod u+w ${cfg.configDir}/moonraker-temp.cfg
-        exec ${pkg}/bin/moonraker -c ${cfg.configDir}/moonraker-temp.cfg
+        config_path=${
+          # Deprecated separate config dir
+          if cfg.configDir != null then "${cfg.configDir}/moonraker-temp.cfg"
+          # Config in unified data path
+          else "${unifiedConfigDir}/moonraker-temp.cfg"
+        }
+        mkdir -p $(dirname "$config_path")
+        cp /etc/moonraker.cfg "$config_path"
+        chmod u+w "$config_path"
+        exec ${pkg}/bin/moonraker -d ${cfg.stateDir} -c "$config_path"
       '';
 
       # Needs `ip` command
@@ -184,5 +202,6 @@ in {
   meta.maintainers = with maintainers; [
     cab404
     vtuan10
+    zhaofengli
   ];
 }
diff --git a/nixos/modules/services/misc/nitter.nix b/nixos/modules/services/misc/nitter.nix
index f0cb5cc151388..2d0d91f95985a 100644
--- a/nixos/modules/services/misc/nitter.nix
+++ b/nixos/modules/services/misc/nitter.nix
@@ -185,6 +185,13 @@ in
           description = lib.mdDoc "Replace YouTube links with links to this instance (blank to disable).";
         };
 
+        replaceReddit = mkOption {
+          type = types.str;
+          default = "";
+          example = "teddit.net";
+          description = lib.mdDoc "Replace Reddit links with links to this instance (blank to disable).";
+        };
+
         replaceInstagram = mkOption {
           type = types.str;
           default = "";
diff --git a/nixos/modules/services/misc/octoprint.nix b/nixos/modules/services/misc/octoprint.nix
index c216c6fa2b77a..43e0ce0c21d32 100644
--- a/nixos/modules/services/misc/octoprint.nix
+++ b/nixos/modules/services/misc/octoprint.nix
@@ -106,6 +106,9 @@ in
 
     systemd.tmpfiles.rules = [
       "d '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -"
+      # this will allow octoprint access to raspberry specific hardware to check for throttling
+      # read-only will not work: "VCHI initialization failed" error
+      "a /dev/vchiq - - - - u:octoprint:rw"
     ];
 
     systemd.services.octoprint = {
diff --git a/nixos/modules/services/misc/paperless.nix b/nixos/modules/services/misc/paperless.nix
index 1dddd147ac095..667f16d98f828 100644
--- a/nixos/modules/services/misc/paperless.nix
+++ b/nixos/modules/services/misc/paperless.nix
@@ -226,9 +226,26 @@ in
 
         # Auto-migrate on first run or if the package has changed
         versionFile="${cfg.dataDir}/src-version"
-        if [[ $(cat "$versionFile" 2>/dev/null) != ${pkg} ]]; then
+        version=$(cat "$versionFile" 2>/dev/null || echo 0)
+
+        if [[ $version != ${pkg.version} ]]; then
           ${pkg}/bin/paperless-ngx migrate
-          echo ${pkg} > "$versionFile"
+
+          # Parse old version string format for backwards compatibility
+          version=$(echo "$version" | grep -ohP '[^-]+$')
+
+          versionLessThan() {
+            target=$1
+            [[ $({ echo "$version"; echo "$target"; } | sort -V | head -1) != "$target" ]]
+          }
+
+          if versionLessThan 1.12.0; then
+            # Reindex documents as mentioned in https://github.com/paperless-ngx/paperless-ngx/releases/tag/v1.12.1
+            echo "Reindexing documents, to allow searching old comments. Required after the 1.12.x upgrade."
+            ${pkg}/bin/paperless-ngx document_index reindex
+          fi
+
+          echo ${pkg.version} > "$versionFile"
         fi
       ''
       + optionalString (cfg.passwordFile != null) ''
diff --git a/nixos/modules/services/misc/pykms.nix b/nixos/modules/services/misc/pykms.nix
index 314388e0152ef..be3accc0d7e5c 100644
--- a/nixos/modules/services/misc/pykms.nix
+++ b/nixos/modules/services/misc/pykms.nix
@@ -85,7 +85,7 @@ in
         WorkingDirectory = libDir;
         SyslogIdentifier = "pykms";
         Restart = "on-failure";
-        MemoryLimit = cfg.memoryLimit;
+        MemoryMax = cfg.memoryLimit;
       };
     };
   };
diff --git a/nixos/modules/services/misc/sourcehut/default.nix b/nixos/modules/services/misc/sourcehut/default.nix
index b03cf0739e9d9..d4391bc49e315 100644
--- a/nixos/modules/services/misc/sourcehut/default.nix
+++ b/nixos/modules/services/misc/sourcehut/default.nix
@@ -438,7 +438,7 @@ in
         };
 
         options."lists.sr.ht" = commonServiceSettings "lists" // {
-          allow-new-lists = mkEnableOption (lib.mdDoc "Allow creation of new lists.");
+          allow-new-lists = mkEnableOption (lib.mdDoc "Allow creation of new lists");
           notify-from = mkOption {
             description = lib.mdDoc "Outgoing email for notifications generated by users.";
             type = types.str;
@@ -1390,6 +1390,6 @@ in
     '')
   ];
 
-  meta.doc = ./default.xml;
+  meta.doc = ./default.md;
   meta.maintainers = with maintainers; [ tomberek ];
 }
diff --git a/nixos/modules/services/misc/sourcehut/default.xml b/nixos/modules/services/misc/sourcehut/default.xml
deleted file mode 100644
index 1d8330931ddf0..0000000000000
--- a/nixos/modules/services/misc/sourcehut/default.xml
+++ /dev/null
@@ -1,113 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-sourcehut">
-  <title>Sourcehut</title>
-  <para>
-    <link xlink:href="https://sr.ht.com/">Sourcehut</link> is an
-    open-source, self-hostable software development platform. The server
-    setup can be automated using
-    <link linkend="opt-services.sourcehut.enable">services.sourcehut</link>.
-  </para>
-  <section xml:id="module-services-sourcehut-basic-usage">
-    <title>Basic usage</title>
-    <para>
-      Sourcehut is a Python and Go based set of applications. This NixOS
-      module also provides basic configuration integrating Sourcehut
-      into locally running <literal>services.nginx</literal>,
-      <literal>services.redis.servers.sourcehut</literal>,
-      <literal>services.postfix</literal> and
-      <literal>services.postgresql</literal> services.
-    </para>
-    <para>
-      A very basic configuration may look like this:
-    </para>
-    <programlisting>
-{ pkgs, ... }:
-let
-  fqdn =
-    let
-      join = hostName: domain: hostName + optionalString (domain != null) &quot;.${domain}&quot;;
-    in join config.networking.hostName config.networking.domain;
-in {
-
-  networking = {
-    hostName = &quot;srht&quot;;
-    domain = &quot;tld&quot;;
-    firewall.allowedTCPPorts = [ 22 80 443 ];
-  };
-
-  services.sourcehut = {
-    enable = true;
-    git.enable = true;
-    man.enable = true;
-    meta.enable = true;
-    nginx.enable = true;
-    postfix.enable = true;
-    postgresql.enable = true;
-    redis.enable = true;
-    settings = {
-        &quot;sr.ht&quot; = {
-          environment = &quot;production&quot;;
-          global-domain = fqdn;
-          origin = &quot;https://${fqdn}&quot;;
-          # Produce keys with srht-keygen from sourcehut.coresrht.
-          network-key = &quot;/run/keys/path/to/network-key&quot;;
-          service-key = &quot;/run/keys/path/to/service-key&quot;;
-        };
-        webhooks.private-key= &quot;/run/keys/path/to/webhook-key&quot;;
-    };
-  };
-
-  security.acme.certs.&quot;${fqdn}&quot;.extraDomainNames = [
-    &quot;meta.${fqdn}&quot;
-    &quot;man.${fqdn}&quot;
-    &quot;git.${fqdn}&quot;
-  ];
-
-  services.nginx = {
-    enable = true;
-    # only recommendedProxySettings are strictly required, but the rest make sense as well.
-    recommendedTlsSettings = true;
-    recommendedOptimisation = true;
-    recommendedGzipSettings = true;
-    recommendedProxySettings = true;
-
-    # Settings to setup what certificates are used for which endpoint.
-    virtualHosts = {
-      &quot;${fqdn}&quot;.enableACME = true;
-      &quot;meta.${fqdn}&quot;.useACMEHost = fqdn:
-      &quot;man.${fqdn}&quot;.useACMEHost = fqdn:
-      &quot;git.${fqdn}&quot;.useACMEHost = fqdn:
-    };
-  };
-}
-</programlisting>
-    <para>
-      The <literal>hostName</literal> option is used internally to
-      configure the nginx reverse-proxy. The <literal>settings</literal>
-      attribute set is used by the configuration generator and the
-      result is placed in <literal>/etc/sr.ht/config.ini</literal>.
-    </para>
-  </section>
-  <section xml:id="module-services-sourcehut-configuration">
-    <title>Configuration</title>
-    <para>
-      All configuration parameters are also stored in
-      <literal>/etc/sr.ht/config.ini</literal> which is generated by the
-      module and linked from the store to ensure that all values from
-      <literal>config.ini</literal> can be modified by the module.
-    </para>
-  </section>
-  <section xml:id="module-services-sourcehut-httpd">
-    <title>Using an alternative webserver as reverse-proxy (e.g.
-    <literal>httpd</literal>)</title>
-    <para>
-      By default, <literal>nginx</literal> is used as reverse-proxy for
-      <literal>sourcehut</literal>. However, it’s possible to use e.g.
-      <literal>httpd</literal> by explicitly disabling
-      <literal>nginx</literal> using
-      <xref linkend="opt-services.nginx.enable" /> and fixing the
-      <literal>settings</literal>.
-    </para>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/misc/taskserver/default.nix b/nixos/modules/services/misc/taskserver/default.nix
index 7331c323adbaa..775b3b6d2eae9 100644
--- a/nixos/modules/services/misc/taskserver/default.nix
+++ b/nixos/modules/services/misc/taskserver/default.nix
@@ -566,5 +566,5 @@ in {
     })
   ];
 
-  meta.doc = ./default.xml;
+  meta.doc = ./default.md;
 }
diff --git a/nixos/modules/services/misc/taskserver/default.xml b/nixos/modules/services/misc/taskserver/default.xml
deleted file mode 100644
index bbb38211b7cae..0000000000000
--- a/nixos/modules/services/misc/taskserver/default.xml
+++ /dev/null
@@ -1,130 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-taskserver">
-  <title>Taskserver</title>
-  <para>
-    Taskserver is the server component of
-    <link xlink:href="https://taskwarrior.org/">Taskwarrior</link>, a
-    free and open source todo list application.
-  </para>
-  <para>
-    <emphasis>Upstream documentation:</emphasis>
-    <link xlink:href="https://taskwarrior.org/docs/#taskd">https://taskwarrior.org/docs/#taskd</link>
-  </para>
-  <section xml:id="module-services-taskserver-configuration">
-    <title>Configuration</title>
-    <para>
-      Taskserver does all of its authentication via TLS using client
-      certificates, so you either need to roll your own CA or purchase a
-      certificate from a known CA, which allows creation of client
-      certificates. These certificates are usually advertised as
-      <quote>server certificates</quote>.
-    </para>
-    <para>
-      So in order to make it easier to handle your own CA, there is a
-      helper tool called <command>nixos-taskserver</command> which
-      manages the custom CA along with Taskserver organisations, users
-      and groups.
-    </para>
-    <para>
-      While the client certificates in Taskserver only authenticate
-      whether a user is allowed to connect, every user has its own UUID
-      which identifies it as an entity.
-    </para>
-    <para>
-      With <command>nixos-taskserver</command> the client certificate is
-      created along with the UUID of the user, so it handles all of the
-      credentials needed in order to setup the Taskwarrior client to
-      work with a Taskserver.
-    </para>
-  </section>
-  <section xml:id="module-services-taskserver-nixos-taskserver-tool">
-    <title>The nixos-taskserver tool</title>
-    <para>
-      Because Taskserver by default only provides scripts to setup users
-      imperatively, the <command>nixos-taskserver</command> tool is used
-      for addition and deletion of organisations along with users and
-      groups defined by
-      <xref linkend="opt-services.taskserver.organisations" /> and as
-      well for imperative set up.
-    </para>
-    <para>
-      The tool is designed to not interfere if the command is used to
-      manually set up some organisations, users or groups.
-    </para>
-    <para>
-      For example if you add a new organisation using
-      <command>nixos-taskserver org add foo</command>, the organisation
-      is not modified and deleted no matter what you define in
-      <option>services.taskserver.organisations</option>, even if you’re
-      adding the same organisation in that option.
-    </para>
-    <para>
-      The tool is modelled to imitate the official
-      <command>taskd</command> command, documentation for each
-      subcommand can be shown by using the <option>--help</option>
-      switch.
-    </para>
-  </section>
-  <section xml:id="module-services-taskserver-declarative-ca-management">
-    <title>Declarative/automatic CA management</title>
-    <para>
-      Everything is done according to what you specify in the module
-      options, however in order to set up a Taskwarrior client for
-      synchronisation with a Taskserver instance, you have to transfer
-      the keys and certificates to the client machine.
-    </para>
-    <para>
-      This is done using
-      <command>nixos-taskserver user export $orgname $username</command>
-      which is printing a shell script fragment to stdout which can
-      either be used verbatim or adjusted to import the user on the
-      client machine.
-    </para>
-    <para>
-      For example, let’s say you have the following configuration:
-    </para>
-    <programlisting>
-{
-  services.taskserver.enable = true;
-  services.taskserver.fqdn = &quot;server&quot;;
-  services.taskserver.listenHost = &quot;::&quot;;
-  services.taskserver.organisations.my-company.users = [ &quot;alice&quot; ];
-}
-</programlisting>
-    <para>
-      This creates an organisation called <literal>my-company</literal>
-      with the user <literal>alice</literal>.
-    </para>
-    <para>
-      Now in order to import the <literal>alice</literal> user to
-      another machine <literal>alicebox</literal>, all we need to do is
-      something like this:
-    </para>
-    <programlisting>
-$ ssh server nixos-taskserver user export my-company alice | sh
-</programlisting>
-    <para>
-      Of course, if no SSH daemon is available on the server you can
-      also copy &amp; paste it directly into a shell.
-    </para>
-    <para>
-      After this step the user should be set up and you can start
-      synchronising your tasks for the first time with
-      <command>task sync init</command> on <literal>alicebox</literal>.
-    </para>
-    <para>
-      Subsequent synchronisation requests merely require the command
-      <command>task sync</command> after that stage.
-    </para>
-  </section>
-  <section xml:id="module-services-taskserver-manual-ca-management">
-    <title>Manual CA management</title>
-    <para>
-      If you set any options within
-      <link linkend="opt-services.taskserver.pki.manual.ca.cert">service.taskserver.pki.manual</link>.*,
-      <command>nixos-taskserver</command> won’t issue certificates, but
-      you can still use it for adding or removing user accounts.
-    </para>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/misc/weechat.nix b/nixos/modules/services/misc/weechat.nix
index aa5b9b22837e2..338493e3cd378 100644
--- a/nixos/modules/services/misc/weechat.nix
+++ b/nixos/modules/services/misc/weechat.nix
@@ -59,5 +59,5 @@ in
       };
   };
 
-  meta.doc = ./weechat.xml;
+  meta.doc = ./weechat.md;
 }
diff --git a/nixos/modules/services/misc/weechat.xml b/nixos/modules/services/misc/weechat.xml
deleted file mode 100644
index 83ae171217d24..0000000000000
--- a/nixos/modules/services/misc/weechat.xml
+++ /dev/null
@@ -1,63 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-weechat">
-  <title>WeeChat</title>
-  <para>
-    <link xlink:href="https://weechat.org/">WeeChat</link> is a fast and
-    extensible IRC client.
-  </para>
-  <section xml:id="module-services-weechat-basic-usage">
-    <title>Basic Usage</title>
-    <para>
-      By default, the module creates a
-      <link xlink:href="https://www.freedesktop.org/wiki/Software/systemd/"><literal>systemd</literal></link>
-      unit which runs the chat client in a detached
-      <link xlink:href="https://www.gnu.org/software/screen/"><literal>screen</literal></link>
-      session.
-    </para>
-    <para>
-      This can be done by enabling the <literal>weechat</literal>
-      service:
-    </para>
-    <programlisting>
-{ ... }:
-
-{
-  services.weechat.enable = true;
-}
-</programlisting>
-    <para>
-      The service is managed by a dedicated user named
-      <literal>weechat</literal> in the state directory
-      <literal>/var/lib/weechat</literal>.
-    </para>
-  </section>
-  <section xml:id="module-services-weechat-reattach">
-    <title>Re-attaching to WeeChat</title>
-    <para>
-      WeeChat runs in a screen session owned by a dedicated user. To
-      explicitly allow your another user to attach to this session, the
-      <literal>screenrc</literal> needs to be tweaked by adding
-      <link xlink:href="https://www.gnu.org/software/screen/manual/html_node/Multiuser.html#Multiuser">multiuser</link>
-      support:
-    </para>
-    <programlisting>
-{
-  programs.screen.screenrc = ''
-    multiuser on
-    acladd normal_user
-  '';
-}
-</programlisting>
-    <para>
-      Now, the session can be re-attached like this:
-    </para>
-    <programlisting>
-screen -x weechat/weechat-screen
-</programlisting>
-    <para>
-      <emphasis>The session name can be changed using
-      <link xlink:href="options.html#opt-services.weechat.sessionName">services.weechat.sessionName.</link></emphasis>
-    </para>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/monitoring/cadvisor.nix b/nixos/modules/services/monitoring/cadvisor.nix
index a8fba4e6e8cef..68e6e8e40b314 100644
--- a/nixos/modules/services/monitoring/cadvisor.nix
+++ b/nixos/modules/services/monitoring/cadvisor.nix
@@ -123,7 +123,7 @@ in {
             ${escapeShellArgs cfg.extraOptions} \
             ${optionalString (cfg.storageDriver != null) ''
               -storage_driver "${cfg.storageDriver}" \
-              -storage_driver_user "${cfg.storageDriverHost}" \
+              -storage_driver_host "${cfg.storageDriverHost}" \
               -storage_driver_db "${cfg.storageDriverDb}" \
               -storage_driver_user "${cfg.storageDriverUser}" \
               -storage_driver_password "$(cat "${cfg.storageDriverPasswordFile}")" \
diff --git a/nixos/modules/services/monitoring/cockpit.nix b/nixos/modules/services/monitoring/cockpit.nix
new file mode 100644
index 0000000000000..2947b4d801201
--- /dev/null
+++ b/nixos/modules/services/monitoring/cockpit.nix
@@ -0,0 +1,231 @@
+{ pkgs, config, lib, ... }:
+
+let
+  cfg = config.services.cockpit;
+  inherit (lib) types mkEnableOption mkOption mkIf mdDoc literalMD mkPackageOptionMD;
+  settingsFormat = pkgs.formats.ini {};
+in {
+  options = {
+    services.cockpit = {
+      enable = mkEnableOption (mdDoc "Cockpit");
+
+      package = mkPackageOptionMD pkgs "Cockpit" {
+        default = [ "cockpit" ];
+      };
+
+      settings = lib.mkOption {
+        type = settingsFormat.type;
+
+        default = {};
+
+        description = mdDoc ''
+          Settings for cockpit that will be saved in /etc/cockpit/cockpit.conf.
+
+          See the [documentation](https://cockpit-project.org/guide/latest/cockpit.conf.5.html), that is also available with `man cockpit.conf.5` for details.
+        '';
+      };
+
+      port = mkOption {
+        description = mdDoc "Port where cockpit will listen.";
+        type = types.port;
+        default = 9090;
+      };
+
+      openFirewall = mkOption {
+        description = mdDoc "Open port for cockpit.";
+        type = types.bool;
+        default = false;
+      };
+    };
+  };
+  config = mkIf cfg.enable {
+
+    # expose cockpit-bridge system-wide
+    environment.systemPackages = [ cfg.package ];
+
+    # allow cockpit to find its plugins
+    environment.pathsToLink = [ "/share/cockpit" ];
+
+    # generate cockpit settings
+    environment.etc."cockpit/cockpit.conf".source = settingsFormat.generate "cockpit.conf" cfg.settings;
+
+    security.pam.services.cockpit = {};
+
+    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
+
+    # units are in reverse sort order if you ls $out/lib/systemd/system
+    # all these units are basically verbatim translated from upstream
+
+    # Translation from $out/lib/systemd/system/systemd-cockpithttps.slice
+    systemd.slices.system-cockpithttps = {
+      description = "Resource limits for all cockpit-ws-https@.service instances";
+      sliceConfig = {
+        TasksMax = 200;
+        MemoryHigh = "75%";
+        MemoryMax = "90%";
+      };
+    };
+
+    # Translation from $out/lib/systemd/system/cockpit-wsinstance-https@.socket
+    systemd.sockets."cockpit-wsinstance-https@" = {
+      unitConfig = {
+        Description = "Socket for Cockpit Web Service https instance %I";
+        BindsTo = [ "cockpit.service" "cockpit-wsinstance-https@%i.service" ];
+        # clean up the socket after the service exits, to prevent fd leak
+        # this also effectively prevents a DoS by starting arbitrarily many sockets, as
+        # the services are resource-limited by system-cockpithttps.slice
+        Documentation = "man:cockpit-ws(8)";
+      };
+      socketConfig = {
+        ListenStream = "/run/cockpit/wsinstance/https@%i.sock";
+        SocketUser = "root";
+        SocketMode = "0600";
+      };
+    };
+
+    # Translation from $out/lib/systemd/system/cockpit-wsinstance-https@.service
+    systemd.services."cockpit-wsinstance-https@" = {
+      description = "Cockpit Web Service https instance %I";
+      bindsTo = [ "cockpit.service"];
+      path = [ cfg.package ];
+      documentation = [ "man:cockpit-ws(8)" ];
+      serviceConfig = {
+        Slice = "system-cockpithttps.slice";
+        ExecStart = "${cfg.package}/libexec/cockpit-ws --for-tls-proxy --port=0";
+        User = "root";
+        Group = "";
+      };
+    };
+
+    # Translation from $out/lib/systemd/system/cockpit-wsinstance-http.socket
+    systemd.sockets.cockpit-wsinstance-http = {
+      unitConfig = {
+        Description = "Socket for Cockpit Web Service http instance";
+        BindsTo = "cockpit.service";
+        Documentation = "man:cockpit-ws(8)";
+      };
+      socketConfig = {
+        ListenStream = "/run/cockpit/wsinstance/http.sock";
+        SocketUser = "root";
+        SocketMode = "0600";
+      };
+    };
+
+    # Translation from $out/lib/systemd/system/cockpit-wsinstance-https-factory.socket
+    systemd.sockets.cockpit-wsinstance-https-factory = {
+      unitConfig = {
+        Description = "Socket for Cockpit Web Service https instance factory";
+        BindsTo = "cockpit.service";
+        Documentation = "man:cockpit-ws(8)";
+      };
+      socketConfig = {
+        ListenStream = "/run/cockpit/wsinstance/https-factory.sock";
+        Accept = true;
+        SocketUser = "root";
+        SocketMode = "0600";
+      };
+    };
+
+    # Translation from $out/lib/systemd/system/cockpit-wsinstance-https-factory@.service
+    systemd.services."cockpit-wsinstance-https-factory@" = {
+      description = "Cockpit Web Service https instance factory";
+      documentation = [ "man:cockpit-ws(8)" ];
+      path = [ cfg.package ];
+      serviceConfig = {
+        ExecStart = "${cfg.package}/libexec/cockpit-wsinstance-factory";
+        User = "root";
+      };
+    };
+
+    # Translation from $out/lib/systemd/system/cockpit-wsinstance-http.service
+    systemd.services."cockpit-wsinstance-http" = {
+      description = "Cockpit Web Service http instance";
+      bindsTo = [ "cockpit.service" ];
+      path = [ cfg.package ];
+      documentation = [ "man:cockpit-ws(8)" ];
+      serviceConfig = {
+        ExecStart = "${cfg.package}/libexec/cockpit-ws --no-tls --port=0";
+        User = "root";
+        Group = "";
+      };
+    };
+
+    # Translation from $out/lib/systemd/system/cockpit.socket
+    systemd.sockets."cockpit" = {
+      unitConfig = {
+        Description = "Cockpit Web Service Socket";
+        Documentation = "man:cockpit-ws(8)";
+        Wants = "cockpit-motd.service";
+      };
+      socketConfig = {
+        ListenStream = cfg.port;
+        ExecStartPost = [
+          "-${cfg.package}/share/cockpit/motd/update-motd \"\" localhost"
+          "-${pkgs.coreutils}/bin/ln -snf active.motd /run/cockpit/motd"
+        ];
+        ExecStopPost = "-${pkgs.coreutils}/bin/ln -snf inactive.motd /run/cockpit/motd";
+      };
+      wantedBy = [ "sockets.target" ];
+    };
+
+    # Translation from $out/lib/systemd/system/cockpit.service
+    systemd.services."cockpit" = {
+      description = "Cockpit Web Service";
+      documentation = [ "man:cockpit-ws(8)" ];
+      restartIfChanged = true;
+      path = with pkgs; [ coreutils cfg.package ];
+      requires = [ "cockpit.socket" "cockpit-wsinstance-http.socket" "cockpit-wsinstance-https-factory.socket" ];
+      after = [ "cockpit-wsinstance-http.socket" "cockpit-wsinstance-https-factory.socket" ];
+      environment = {
+        G_MESSAGES_DEBUG = "cockpit-ws,cockpit-bridge";
+      };
+      serviceConfig = {
+        RuntimeDirectory="cockpit/tls";
+        ExecStartPre = [
+          # cockpit-tls runs in a more constrained environment, these + means that these commands
+          # will run with full privilege instead of inside that constrained environment
+          # See https://www.freedesktop.org/software/systemd/man/systemd.service.html#ExecStart= for details
+          "+${cfg.package}/libexec/cockpit-certificate-ensure --for-cockpit-tls"
+        ];
+        ExecStart = "${cfg.package}/libexec/cockpit-tls";
+        User = "root";
+        Group = "";
+        NoNewPrivileges = true;
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectKernelTunables = true;
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+        MemoryDenyWriteExecute = true;
+      };
+    };
+
+    # Translation from $out/lib/systemd/system/cockpit-motd.service
+    # This part basically implements a motd state machine:
+    # - If cockpit.socket is enabled then /run/cockpit/motd points to /run/cockpit/active.motd
+    # - If cockpit.socket is disabled then /run/cockpit/motd points to /run/cockpit/inactive.motd
+    # - As cockpit.socket is disabled by default, /run/cockpit/motd points to /run/cockpit/inactive.motd
+    # /run/cockpit/active.motd is generated dynamically by cockpit-motd.service
+    systemd.services."cockpit-motd" = {
+      path = with pkgs; [ nettools ];
+      serviceConfig = {
+        Type = "oneshot";
+        ExecStart = "${cfg.package}/share/cockpit/motd/update-motd";
+      };
+      description = "Cockpit motd updater service";
+      documentation = [ "man:cockpit-ws(8)" ];
+      wants = [ "network.target" ];
+      after = [ "network.target" "cockpit.socket" ];
+    };
+
+    systemd.tmpfiles.rules = [ # From $out/lib/tmpfiles.d/cockpit-tmpfiles.conf
+      "C /run/cockpit/inactive.motd 0640 root root - ${cfg.package}/share/cockpit/motd/inactive.motd"
+      "f /run/cockpit/active.motd   0640 root root -"
+      "L+ /run/cockpit/motd - - - - inactive.motd"
+      "d /etc/cockpit/ws-certs.d 0600 root root 0"
+    ];
+  };
+
+  meta.maintainers = pkgs.cockpit.meta.maintainers;
+}
diff --git a/nixos/modules/services/monitoring/mackerel-agent.nix b/nixos/modules/services/monitoring/mackerel-agent.nix
index 4185cd76c4eb0..67dc1bc19edd8 100644
--- a/nixos/modules/services/monitoring/mackerel-agent.nix
+++ b/nixos/modules/services/monitoring/mackerel-agent.nix
@@ -11,7 +11,7 @@ in {
 
     # the upstream package runs as root, but doesn't seem to be strictly
     # necessary for basic functionality
-    runAsRoot = mkEnableOption (lib.mdDoc "Whether to run as root.");
+    runAsRoot = mkEnableOption (lib.mdDoc "Whether to run as root");
 
     autoRetirement = mkEnableOption (lib.mdDoc ''
       Whether to automatically retire the host upon OS shutdown.
diff --git a/nixos/modules/services/monitoring/mimir.nix b/nixos/modules/services/monitoring/mimir.nix
index 568066990f23e..edca9b7be4ff0 100644
--- a/nixos/modules/services/monitoring/mimir.nix
+++ b/nixos/modules/services/monitoring/mimir.nix
@@ -25,6 +25,13 @@ in {
         Specify a configuration file that Mimir should use.
       '';
     };
+
+    package = mkOption {
+      default = pkgs.mimir;
+      defaultText = lib.literalExpression "pkgs.mimir";
+      type = types.package;
+      description = lib.mdDoc ''Mimir package to use.'';
+    };
   };
 
   config = mkIf cfg.enable {
@@ -53,7 +60,7 @@ in {
                else cfg.configFile;
       in
       {
-        ExecStart = "${pkgs.mimir}/bin/mimir --config.file=${conf}";
+        ExecStart = "${cfg.package}/bin/mimir --config.file=${conf}";
         DynamicUser = true;
         Restart = "always";
         ProtectSystem = "full";
diff --git a/nixos/modules/services/monitoring/parsedmarc.nix b/nixos/modules/services/monitoring/parsedmarc.nix
index 2e7c4fd00b426..44fc359b6a7de 100644
--- a/nixos/modules/services/monitoring/parsedmarc.nix
+++ b/nixos/modules/services/monitoring/parsedmarc.nix
@@ -409,7 +409,7 @@ in
 
       provision = {
         enable = cfg.provision.grafana.datasource || cfg.provision.grafana.dashboard;
-        datasources =
+        datasources.settings.datasources =
           let
             esVersion = lib.getVersion config.services.elasticsearch.package;
           in
@@ -435,7 +435,7 @@ in
                 };
               }
             ];
-        dashboards = lib.mkIf cfg.provision.grafana.dashboard [{
+        dashboards.settings.providers = lib.mkIf cfg.provision.grafana.dashboard [{
           name = "parsedmarc";
           options.path = "${pkgs.python3Packages.parsedmarc.dashboard}";
         }];
@@ -539,6 +539,6 @@ in
     };
   };
 
-  meta.doc = ./parsedmarc.xml;
+  meta.doc = ./parsedmarc.md;
   meta.maintainers = [ lib.maintainers.talyz ];
 }
diff --git a/nixos/modules/services/monitoring/parsedmarc.xml b/nixos/modules/services/monitoring/parsedmarc.xml
deleted file mode 100644
index 4d9b12c9a4293..0000000000000
--- a/nixos/modules/services/monitoring/parsedmarc.xml
+++ /dev/null
@@ -1,126 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-parsedmarc">
-  <title>parsedmarc</title>
-  <para>
-    <link xlink:href="https://domainaware.github.io/parsedmarc/">parsedmarc</link>
-    is a service which parses incoming
-    <link xlink:href="https://dmarc.org/">DMARC</link> reports and
-    stores or sends them to a downstream service for further analysis.
-    In combination with Elasticsearch, Grafana and the included Grafana
-    dashboard, it provides a handy overview of DMARC reports over time.
-  </para>
-  <section xml:id="module-services-parsedmarc-basic-usage">
-    <title>Basic usage</title>
-    <para>
-      A very minimal setup which reads incoming reports from an external
-      email address and saves them to a local Elasticsearch instance
-      looks like this:
-    </para>
-    <programlisting language="nix">
-services.parsedmarc = {
-  enable = true;
-  settings.imap = {
-    host = &quot;imap.example.com&quot;;
-    user = &quot;alice@example.com&quot;;
-    password = &quot;/path/to/imap_password_file&quot;;
-  };
-  provision.geoIp = false; # Not recommended!
-};
-</programlisting>
-    <para>
-      Note that GeoIP provisioning is disabled in the example for
-      simplicity, but should be turned on for fully functional reports.
-    </para>
-  </section>
-  <section xml:id="module-services-parsedmarc-local-mail">
-    <title>Local mail</title>
-    <para>
-      Instead of watching an external inbox, a local inbox can be
-      automatically provisioned. The recipient’s name is by default set
-      to <literal>dmarc</literal>, but can be configured in
-      <link xlink:href="options.html#opt-services.parsedmarc.provision.localMail.recipientName">services.parsedmarc.provision.localMail.recipientName</link>.
-      You need to add an MX record pointing to the host. More
-      concretely: for the example to work, an MX record needs to be set
-      up for <literal>monitoring.example.com</literal> and the complete
-      email address that should be configured in the domain’s dmarc
-      policy is <literal>dmarc@monitoring.example.com</literal>.
-    </para>
-    <programlisting language="nix">
-services.parsedmarc = {
-  enable = true;
-  provision = {
-    localMail = {
-      enable = true;
-      hostname = monitoring.example.com;
-    };
-    geoIp = false; # Not recommended!
-  };
-};
-</programlisting>
-  </section>
-  <section xml:id="module-services-parsedmarc-grafana-geoip">
-    <title>Grafana and GeoIP</title>
-    <para>
-      The reports can be visualized and summarized with parsedmarc’s
-      official Grafana dashboard. For all views to work, and for the
-      data to be complete, GeoIP databases are also required. The
-      following example shows a basic deployment where the provisioned
-      Elasticsearch instance is automatically added as a Grafana
-      datasource, and the dashboard is added to Grafana as well.
-    </para>
-    <programlisting language="nix">
-services.parsedmarc = {
-  enable = true;
-  provision = {
-    localMail = {
-      enable = true;
-      hostname = url;
-    };
-    grafana = {
-      datasource = true;
-      dashboard = true;
-    };
-  };
-};
-
-# Not required, but recommended for full functionality
-services.geoipupdate = {
-  settings = {
-    AccountID = 000000;
-    LicenseKey = &quot;/path/to/license_key_file&quot;;
-  };
-};
-
-services.grafana = {
-  enable = true;
-  addr = &quot;0.0.0.0&quot;;
-  domain = url;
-  rootUrl = &quot;https://&quot; + url;
-  protocol = &quot;socket&quot;;
-  security = {
-    adminUser = &quot;admin&quot;;
-    adminPasswordFile = &quot;/path/to/admin_password_file&quot;;
-    secretKeyFile = &quot;/path/to/secret_key_file&quot;;
-  };
-};
-
-services.nginx = {
-  enable = true;
-  recommendedTlsSettings = true;
-  recommendedOptimisation = true;
-  recommendedGzipSettings = true;
-  recommendedProxySettings = true;
-  upstreams.grafana.servers.&quot;unix:/${config.services.grafana.socket}&quot; = {};
-  virtualHosts.${url} = {
-    root = config.services.grafana.staticRootPath;
-    enableACME = true;
-    forceSSL = true;
-    locations.&quot;/&quot;.tryFiles = &quot;$uri @grafana&quot;;
-    locations.&quot;@grafana&quot;.proxyPass = &quot;http://grafana&quot;;
-  };
-};
-users.users.nginx.extraGroups = [ &quot;grafana&quot; ];
-</programlisting>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix
index f3fbfb149ad77..fd40dce1410c1 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters.nix
@@ -64,6 +64,7 @@ let
     "rspamd"
     "rtl_433"
     "script"
+    "shelly"
     "snmp"
     "smartctl"
     "smokeping"
@@ -323,7 +324,7 @@ in
   );
 
   meta = {
-    doc = ./exporters.xml;
+    doc = ./exporters.md;
     maintainers = [ maintainers.willibutz ];
   };
 }
diff --git a/nixos/modules/services/monitoring/prometheus/exporters.xml b/nixos/modules/services/monitoring/prometheus/exporters.xml
deleted file mode 100644
index 0ea95e513ff33..0000000000000
--- a/nixos/modules/services/monitoring/prometheus/exporters.xml
+++ /dev/null
@@ -1,245 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-prometheus-exporters">
-  <title>Prometheus exporters</title>
-  <para>
-    Prometheus exporters provide metrics for the
-    <link xlink:href="https://prometheus.io">prometheus monitoring
-    system</link>.
-  </para>
-  <section xml:id="module-services-prometheus-exporters-configuration">
-    <title>Configuration</title>
-    <para>
-      One of the most common exporters is the
-      <link xlink:href="https://github.com/prometheus/node_exporter">node
-      exporter</link>, it provides hardware and OS metrics from the host
-      it’s running on. The exporter could be configured as follows:
-    </para>
-    <programlisting>
-  services.prometheus.exporters.node = {
-    enable = true;
-    port = 9100;
-    enabledCollectors = [
-      &quot;logind&quot;
-      &quot;systemd&quot;
-    ];
-    disabledCollectors = [
-      &quot;textfile&quot;
-    ];
-    openFirewall = true;
-    firewallFilter = &quot;-i br0 -p tcp -m tcp --dport 9100&quot;;
-  };
-</programlisting>
-    <para>
-      It should now serve all metrics from the collectors that are
-      explicitly enabled and the ones that are
-      <link xlink:href="https://github.com/prometheus/node_exporter#enabled-by-default">enabled
-      by default</link>, via http under <literal>/metrics</literal>. In
-      this example the firewall should just allow incoming connections
-      to the exporter’s port on the bridge interface
-      <literal>br0</literal> (this would have to be configured
-      separately of course). For more information about configuration
-      see <literal>man configuration.nix</literal> or search through the
-      <link xlink:href="https://nixos.org/nixos/options.html#prometheus.exporters">available
-      options</link>.
-    </para>
-    <para>
-      Prometheus can now be configured to consume the metrics produced
-      by the exporter:
-    </para>
-    <programlisting>
-    services.prometheus = {
-      # ...
-
-      scrapeConfigs = [
-        {
-          job_name = &quot;node&quot;;
-          static_configs = [{
-            targets = [ &quot;localhost:${toString config.services.prometheus.exporters.node.port}&quot; ];
-          }];
-        }
-      ];
-
-      # ...
-    }
-</programlisting>
-  </section>
-  <section xml:id="module-services-prometheus-exporters-new-exporter">
-    <title>Adding a new exporter</title>
-    <para>
-      To add a new exporter, it has to be packaged first (see
-      <literal>nixpkgs/pkgs/servers/monitoring/prometheus/</literal> for
-      examples), then a module can be added. The postfix exporter is
-      used in this example:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          Some default options for all exporters are provided by
-          <literal>nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix</literal>:
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              <literal>enable</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>port</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>listenAddress</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>extraFlags</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>openFirewall</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>firewallFilter</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>user</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>group</literal>
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          As there is already a package available, the module can now be
-          added. This is accomplished by adding a new file to the
-          <literal>nixos/modules/services/monitoring/prometheus/exporters/</literal>
-          directory, which will be called postfix.nix and contains all
-          exporter specific options and configuration:
-        </para>
-        <programlisting>
-# nixpgs/nixos/modules/services/prometheus/exporters/postfix.nix
-{ config, lib, pkgs, options }:
-
-with lib;
-
-let
-  # for convenience we define cfg here
-  cfg = config.services.prometheus.exporters.postfix;
-in
-{
-  port = 9154; # The postfix exporter listens on this port by default
-
-  # `extraOpts` is an attribute set which contains additional options
-  # (and optional overrides for default options).
-  # Note that this attribute is optional.
-  extraOpts = {
-    telemetryPath = mkOption {
-      type = types.str;
-      default = &quot;/metrics&quot;;
-      description = ''
-        Path under which to expose metrics.
-      '';
-    };
-    logfilePath = mkOption {
-      type = types.path;
-      default = /var/log/postfix_exporter_input.log;
-      example = /var/log/mail.log;
-      description = ''
-        Path where Postfix writes log entries.
-        This file will be truncated by this exporter!
-      '';
-    };
-    showqPath = mkOption {
-      type = types.path;
-      default = /var/spool/postfix/public/showq;
-      example = /var/lib/postfix/queue/public/showq;
-      description = ''
-        Path at which Postfix places its showq socket.
-      '';
-    };
-  };
-
-  # `serviceOpts` is an attribute set which contains configuration
-  # for the exporter's systemd service. One of
-  # `serviceOpts.script` and `serviceOpts.serviceConfig.ExecStart`
-  # has to be specified here. This will be merged with the default
-  # service configuration.
-  # Note that by default 'DynamicUser' is 'true'.
-  serviceOpts = {
-    serviceConfig = {
-      DynamicUser = false;
-      ExecStart = ''
-        ${pkgs.prometheus-postfix-exporter}/bin/postfix_exporter \
-          --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
-          --web.telemetry-path ${cfg.telemetryPath} \
-          ${concatStringsSep &quot; \\\n  &quot; cfg.extraFlags}
-      '';
-    };
-  };
-}
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          This should already be enough for the postfix exporter.
-          Additionally one could now add assertions and conditional
-          default values. This can be done in the
-          <quote>meta-module</quote> that combines all exporter
-          definitions and generates the submodules:
-          <literal>nixpkgs/nixos/modules/services/prometheus/exporters.nix</literal>
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="module-services-prometheus-exporters-update-exporter-module">
-    <title>Updating an exporter module</title>
-    <para>
-      Should an exporter option change at some point, it is possible to
-      add information about the change to the exporter definition
-      similar to <literal>nixpkgs/nixos/modules/rename.nix</literal>:
-    </para>
-    <programlisting>
-{ config, lib, pkgs, options }:
-
-with lib;
-
-let
-  cfg = config.services.prometheus.exporters.nginx;
-in
-{
-  port = 9113;
-  extraOpts = {
-    # additional module options
-    # ...
-  };
-  serviceOpts = {
-    # service configuration
-    # ...
-  };
-  imports = [
-    # 'services.prometheus.exporters.nginx.telemetryEndpoint' -&gt; 'services.prometheus.exporters.nginx.telemetryPath'
-    (mkRenamedOptionModule [ &quot;telemetryEndpoint&quot; ] [ &quot;telemetryPath&quot; ])
-
-    # removed option 'services.prometheus.exporters.nginx.insecure'
-    (mkRemovedOptionModule [ &quot;insecure&quot; ] ''
-      This option was replaced by 'prometheus.exporters.nginx.sslVerify' which defaults to true.
-    '')
-    ({ options.warnings = options.warnings; })
-  ];
-}
-</programlisting>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/pihole.nix b/nixos/modules/services/monitoring/prometheus/exporters/pihole.nix
index 537d72e85c8f0..6f403b3e58c81 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/pihole.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/pihole.nix
@@ -6,6 +6,11 @@ let
   cfg = config.services.prometheus.exporters.pihole;
 in
 {
+  imports = [
+    (mkRemovedOptionModule [ "interval"] "This option has been removed.")
+    ({ options.warnings = options.warnings; options.assertions = options.assertions; })
+  ];
+
   port = 9617;
   extraOpts = {
     apiToken = mkOption {
@@ -13,15 +18,7 @@ in
       default = "";
       example = "580a770cb40511eb85290242ac130003580a770cb40511eb85290242ac130003";
       description = lib.mdDoc ''
-        pi-hole API token which can be used instead of a password
-      '';
-    };
-    interval = mkOption {
-      type = types.str;
-      default = "10s";
-      example = "30s";
-      description = lib.mdDoc ''
-        How often to scrape new data
+        Pi-Hole API token which can be used instead of a password
       '';
     };
     password = mkOption {
@@ -29,7 +26,7 @@ in
       default = "";
       example = "password";
       description = lib.mdDoc ''
-        The password to login into pihole. An api token can be used instead.
+        The password to login into Pi-Hole. An api token can be used instead.
       '';
     };
     piholeHostname = mkOption {
@@ -37,7 +34,7 @@ in
       default = "pihole";
       example = "127.0.0.1";
       description = lib.mdDoc ''
-        Hostname or address where to find the pihole webinterface
+        Hostname or address where to find the Pi-Hole webinterface
       '';
     };
     piholePort = mkOption {
@@ -45,7 +42,7 @@ in
       default = 80;
       example = 443;
       description = lib.mdDoc ''
-        The port pihole webinterface is reachable on
+        The port Pi-Hole webinterface is reachable on
       '';
     };
     protocol = mkOption {
@@ -53,21 +50,28 @@ in
       default = "http";
       example = "https";
       description = lib.mdDoc ''
-        The protocol which is used to connect to pihole
+        The protocol which is used to connect to Pi-Hole
+      '';
+    };
+    timeout = mkOption {
+      type = types.str;
+      default = "5s";
+      description = lib.mdDoc ''
+        Controls the timeout to connect to a Pi-Hole instance
       '';
     };
   };
   serviceOpts = {
     serviceConfig = {
       ExecStart = ''
-        ${pkgs.bash}/bin/bash -c "${pkgs.prometheus-pihole-exporter}/bin/pihole-exporter \
-          -interval ${cfg.interval} \
+        ${pkgs.prometheus-pihole-exporter}/bin/pihole-exporter \
           ${optionalString (cfg.apiToken != "") "-pihole_api_token ${cfg.apiToken}"} \
           -pihole_hostname ${cfg.piholeHostname} \
           ${optionalString (cfg.password != "") "-pihole_password ${cfg.password}"} \
           -pihole_port ${toString cfg.piholePort} \
           -pihole_protocol ${cfg.protocol} \
-          -port ${toString cfg.port}"
+          -port ${toString cfg.port} \
+          -timeout ${cfg.timeout}
       '';
     };
   };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/shelly.nix b/nixos/modules/services/monitoring/prometheus/exporters/shelly.nix
new file mode 100644
index 0000000000000..b9cfd1b1e84a9
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/shelly.nix
@@ -0,0 +1,27 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.shelly;
+in
+{
+  port = 9784;
+  extraOpts = {
+    metrics-file = mkOption {
+      type = types.path;
+      description = lib.mdDoc ''
+        Path to the JSON file with the metric definitions
+      '';
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      ExecStart = ''
+        ${pkgs.prometheus-shelly-exporter}/bin/shelly_exporter \
+          -metrics-file ${cfg.metrics-file} \
+          -listen-address ${cfg.listenAddress}:${toString cfg.port}
+      '';
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/unpoller.nix b/nixos/modules/services/monitoring/prometheus/exporters/unpoller.nix
index 5cd1e2c65e906..3b7f978528cd1 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/unpoller.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/unpoller.nix
@@ -24,9 +24,9 @@ in {
     inherit (options.services.unpoller.unifi) controllers;
     inherit (options.services.unpoller) loki;
     log = {
-      debug = mkEnableOption (lib.mdDoc "debug logging including line numbers, high resolution timestamps, per-device logs.");
-      quiet = mkEnableOption (lib.mdDoc "startup and error logs only.");
-      prometheusErrors = mkEnableOption (lib.mdDoc "emitting errors to prometheus.");
+      debug = mkEnableOption (lib.mdDoc "debug logging including line numbers, high resolution timestamps, per-device logs");
+      quiet = mkEnableOption (lib.mdDoc "startup and error logs only");
+      prometheusErrors = mkEnableOption (lib.mdDoc "emitting errors to prometheus");
     };
   };
 
diff --git a/nixos/modules/services/monitoring/uptime-kuma.nix b/nixos/modules/services/monitoring/uptime-kuma.nix
index 886e14b5f6c9f..5f803d57b5e97 100644
--- a/nixos/modules/services/monitoring/uptime-kuma.nix
+++ b/nixos/modules/services/monitoring/uptime-kuma.nix
@@ -11,7 +11,7 @@ in
 
   options = {
     services.uptime-kuma = {
-      enable = mkEnableOption (mdDoc "Uptime Kuma, this assumes a reverse proxy to be set.");
+      enable = mkEnableOption (mdDoc "Uptime Kuma, this assumes a reverse proxy to be set");
 
       package = mkOption {
         type = types.package;
@@ -20,7 +20,7 @@ in
         description = lib.mdDoc "Uptime Kuma package to use.";
       };
 
-      appriseSupport = mkEnableOption (mdDoc "apprise support for notifications.");
+      appriseSupport = mkEnableOption (mdDoc "apprise support for notifications");
 
       settings = lib.mkOption {
         type = lib.types.submodule { freeformType = with lib.types; attrsOf str; };
diff --git a/nixos/modules/services/network-filesystems/kubo.nix b/nixos/modules/services/network-filesystems/kubo.nix
index 13a062c32128a..4d423c9059866 100644
--- a/nixos/modules/services/network-filesystems/kubo.nix
+++ b/nixos/modules/services/network-filesystems/kubo.nix
@@ -5,6 +5,23 @@ let
 
   settingsFormat = pkgs.formats.json {};
 
+  rawDefaultConfig = lib.importJSON (pkgs.runCommand "kubo-default-config" {
+    nativeBuildInputs = [ cfg.package ];
+  } ''
+    export IPFS_PATH="$TMPDIR"
+    ipfs init --empty-repo --profile=${profile}
+    ipfs --offline config show > "$out"
+  '');
+
+  # Remove the PeerID (an attribute of "Identity") of the temporary Kubo repo.
+  # The "Pinning" section contains the "RemoteServices" section, which would prevent
+  # the daemon from starting as that setting can't be changed via ipfs config replace.
+  defaultConfig = builtins.removeAttrs rawDefaultConfig [ "Identity" "Pinning" ];
+
+  customizedConfig = lib.recursiveUpdate defaultConfig cfg.settings;
+
+  configFile = settingsFormat.generate "kubo-config.json" customizedConfig;
+
   kuboFlags = utils.escapeSystemdExecArgs (
     optional cfg.autoMount "--mount" ++
     optional cfg.enableGC "--enable-gc" ++
@@ -161,9 +178,9 @@ in
           };
         };
         description = lib.mdDoc ''
-          Attrset of daemon configuration to set using {command}`ipfs config`, every time the daemon starts.
+          Attrset of daemon configuration.
           See [https://github.com/ipfs/kubo/blob/master/docs/config.md](https://github.com/ipfs/kubo/blob/master/docs/config.md) for reference.
-          Keep in mind that this configuration is stateful; i.e., unsetting anything in here does not reset the value to the default!
+          You can't set `Identity` or `Pinning`.
         '';
         default = { };
         example = {
@@ -211,6 +228,21 @@ in
   ###### implementation
 
   config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = !builtins.hasAttr "Identity" cfg.settings;
+        message = ''
+          You can't set services.kubo.settings.Identity because the ``config replace`` subcommand used at startup does not support modifying any of the Identity settings.
+        '';
+      }
+      {
+        assertion = !((builtins.hasAttr "Pinning" cfg.settings) && (builtins.hasAttr "RemoteServices" cfg.settings.Pinning));
+        message = ''
+          You can't set services.kubo.settings.Pinning.RemoteServices because the ``config replace`` subcommand used at startup does not work with it.
+        '';
+      }
+    ];
+
     environment.systemPackages = [ cfg.package ];
     environment.variables.IPFS_PATH = cfg.dataDir;
 
@@ -262,21 +294,26 @@ in
 
       preStart = ''
         if [[ ! -f "$IPFS_PATH/config" ]]; then
-          ipfs init ${optionalString cfg.emptyRepo "-e"} --profile=${profile}
+          ipfs init ${optionalString cfg.emptyRepo "-e"}
         else
           # After an unclean shutdown this file may exist which will cause the config command to attempt to talk to the daemon. This will hang forever if systemd is holding our sockets open.
           rm -vf "$IPFS_PATH/api"
       '' + optionalString cfg.autoMigrate ''
         ${pkgs.kubo-migrator}/bin/fs-repo-migrations -to '${cfg.package.repoVersion}' -y
       '' + ''
-          ipfs --offline config profile apply ${profile} >/dev/null
         fi
-      '' + ''
-        ipfs --offline config show \
-          | ${pkgs.jq}/bin/jq '. * $settings' --argjson settings ${
-              escapeShellArg (builtins.toJSON cfg.settings)
-            } \
-          | ipfs --offline config replace -
+        ipfs --offline config show |
+          ${pkgs.jq}/bin/jq -s '.[0].Pinning as $Pinning | .[0].Identity as $Identity | .[1] + {$Identity,$Pinning}' - '${configFile}' |
+
+          # This command automatically injects the private key and other secrets from
+          # the old config file back into the new config file.
+          # Unfortunately, it doesn't keep the original `Identity.PeerID`,
+          # so we need `ipfs config show` and jq above.
+          # See https://github.com/ipfs/kubo/issues/8993 for progress on fixing this problem.
+          # Kubo also wants a specific version of the original "Pinning.RemoteServices"
+          # section (redacted by `ipfs config show`), such that that section doesn't
+          # change when the changes are applied. Whyyyyyy.....
+          ipfs --offline config replace -
       '';
       serviceConfig = {
         ExecStart = [ "" "${cfg.package}/bin/ipfs daemon ${kuboFlags}" ];
diff --git a/nixos/modules/services/network-filesystems/litestream/default.nix b/nixos/modules/services/network-filesystems/litestream/default.nix
index 0d987f12a3242..6e2ec1ccaa3c7 100644
--- a/nixos/modules/services/network-filesystems/litestream/default.nix
+++ b/nixos/modules/services/network-filesystems/litestream/default.nix
@@ -95,5 +95,5 @@ in
     users.groups.litestream = {};
   };
 
-  meta.doc = ./default.xml;
+  meta.doc = ./default.md;
 }
diff --git a/nixos/modules/services/network-filesystems/litestream/default.xml b/nixos/modules/services/network-filesystems/litestream/default.xml
deleted file mode 100644
index 756899fdb88d9..0000000000000
--- a/nixos/modules/services/network-filesystems/litestream/default.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-litestream">
-  <title>Litestream</title>
-  <para>
-    <link xlink:href="https://litestream.io/">Litestream</link> is a
-    standalone streaming replication tool for SQLite.
-  </para>
-  <section xml:id="module-services-litestream-configuration">
-    <title>Configuration</title>
-    <para>
-      Litestream service is managed by a dedicated user named
-      <literal>litestream</literal> which needs permission to the
-      database file. Here’s an example config which gives required
-      permissions to access
-      <link linkend="opt-services.grafana.settings.database.path">grafana
-      database</link>:
-    </para>
-    <programlisting>
-{ pkgs, ... }:
-{
-  users.users.litestream.extraGroups = [ &quot;grafana&quot; ];
-
-  systemd.services.grafana.serviceConfig.ExecStartPost = &quot;+&quot; + pkgs.writeShellScript &quot;grant-grafana-permissions&quot; ''
-    timeout=10
-
-    while [ ! -f /var/lib/grafana/data/grafana.db ];
-    do
-      if [ &quot;$timeout&quot; == 0 ]; then
-        echo &quot;ERROR: Timeout while waiting for /var/lib/grafana/data/grafana.db.&quot;
-        exit 1
-      fi
-
-      sleep 1
-
-      ((timeout--))
-    done
-
-    find /var/lib/grafana -type d -exec chmod -v 775 {} \;
-    find /var/lib/grafana -type f -exec chmod -v 660 {} \;
-  '';
-
-  services.litestream = {
-    enable = true;
-
-    environmentFile = &quot;/run/secrets/litestream&quot;;
-
-    settings = {
-      dbs = [
-        {
-          path = &quot;/var/lib/grafana/data/grafana.db&quot;;
-          replicas = [{
-            url = &quot;s3://mybkt.litestream.io/grafana&quot;;
-          }];
-        }
-      ];
-    };
-  };
-}
-</programlisting>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/network-filesystems/moosefs.nix b/nixos/modules/services/network-filesystems/moosefs.nix
index ab82a2a07dd4b..49cbc89d5a91f 100644
--- a/nixos/modules/services/network-filesystems/moosefs.nix
+++ b/nixos/modules/services/network-filesystems/moosefs.nix
@@ -85,7 +85,7 @@ in {
         description = lib.mdDoc "Run daemons as user moosefs instead of root.";
       };
 
-      client.enable = mkEnableOption (lib.mdDoc "Moosefs client.");
+      client.enable = mkEnableOption (lib.mdDoc "Moosefs client");
 
       master = {
         enable = mkOption {
@@ -131,7 +131,7 @@ in {
       };
 
       metalogger = {
-        enable = mkEnableOption (lib.mdDoc "Moosefs metalogger daemon.");
+        enable = mkEnableOption (lib.mdDoc "Moosefs metalogger daemon");
 
         settings = mkOption {
           type = types.submodule {
@@ -149,7 +149,7 @@ in {
       };
 
       chunkserver = {
-        enable = mkEnableOption (lib.mdDoc "Moosefs chunkserver daemon.");
+        enable = mkEnableOption (lib.mdDoc "Moosefs chunkserver daemon");
 
         openFirewall = mkOption {
           type = types.bool;
diff --git a/nixos/modules/services/networking/avahi-daemon.nix b/nixos/modules/services/networking/avahi-daemon.nix
index 28dd24f4e7c0c..103f73fdaa685 100644
--- a/nixos/modules/services/networking/avahi-daemon.nix
+++ b/nixos/modules/services/networking/avahi-daemon.nix
@@ -275,6 +275,7 @@ in
         BusName = "org.freedesktop.Avahi";
         Type = "dbus";
         ExecStart = "${pkgs.avahi}/sbin/avahi-daemon --syslog -f ${avahiDaemonConf}";
+        ConfigurationDirectory = "avahi/services";
       };
     };
 
diff --git a/nixos/modules/services/networking/blockbook-frontend.nix b/nixos/modules/services/networking/blockbook-frontend.nix
index ab784563e4acf..46b26195d2113 100644
--- a/nixos/modules/services/networking/blockbook-frontend.nix
+++ b/nixos/modules/services/networking/blockbook-frontend.nix
@@ -10,7 +10,7 @@ let
 
     options = {
 
-      enable = mkEnableOption (lib.mdDoc "blockbook-frontend application.");
+      enable = mkEnableOption (lib.mdDoc "blockbook-frontend application");
 
       package = mkOption {
         type = types.package;
diff --git a/nixos/modules/services/networking/envoy.nix b/nixos/modules/services/networking/envoy.nix
index 20cfebb799144..c68ceab9619c4 100644
--- a/nixos/modules/services/networking/envoy.nix
+++ b/nixos/modules/services/networking/envoy.nix
@@ -6,18 +6,29 @@ let
   cfg = config.services.envoy;
   format = pkgs.formats.json { };
   conf = format.generate "envoy.json" cfg.settings;
-  validateConfig = file:
+  validateConfig = required: file:
     pkgs.runCommand "validate-envoy-conf" { } ''
-      ${pkgs.envoy}/bin/envoy --log-level error --mode validate -c "${file}"
+      ${cfg.package}/bin/envoy --log-level error --mode validate -c "${file}" ${lib.optionalString (!required) "|| true"}
       cp "${file}" "$out"
     '';
-
 in
 
 {
   options.services.envoy = {
     enable = mkEnableOption (lib.mdDoc "Envoy reverse proxy");
 
+    package = mkPackageOptionMD pkgs "envoy" { };
+
+    requireValidConfig = mkOption {
+      type = types.bool;
+      default = true;
+      description = lib.mdDoc ''
+        Whether a failure during config validation at build time is fatal.
+        When the config can't be checked during build time, for example when it includes
+        other files, disable this option.
+      '';
+    };
+
     settings = mkOption {
       type = format.type;
       default = { };
@@ -46,38 +57,44 @@ in
   };
 
   config = mkIf cfg.enable {
-    environment.systemPackages = [ pkgs.envoy ];
+    environment.systemPackages = [ cfg.package ];
     systemd.services.envoy = {
       description = "Envoy reverse proxy";
       after = [ "network-online.target" ];
       requires = [ "network-online.target" ];
       wantedBy = [ "multi-user.target" ];
       serviceConfig = {
-        ExecStart = "${pkgs.envoy}/bin/envoy -c ${validateConfig conf}";
-        DynamicUser = true;
+        ExecStart = "${cfg.package}/bin/envoy -c ${validateConfig cfg.requireValidConfig conf}";
+        CacheDirectory = [ "envoy" ];
+        LogsDirectory = [ "envoy" ];
         Restart = "no";
-        CacheDirectory = "envoy";
-        LogsDirectory = "envoy";
-        AmbientCapabilities = "CAP_NET_BIND_SERVICE";
-        CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
-        RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK AF_XDP";
-        SystemCallArchitectures = "native";
+        # Hardening
+        AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
+        CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
+        DeviceAllow = [ "" ];
+        DevicePolicy = "closed";
+        DynamicUser = true;
         LockPersonality = true;
-        RestrictNamespaces = true;
-        RestrictRealtime = true;
-        PrivateUsers = false;  # breaks CAP_NET_BIND_SERVICE
+        MemoryDenyWriteExecute = false; # at least wasmr needs WX permission
         PrivateDevices = true;
+        PrivateUsers = false; # breaks CAP_NET_BIND_SERVICE
+        ProcSubset = "pid";
         ProtectClock = true;
         ProtectControlGroups = true;
         ProtectHome = true;
+        ProtectHostname = true;
         ProtectKernelLogs = true;
         ProtectKernelModules = true;
         ProtectKernelTunables = true;
         ProtectProc = "ptraceable";
-        ProtectHostname = true;
         ProtectSystem = "strict";
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" "AF_XDP" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        SystemCallArchitectures = "native";
+        SystemCallErrorNumber = "EPERM";
+        SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
         UMask = "0066";
-        SystemCallFilter = "~@clock @module @mount @reboot @swap @obsolete @cpu-emulation";
       };
     };
   };
diff --git a/nixos/modules/services/networking/firefox-syncserver.nix b/nixos/modules/services/networking/firefox-syncserver.nix
index c26a6ae265ffe..42924d7f69938 100644
--- a/nixos/modules/services/networking/firefox-syncserver.nix
+++ b/nixos/modules/services/networking/firefox-syncserver.nix
@@ -304,6 +304,10 @@ in
         forceSSL = cfg.singleNode.enableTLS;
         locations."/" = {
           proxyPass = "http://127.0.0.1:${toString cfg.settings.port}";
+          # We need to pass the Host header that matches the original Host header. Otherwise,
+          # Hawk authentication will fail (because it assumes that the client and server see
+          # the same value of the Host header).
+          recommendedProxySettings = true;
         };
       };
     };
@@ -311,6 +315,6 @@ in
 
   meta = {
     maintainers = with lib.maintainers; [ pennae ];
-    doc = ./firefox-syncserver.xml;
+    doc = ./firefox-syncserver.md;
   };
 }
diff --git a/nixos/modules/services/networking/firefox-syncserver.xml b/nixos/modules/services/networking/firefox-syncserver.xml
deleted file mode 100644
index 440922cbba00f..0000000000000
--- a/nixos/modules/services/networking/firefox-syncserver.xml
+++ /dev/null
@@ -1,79 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-firefox-syncserver">
-  <title>Firefox Sync server</title>
-  <para>
-    A storage server for Firefox Sync that you can easily host yourself.
-  </para>
-  <section xml:id="module-services-firefox-syncserver-quickstart">
-    <title>Quickstart</title>
-    <para>
-      The absolute minimal configuration for the sync server looks like
-      this:
-    </para>
-    <programlisting language="nix">
-services.mysql.package = pkgs.mariadb;
-
-services.firefox-syncserver = {
-  enable = true;
-  secrets = builtins.toFile &quot;sync-secrets&quot; ''
-    SYNC_MASTER_SECRET=this-secret-is-actually-leaked-to-/nix/store
-  '';
-  singleNode = {
-    enable = true;
-    hostname = &quot;localhost&quot;;
-    url = &quot;http://localhost:5000&quot;;
-  };
-};
-</programlisting>
-    <para>
-      This will start a sync server that is only accessible locally.
-      Once the services is running you can navigate to
-      <literal>about:config</literal> in your Firefox profile and set
-      <literal>identity.sync.tokenserver.uri</literal> to
-      <literal>http://localhost:5000/1.0/sync/1.5</literal>. Your
-      browser will now use your local sync server for data storage.
-    </para>
-    <warning>
-      <para>
-        This configuration should never be used in production. It is not
-        encrypted and stores its secrets in a world-readable location.
-      </para>
-    </warning>
-  </section>
-  <section xml:id="module-services-firefox-syncserver-configuration">
-    <title>More detailed setup</title>
-    <para>
-      The <literal>firefox-syncserver</literal> service provides a
-      number of options to make setting up small deployment easier.
-      These are grouped under the <literal>singleNode</literal> element
-      of the option tree and allow simple configuration of the most
-      important parameters.
-    </para>
-    <para>
-      Single node setup is split into two kinds of options: those that
-      affect the sync server itself, and those that affect its
-      surroundings. Options that affect the sync server are
-      <literal>capacity</literal>, which configures how many accounts
-      may be active on this instance, and <literal>url</literal>, which
-      holds the URL under which the sync server can be accessed. The
-      <literal>url</literal> can be configured automatically when using
-      nginx.
-    </para>
-    <para>
-      Options that affect the surroundings of the sync server are
-      <literal>enableNginx</literal>, <literal>enableTLS</literal> and
-      <literal>hostnam</literal>. If <literal>enableNginx</literal> is
-      set the sync server module will automatically add an nginx virtual
-      host to the system using <literal>hostname</literal> as the domain
-      and set <literal>url</literal> accordingly. If
-      <literal>enableTLS</literal> is set the module will also enable
-      ACME certificates on the new virtual host and force all
-      connections to be made via TLS.
-    </para>
-    <para>
-      For actual deployment it is also recommended to store the
-      <literal>secrets</literal> file in a secure location.
-    </para>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/networking/headscale.nix b/nixos/modules/services/networking/headscale.nix
index cc46819eed5a6..390a448ab5842 100644
--- a/nixos/modules/services/networking/headscale.nix
+++ b/nixos/modules/services/networking/headscale.nix
@@ -299,17 +299,51 @@ in {
                 '';
               };
 
-              domain_map = mkOption {
+              scope = mkOption {
+                type = types.listOf types.str;
+                default = ["openid" "profile" "email"];
+                description = lib.mdDoc ''
+                  Scopes used in the OIDC flow.
+                '';
+              };
+
+              extra_params = mkOption {
                 type = types.attrsOf types.str;
-                default = {};
+                default = { };
                 description = lib.mdDoc ''
-                  Domain map is used to map incomming users (by their email) to
-                  a namespace. The key can be a string, or regex.
+                  Custom query parameters to send with the Authorize Endpoint request.
                 '';
                 example = {
-                  ".*" = "default-namespace";
+                  domain_hint = "example.com";
                 };
               };
+
+              allowed_domains = mkOption {
+                type = types.listOf types.str;
+                default = [ ];
+                description = lib.mdDoc ''
+                  Allowed principal domains. if an authenticated user's domain
+                  is not in this list authentication request will be rejected.
+                '';
+                example = [ "example.com" ];
+              };
+
+              allowed_users = mkOption {
+                type = types.listOf types.str;
+                default = [ ];
+                description = lib.mdDoc ''
+                  Users allowed to authenticate even if not in allowedDomains.
+                '';
+                example = [ "alice@example.com" ];
+              };
+
+              strip_email_domain = mkOption {
+                type = types.bool;
+                default = true;
+                description = lib.mdDoc ''
+                  Whether the domain part of the email address should be removed when generating namespaces.
+                '';
+              };
             };
 
             tls_letsencrypt_hostname = mkOption {
@@ -392,13 +426,16 @@ in {
     (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" "domainMap"] ["services" "headscale" "settings" "oidc" "domain_map"])
     (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"])
     (mkRenamedOptionModule ["services" "headscale" "tls" "certFile"] ["services" "headscale" "settings" "tls_cert_path"])
     (mkRenamedOptionModule ["services" "headscale" "tls" "keyFile"] ["services" "headscale" "settings" "tls_key_path"])
     (mkRenamedOptionModule ["services" "headscale" "aclPolicyFile"] ["services" "headscale" "settings" "acl_policy_path"])
+
+    (mkRemovedOptionModule ["services" "headscale" "openIdConnect" "domainMap"] ''
+      Headscale no longer uses domain_map. If you're using an old version of headscale you can still set this option via services.headscale.settings.oidc.domain_map.
+    '')
   ];
 
   config = mkIf cfg.enable {
diff --git a/nixos/modules/services/networking/hostapd.nix b/nixos/modules/services/networking/hostapd.nix
index f13457fcde252..ecc158f8151d1 100644
--- a/nixos/modules/services/networking/hostapd.nix
+++ b/nixos/modules/services/networking/hostapd.nix
@@ -20,6 +20,8 @@ let
     ssid=${cfg.ssid}
     hw_mode=${cfg.hwMode}
     channel=${toString cfg.channel}
+    ieee80211n=1
+    ieee80211ac=1
     ${optionalString (cfg.countryCode != null) "country_code=${cfg.countryCode}"}
     ${optionalString (cfg.countryCode != null) "ieee80211d=1"}
 
@@ -34,6 +36,7 @@ let
 
     ${optionalString cfg.wpa ''
       wpa=2
+      wpa_pairwise=CCMP
       wpa_passphrase=${cfg.wpaPassphrase}
     ''}
     ${optionalString cfg.noScan "noscan=1"}
@@ -66,7 +69,6 @@ in
       };
 
       interface = mkOption {
-        default = "";
         example = "wlp2s0";
         type = types.str;
         description = lib.mdDoc ''
diff --git a/nixos/modules/services/networking/imaginary.nix b/nixos/modules/services/networking/imaginary.nix
new file mode 100644
index 0000000000000..a655903d1031c
--- /dev/null
+++ b/nixos/modules/services/networking/imaginary.nix
@@ -0,0 +1,113 @@
+{ lib, config, pkgs, utils, ... }:
+
+let
+  inherit (lib) mdDoc mkEnableOption mkIf mkOption types;
+
+  cfg = config.services.imaginary;
+in {
+  options.services.imaginary = {
+    enable = mkEnableOption (mdDoc "imaginary image processing microservice");
+
+    address = mkOption {
+      type = types.str;
+      default = "localhost";
+      description = mdDoc ''
+        Bind address. Corresponds to the `-a` flag.
+        Set to `""` to bind to all addresses.
+      '';
+      example = "[::1]";
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 8088;
+      description = mdDoc "Bind port. Corresponds to the `-p` flag.";
+    };
+
+    settings = mkOption {
+      description = mdDoc ''
+        Command line arguments passed to the imaginary executable, stripped of
+        the prefix `-`. See upstream's
+        [README](https://github.com/h2non/imaginary#command-line-usage) for all
+        options.
+      '';
+      type = types.submodule {
+        freeformType = with types; attrsOf (oneOf [
+          bool
+          int
+          (nonEmptyListOf str)
+          str
+        ]);
+
+        options = {
+          return-size = mkOption {
+            type = types.bool;
+            default = false;
+            description = mdDoc "Return the image size in the HTTP headers.";
+          };
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [ {
+      assertion = ! lib.hasAttr "a" cfg.settings;
+      message = "Use services.imaginary.address to specify the -a flag.";
+    } {
+      assertion = ! lib.hasAttr "p" cfg.settings;
+      message = "Use services.imaginary.port to specify the -p flag.";
+    } ];
+
+    systemd.services.imaginary = {
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = rec {
+        ExecStart = let
+          args = lib.mapAttrsToList (key: val:
+            "-" + key + "=" + lib.concatStringsSep "," (map toString (lib.toList val))
+          ) (cfg.settings // { a = cfg.address; p = cfg.port; });
+        in "${pkgs.imaginary}/bin/imaginary ${utils.escapeSystemdExecArgs args}";
+        ProtectProc = "invisible";
+        BindReadOnlyPaths = lib.optional (cfg.settings ? mount) cfg.settings.mount;
+        CapabilityBoundingSet = if cfg.port < 1024 then
+          [ "CAP_NET_BIND_SERVICE" ]
+        else
+          [ "" ];
+        AmbientCapabilities = CapabilityBoundingSet;
+        NoNewPrivileges = true;
+        DynamicUser = true;
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        TemporaryFileSystem = [ "/:ro" ];
+        PrivateTmp = true;
+        PrivateDevices = true;
+        PrivateUsers = cfg.port >= 1024;
+        ProtectHostname = true;
+        ProtectClock = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectKernelLogs = true;
+        ProtectControlGroups = true;
+        RestrictAddressFamilies = [
+          "AF_INET"
+          "AF_INET6"
+        ];
+        RestrictNamespaces = true;
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        RestrictRealtime = true;
+        PrivateMounts = true;
+        SystemCallFilter = [
+          "@system-service"
+          "~@privileged"
+        ];
+        DevicePolicy = "closed";
+      };
+    };
+  };
+
+  meta = {
+    maintainers = with lib.maintainers; [ dotlambda ];
+  };
+}
diff --git a/nixos/modules/services/networking/minidlna.nix b/nixos/modules/services/networking/minidlna.nix
index 549f1fe5de304..d0de6cd4fdc6b 100644
--- a/nixos/modules/services/networking/minidlna.nix
+++ b/nixos/modules/services/networking/minidlna.nix
@@ -16,7 +16,7 @@ in
     description = lib.mdDoc ''
       Whether to enable MiniDLNA, a simple DLNA server.
       It serves media files such as video and music to DLNA client devices
-      such as televisions and media players. If you use the firewall consider
+      such as televisions and media players. If you use the firewall, consider
       adding the following: `services.minidlna.openFirewall = true;`
     '';
   };
@@ -54,10 +54,7 @@ in
         description = lib.mdDoc ''
           The interval between announces (in seconds).
           Instead of waiting for announces, you should set `openFirewall` option to use SSDP discovery.
-          Furthermore, this option has been set to 90000 in order to prevent disconnects with certain
-          clients and relies solely on the discovery.
-
-          Lower values (e.g. 30 seconds) should be used if you can't use the discovery.
+          Lower values (e.g. 30 seconds) should be used if your network blocks the discovery unicast.
           Some relevant information can be found here:
           https://sourceforge.net/p/minidlna/discussion/879957/thread/1389d197/
         '';
@@ -82,8 +79,8 @@ in
       };
       options.root_container = mkOption {
         type = types.str;
-        default = ".";
-        example = "B";
+        default = "B";
+        example = ".";
         description = lib.mdDoc "Use a different container as the root of the directory tree presented to clients.";
       };
       options.log_level = mkOption {
@@ -133,22 +130,19 @@ in
 
     users.groups.minidlna.gid = config.ids.gids.minidlna;
 
-    systemd.services.minidlna =
-      { description = "MiniDLNA Server";
-
-        wantedBy = [ "multi-user.target" ];
-        after = [ "network.target" ];
+    systemd.services.minidlna = {
+      description = "MiniDLNA Server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
 
-        serviceConfig =
-          { User = "minidlna";
-            Group = "minidlna";
-            CacheDirectory = "minidlna";
-            RuntimeDirectory = "minidlna";
-            PIDFile = "/run/minidlna/pid";
-            ExecStart =
-              "${pkgs.minidlna}/sbin/minidlnad -S -P /run/minidlna/pid" +
-              " -f ${settingsFile}";
-          };
+      serviceConfig = {
+        User = "minidlna";
+        Group = "minidlna";
+        CacheDirectory = "minidlna";
+        RuntimeDirectory = "minidlna";
+        PIDFile = "/run/minidlna/pid";
+        ExecStart = "${pkgs.minidlna}/sbin/minidlnad -S -P /run/minidlna/pid -f ${settingsFile}";
       };
+    };
   };
 }
diff --git a/nixos/modules/services/networking/mosquitto.nix b/nixos/modules/services/networking/mosquitto.nix
index 563412025561f..a4fd2fd7c89f1 100644
--- a/nixos/modules/services/networking/mosquitto.nix
+++ b/nixos/modules/services/networking/mosquitto.nix
@@ -671,6 +671,6 @@ in
 
   meta = {
     maintainers = with lib.maintainers; [ pennae ];
-    doc = ./mosquitto.xml;
+    doc = ./mosquitto.md;
   };
 }
diff --git a/nixos/modules/services/networking/mosquitto.xml b/nixos/modules/services/networking/mosquitto.xml
deleted file mode 100644
index 91934617c56d5..0000000000000
--- a/nixos/modules/services/networking/mosquitto.xml
+++ /dev/null
@@ -1,149 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-mosquitto">
-  <title>Mosquitto</title>
-  <para>
-    Mosquitto is a MQTT broker often used for IoT or home automation
-    data transport.
-  </para>
-  <section xml:id="module-services-mosquitto-quickstart">
-    <title>Quickstart</title>
-    <para>
-      A minimal configuration for Mosquitto is
-    </para>
-    <programlisting language="nix">
-services.mosquitto = {
-  enable = true;
-  listeners = [ {
-    acl = [ &quot;pattern readwrite #&quot; ];
-    omitPasswordAuth = true;
-    settings.allow_anonymous = true;
-  } ];
-};
-</programlisting>
-    <para>
-      This will start a broker on port 1883, listening on all interfaces
-      of the machine, allowing read/write access to all topics to any
-      user without password requirements.
-    </para>
-    <para>
-      User authentication can be configured with the
-      <literal>users</literal> key of listeners. A config that gives
-      full read access to a user <literal>monitor</literal> and
-      restricted write access to a user <literal>service</literal> could
-      look like
-    </para>
-    <programlisting language="nix">
-services.mosquitto = {
-  enable = true;
-  listeners = [ {
-    users = {
-      monitor = {
-        acl = [ &quot;read #&quot; ];
-        password = &quot;monitor&quot;;
-      };
-      service = {
-        acl = [ &quot;write service/#&quot; ];
-        password = &quot;service&quot;;
-      };
-    };
-  } ];
-};
-</programlisting>
-    <para>
-      TLS authentication is configured by setting TLS-related options of
-      the listener:
-    </para>
-    <programlisting language="nix">
-services.mosquitto = {
-  enable = true;
-  listeners = [ {
-    port = 8883; # port change is not required, but helpful to avoid mistakes
-    # ...
-    settings = {
-      cafile = &quot;/path/to/mqtt.ca.pem&quot;;
-      certfile = &quot;/path/to/mqtt.pem&quot;;
-      keyfile = &quot;/path/to/mqtt.key&quot;;
-    };
-  } ];
-</programlisting>
-  </section>
-  <section xml:id="module-services-mosquitto-config">
-    <title>Configuration</title>
-    <para>
-      The Mosquitto configuration has four distinct types of settings:
-      the global settings of the daemon, listeners, plugins, and
-      bridges. Bridges and listeners are part of the global
-      configuration, plugins are part of listeners. Users of the broker
-      are configured as parts of listeners rather than globally,
-      allowing configurations in which a given user is only allowed to
-      log in to the broker using specific listeners (eg to configure an
-      admin user with full access to all topics, but restricted to
-      localhost).
-    </para>
-    <para>
-      Almost all options of Mosquitto are available for configuration at
-      their appropriate levels, some as NixOS options written in camel
-      case, the remainders under <literal>settings</literal> with their
-      exact names in the Mosquitto config file. The exceptions are
-      <literal>acl_file</literal> (which is always set according to the
-      <literal>acl</literal> attributes of a listener and its users) and
-      <literal>per_listener_settings</literal> (which is always set to
-      <literal>true</literal>).
-    </para>
-    <section xml:id="module-services-mosquitto-config-passwords">
-      <title>Password authentication</title>
-      <para>
-        Mosquitto can be run in two modes, with a password file or
-        without. Each listener has its own password file, and different
-        listeners may use different password files. Password file
-        generation can be disabled by setting
-        <literal>omitPasswordAuth = true</literal> for a listener; in
-        this case it is necessary to either set
-        <literal>settings.allow_anonymous = true</literal> to allow all
-        logins, or to configure other authentication methods like TLS
-        client certificates with
-        <literal>settings.use_identity_as_username = true</literal>.
-      </para>
-      <para>
-        The default is to generate a password file for each listener
-        from the users configured to that listener. Users with no
-        configured password will not be added to the password file and
-        thus will not be able to use the broker.
-      </para>
-    </section>
-    <section xml:id="module-services-mosquitto-config-acl">
-      <title>ACL format</title>
-      <para>
-        Every listener has a Mosquitto <literal>acl_file</literal>
-        attached to it. This ACL is configured via two attributes of the
-        config:
-      </para>
-      <itemizedlist spacing="compact">
-        <listitem>
-          <para>
-            the <literal>acl</literal> attribute of the listener
-            configures pattern ACL entries and topic ACL entries for
-            anonymous users. Each entry must be prefixed with
-            <literal>pattern</literal> or <literal>topic</literal> to
-            distinguish between these two cases.
-          </para>
-        </listitem>
-        <listitem>
-          <para>
-            the <literal>acl</literal> attribute of every user
-            configures in the listener configured the ACL for that given
-            user. Only topic ACLs are supported by Mosquitto in this
-            setting, so no prefix is required or allowed.
-          </para>
-        </listitem>
-      </itemizedlist>
-      <para>
-        The default ACL for a listener is empty, disallowing all
-        accesses from all clients. To configure a completely open ACL,
-        set <literal>acl = [ &quot;pattern readwrite #&quot; ]</literal>
-        in the listener.
-      </para>
-    </section>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/networking/multipath.nix b/nixos/modules/services/networking/multipath.nix
index 54ee2a0156878..b20ec76ddf594 100644
--- a/nixos/modules/services/networking/multipath.nix
+++ b/nixos/modules/services/networking/multipath.nix
@@ -516,7 +516,6 @@ in {
         ${optionalString (!isNull defaults) ''
           defaults {
           ${indentLines 2 defaults}
-            multipath_dir ${cfg.package}/lib/multipath
           }
         ''}
         ${optionalString (!isNull blacklist) ''
diff --git a/nixos/modules/services/networking/nebula.nix b/nixos/modules/services/networking/nebula.nix
index 2bedafc5d9fe8..e1a8c6740f57e 100644
--- a/nixos/modules/services/networking/nebula.nix
+++ b/nixos/modules/services/networking/nebula.nix
@@ -68,6 +68,12 @@ in
               description = lib.mdDoc "Whether this node is a lighthouse.";
             };
 
+            isRelay = mkOption {
+              type = types.bool;
+              default = false;
+              description = lib.mdDoc "Whether this node is a relay.";
+            };
+
             lighthouses = mkOption {
               type = types.listOf types.str;
               default = [];
@@ -78,6 +84,15 @@ in
               example = [ "192.168.100.1" ];
             };
 
+            relays = mkOption {
+              type = types.listOf types.str;
+              default = [];
+              description = lib.mdDoc ''
+                List of IPs of relays that this node should allow traffic from.
+              '';
+              example = [ "192.168.100.1" ];
+            };
+
             listen.host = mkOption {
               type = types.str;
               default = "0.0.0.0";
@@ -157,6 +172,11 @@ in
             am_lighthouse = netCfg.isLighthouse;
             hosts = netCfg.lighthouses;
           };
+          relay = {
+            am_relay = netCfg.isRelay;
+            relays = netCfg.relays;
+            use_relays = true;
+          };
           listen = {
             host = netCfg.listen.host;
             port = netCfg.listen.port;
@@ -173,25 +193,41 @@ in
         configFile = format.generate "nebula-config-${netName}.yml" settings;
         in
         {
-          # Create systemd service for Nebula.
+          # Create the systemd service for Nebula.
           "nebula@${netName}" = {
             description = "Nebula VPN service for ${netName}";
             wants = [ "basic.target" ];
             after = [ "basic.target" "network.target" ];
             before = [ "sshd.service" ];
             wantedBy = [ "multi-user.target" ];
-            serviceConfig = mkMerge [
-              {
-                Type = "simple";
-                Restart = "always";
-                ExecStart = "${netCfg.package}/bin/nebula -config ${configFile}";
-              }
-              # The service needs to launch as root to access the tun device, if it's enabled.
-              (mkIf netCfg.tun.disable {
-                User = networkId;
-                Group = networkId;
-              })
-            ];
+            serviceConfig = {
+              Type = "simple";
+              Restart = "always";
+              ExecStart = "${netCfg.package}/bin/nebula -config ${configFile}";
+              UMask = "0027";
+              CapabilityBoundingSet = "CAP_NET_ADMIN";
+              AmbientCapabilities = "CAP_NET_ADMIN";
+              LockPersonality = true;
+              NoNewPrivileges = true;
+              PrivateDevices = false; # needs access to /dev/net/tun (below)
+              DeviceAllow = "/dev/net/tun rw";
+              DevicePolicy = "closed";
+              PrivateTmp = true;
+              PrivateUsers = false; # CapabilityBoundingSet needs to apply to the host namespace
+              ProtectClock = true;
+              ProtectControlGroups = true;
+              ProtectHome = true;
+              ProtectHostname = true;
+              ProtectKernelLogs = true;
+              ProtectKernelModules = true;
+              ProtectKernelTunables = true;
+              ProtectProc = "invisible";
+              ProtectSystem = "strict";
+              RestrictNamespaces = true;
+              RestrictSUIDSGID = true;
+              User = networkId;
+              Group = networkId;
+            };
             unitConfig.StartLimitIntervalSec = 0; # ensure Restart=always is always honoured (networks can go down for arbitrarily long)
           };
         }) enabledNetworks);
@@ -202,7 +238,7 @@ in
 
     # Create the service users and groups.
     users.users = mkMerge (mapAttrsToList (netName: netCfg:
-      mkIf netCfg.tun.disable {
+      {
         ${nameToId netName} = {
           group = nameToId netName;
           description = "Nebula service user for network ${netName}";
@@ -210,9 +246,8 @@ in
         };
       }) enabledNetworks);
 
-    users.groups = mkMerge (mapAttrsToList (netName: netCfg:
-      mkIf netCfg.tun.disable {
-        ${nameToId netName} = {};
-      }) enabledNetworks);
+    users.groups = mkMerge (mapAttrsToList (netName: netCfg: {
+      ${nameToId netName} = {};
+    }) enabledNetworks);
   };
 }
diff --git a/nixos/modules/services/networking/networkd-dispatcher.nix b/nixos/modules/services/networking/networkd-dispatcher.nix
new file mode 100644
index 0000000000000..d13ca23368c5b
--- /dev/null
+++ b/nixos/modules/services/networking/networkd-dispatcher.nix
@@ -0,0 +1,63 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.networkd-dispatcher;
+in {
+  options = {
+    services.networkd-dispatcher = {
+
+      enable = mkEnableOption (mdDoc ''
+        Networkd-dispatcher service for systemd-networkd connection status
+        change. See [https://gitlab.com/craftyguy/networkd-dispatcher](upstream instructions)
+        for usage.
+      '');
+
+      scriptDir = mkOption {
+        type = types.path;
+        default = "/var/lib/networkd-dispatcher";
+        description = mdDoc ''
+          This directory is used for keeping various scripts read and run by
+          networkd-dispatcher. See [https://gitlab.com/craftyguy/networkd-dispatcher](upstream instructions)
+          for directory structure and script usage.
+        '';
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd = {
+
+      packages = [ pkgs.networkd-dispatcher ];
+      services.networkd-dispatcher = {
+        wantedBy = [ "multi-user.target" ];
+        # Override existing ExecStart definition
+        serviceConfig.ExecStart = [
+          ""
+          "${pkgs.networkd-dispatcher}/bin/networkd-dispatcher -v --script-dir ${cfg.scriptDir} $networkd_dispatcher_args"
+        ];
+      };
+
+      # Directory structure required according to upstream instructions
+      # https://gitlab.com/craftyguy/networkd-dispatcher
+      tmpfiles.rules = [
+        "d '${cfg.scriptDir}'               0750 root root - -"
+        "d '${cfg.scriptDir}/routable.d'    0750 root root - -"
+        "d '${cfg.scriptDir}/dormant.d'     0750 root root - -"
+        "d '${cfg.scriptDir}/no-carrier.d'  0750 root root - -"
+        "d '${cfg.scriptDir}/off.d'         0750 root root - -"
+        "d '${cfg.scriptDir}/carrier.d'     0750 root root - -"
+        "d '${cfg.scriptDir}/degraded.d'    0750 root root - -"
+        "d '${cfg.scriptDir}/configuring.d' 0750 root root - -"
+        "d '${cfg.scriptDir}/configured.d'  0750 root root - -"
+      ];
+
+    };
+
+
+  };
+}
+
diff --git a/nixos/modules/services/networking/ntp/chrony.nix b/nixos/modules/services/networking/ntp/chrony.nix
index dc180d4a4f954..6c8d7b985d5f1 100644
--- a/nixos/modules/services/networking/ntp/chrony.nix
+++ b/nixos/modules/services/networking/ntp/chrony.nix
@@ -185,7 +185,7 @@ in
           ProtectSystem = "full";
           ProtectHome = true;
           PrivateTmp = true;
-          PrivateDevices = true;
+          PrivateDevices = false;
           PrivateUsers = false;
           ProtectHostname = true;
           ProtectClock = false;
@@ -203,7 +203,7 @@ in
           PrivateMounts = true;
           # System Call Filtering
           SystemCallArchitectures = "native";
-          SystemCallFilter = [ "~@cpu-emulation @debug @keyring @mount @obsolete @privileged @resources" "@clock" "@setuid" "capset" "chown" ];
+          SystemCallFilter = [ "~@cpu-emulation @debug @keyring @mount @obsolete @privileged @resources" "@clock" "@setuid" "capset" "chown" ] ++ lib.optional pkgs.stdenv.hostPlatform.isAarch64 "fchownat";
         };
       };
   };
diff --git a/nixos/modules/services/networking/openconnect.nix b/nixos/modules/services/networking/openconnect.nix
index 5a02bd072257f..7f9006053b890 100644
--- a/nixos/modules/services/networking/openconnect.nix
+++ b/nixos/modules/services/networking/openconnect.nix
@@ -90,6 +90,7 @@ let
   generateConfig = name: icfg:
     pkgs.writeText "config" ''
       interface=${name}
+      ${optionalString (icfg.protocol != null) "protocol=${icfg.protocol}"}
       ${optionalString (icfg.user != null) "user=${icfg.user}"}
       ${optionalString (icfg.passwordFile != null) "passwd-on-stdin"}
       ${optionalString (icfg.certificate != null)
diff --git a/nixos/modules/services/networking/openvpn.nix b/nixos/modules/services/networking/openvpn.nix
index 492a0936fdbb6..9a5866f2afd40 100644
--- a/nixos/modules/services/networking/openvpn.nix
+++ b/nixos/modules/services/networking/openvpn.nix
@@ -14,7 +14,6 @@ let
       path = makeBinPath (getAttr "openvpn-${name}" config.systemd.services).path;
 
       upScript = ''
-        #! /bin/sh
         export PATH=${path}
 
         # For convenience in client scripts, extract the remote domain
@@ -34,7 +33,6 @@ let
       '';
 
       downScript = ''
-        #! /bin/sh
         export PATH=${path}
         ${optionalString cfg.updateResolvConf
            "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"}
@@ -47,9 +45,9 @@ let
           ${optionalString (cfg.up != "" || cfg.down != "" || cfg.updateResolvConf) "script-security 2"}
           ${cfg.config}
           ${optionalString (cfg.up != "" || cfg.updateResolvConf)
-              "up ${pkgs.writeScript "openvpn-${name}-up" upScript}"}
+              "up ${pkgs.writeShellScript "openvpn-${name}-up" upScript}"}
           ${optionalString (cfg.down != "" || cfg.updateResolvConf)
-              "down ${pkgs.writeScript "openvpn-${name}-down" downScript}"}
+              "down ${pkgs.writeShellScript "openvpn-${name}-down" downScript}"}
           ${optionalString (cfg.authUserPass != null)
               "auth-user-pass ${pkgs.writeText "openvpn-credentials-${name}" ''
                 ${cfg.authUserPass.username}
@@ -57,7 +55,8 @@ let
               ''}"}
         '';
 
-    in {
+    in
+    {
       description = "OpenVPN instance ‘${name}’";
 
       wantedBy = optional cfg.autoStart "multi-user.target";
@@ -70,6 +69,16 @@ let
       serviceConfig.Type = "notify";
     };
 
+  restartService = optionalAttrs cfg.restartAfterSleep {
+    openvpn-restart = {
+      wantedBy = [ "sleep.target" ];
+      path = [ pkgs.procps ];
+      script = "pkill --signal SIGHUP --exact openvpn";
+      #SIGHUP makes openvpn process to self-exit and then it got restarted by systemd because of Restart=always
+      description = "Sends a signal to OpenVPN process to trigger a restart after return from sleep";
+    };
+  };
+
 in
 
 {
@@ -82,7 +91,7 @@ in
   options = {
 
     services.openvpn.servers = mkOption {
-      default = {};
+      default = { };
 
       example = literalExpression ''
         {
@@ -201,14 +210,21 @@ in
 
     };
 
+    services.openvpn.restartAfterSleep = mkOption {
+      default = true;
+      type = types.bool;
+      description = lib.mdDoc "Whether OpenVPN client should be restarted after sleep.";
+    };
+
   };
 
 
   ###### implementation
 
-  config = mkIf (cfg.servers != {}) {
+  config = mkIf (cfg.servers != { }) {
 
-    systemd.services = listToAttrs (mapAttrsFlatten (name: value: nameValuePair "openvpn-${name}" (makeOpenVPNJob value name)) cfg.servers);
+    systemd.services = (listToAttrs (mapAttrsFlatten (name: value: nameValuePair "openvpn-${name}" (makeOpenVPNJob value name)) cfg.servers))
+      // restartService;
 
     environment.systemPackages = [ openvpn ];
 
diff --git a/nixos/modules/services/networking/pleroma.nix b/nixos/modules/services/networking/pleroma.nix
index f317510258ba5..e9db7f3eab8ed 100644
--- a/nixos/modules/services/networking/pleroma.nix
+++ b/nixos/modules/services/networking/pleroma.nix
@@ -147,5 +147,5 @@ in {
 
   };
   meta.maintainers = with lib.maintainers; [ ninjatrappeur ];
-  meta.doc = ./pleroma.xml;
+  meta.doc = ./pleroma.md;
 }
diff --git a/nixos/modules/services/networking/pleroma.xml b/nixos/modules/services/networking/pleroma.xml
deleted file mode 100644
index 97954f4b95141..0000000000000
--- a/nixos/modules/services/networking/pleroma.xml
+++ /dev/null
@@ -1,244 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-pleroma">
-  <title>Pleroma</title>
-  <para>
-    <link xlink:href="https://pleroma.social/">Pleroma</link> is a
-    lightweight activity pub server.
-  </para>
-  <section xml:id="module-services-pleroma-generate-config">
-    <title>Generating the Pleroma config</title>
-    <para>
-      The <literal>pleroma_ctl</literal> CLI utility will prompt you
-      some questions and it will generate an initial config file. This
-      is an example of usage
-    </para>
-    <programlisting>
-$ mkdir tmp-pleroma
-$ cd tmp-pleroma
-$ nix-shell -p pleroma-otp
-$ pleroma_ctl instance gen --output config.exs --output-psql setup.psql
-</programlisting>
-    <para>
-      The <literal>config.exs</literal> file can be further customized
-      following the instructions on the
-      <link xlink:href="https://docs-develop.pleroma.social/backend/configuration/cheatsheet/">upstream
-      documentation</link>. Many refinements can be applied also after
-      the service is running.
-    </para>
-  </section>
-  <section xml:id="module-services-pleroma-initialize-db">
-    <title>Initializing the database</title>
-    <para>
-      First, the Postgresql service must be enabled in the NixOS
-      configuration
-    </para>
-    <programlisting>
-services.postgresql = {
-  enable = true;
-  package = pkgs.postgresql_13;
-};
-</programlisting>
-    <para>
-      and activated with the usual
-    </para>
-    <programlisting>
-$ nixos-rebuild switch
-</programlisting>
-    <para>
-      Then you can create and seed the database, using the
-      <literal>setup.psql</literal> file that you generated in the
-      previous section, by running
-    </para>
-    <programlisting>
-$ sudo -u postgres psql -f setup.psql
-</programlisting>
-  </section>
-  <section xml:id="module-services-pleroma-enable">
-    <title>Enabling the Pleroma service locally</title>
-    <para>
-      In this section we will enable the Pleroma service only locally,
-      so its configurations can be improved incrementally.
-    </para>
-    <para>
-      This is an example of configuration, where
-      <xref linkend="opt-services.pleroma.configs" /> option contains
-      the content of the file <literal>config.exs</literal>, generated
-      <link linkend="module-services-pleroma-generate-config">in the
-      first section</link>, but with the secrets (database password,
-      endpoint secret key, salts, etc.) removed. Removing secrets is
-      important, because otherwise they will be stored publicly in the
-      Nix store.
-    </para>
-    <programlisting>
-services.pleroma = {
-  enable = true;
-  secretConfigFile = &quot;/var/lib/pleroma/secrets.exs&quot;;
-  configs = [
-    ''
-    import Config
-
-    config :pleroma, Pleroma.Web.Endpoint,
-      url: [host: &quot;pleroma.example.net&quot;, scheme: &quot;https&quot;, port: 443],
-      http: [ip: {127, 0, 0, 1}, port: 4000]
-
-    config :pleroma, :instance,
-      name: &quot;Test&quot;,
-      email: &quot;admin@example.net&quot;,
-      notify_email: &quot;admin@example.net&quot;,
-      limit: 5000,
-      registrations_open: true
-
-    config :pleroma, :media_proxy,
-      enabled: false,
-      redirect_on_failure: true
-
-    config :pleroma, Pleroma.Repo,
-      adapter: Ecto.Adapters.Postgres,
-      username: &quot;pleroma&quot;,
-      database: &quot;pleroma&quot;,
-      hostname: &quot;localhost&quot;
-
-    # Configure web push notifications
-    config :web_push_encryption, :vapid_details,
-      subject: &quot;mailto:admin@example.net&quot;
-
-    # ... TO CONTINUE ...
-    ''
-  ];
-};
-</programlisting>
-    <para>
-      Secrets must be moved into a file pointed by
-      <xref linkend="opt-services.pleroma.secretConfigFile" />, in our
-      case <literal>/var/lib/pleroma/secrets.exs</literal>. This file
-      can be created copying the previously generated
-      <literal>config.exs</literal> file and then removing all the
-      settings, except the secrets. This is an example
-    </para>
-    <programlisting>
-# Pleroma instance passwords
-
-import Config
-
-config :pleroma, Pleroma.Web.Endpoint,
-   secret_key_base: &quot;&lt;the secret generated by pleroma_ctl&gt;&quot;,
-   signing_salt: &quot;&lt;the secret generated by pleroma_ctl&gt;&quot;
-
-config :pleroma, Pleroma.Repo,
-  password: &quot;&lt;the secret generated by pleroma_ctl&gt;&quot;
-
-# Configure web push notifications
-config :web_push_encryption, :vapid_details,
-  public_key: &quot;&lt;the secret generated by pleroma_ctl&gt;&quot;,
-  private_key: &quot;&lt;the secret generated by pleroma_ctl&gt;&quot;
-
-# ... TO CONTINUE ...
-</programlisting>
-    <para>
-      Note that the lines of the same configuration group are comma
-      separated (i.e. all the lines end with a comma, except the last
-      one), so when the lines with passwords are added or removed,
-      commas must be adjusted accordingly.
-    </para>
-    <para>
-      The service can be enabled with the usual
-    </para>
-    <programlisting>
-$ nixos-rebuild switch
-</programlisting>
-    <para>
-      The service is accessible only from the local
-      <literal>127.0.0.1:4000</literal> port. It can be tested using a
-      port forwarding like this
-    </para>
-    <programlisting>
-$ ssh -L 4000:localhost:4000 myuser@example.net
-</programlisting>
-    <para>
-      and then accessing
-      <link xlink:href="http://localhost:4000">http://localhost:4000</link>
-      from a web browser.
-    </para>
-  </section>
-  <section xml:id="module-services-pleroma-admin-user">
-    <title>Creating the admin user</title>
-    <para>
-      After Pleroma service is running, all
-      <link xlink:href="https://docs-develop.pleroma.social/">Pleroma
-      administration utilities</link> can be used. In particular an
-      admin user can be created with
-    </para>
-    <programlisting>
-$ pleroma_ctl user new &lt;nickname&gt; &lt;email&gt;  --admin --moderator --password &lt;password&gt;
-</programlisting>
-  </section>
-  <section xml:id="module-services-pleroma-nginx">
-    <title>Configuring Nginx</title>
-    <para>
-      In this configuration, Pleroma is listening only on the local port
-      4000. Nginx can be configured as a Reverse Proxy, for forwarding
-      requests from public ports to the Pleroma service. This is an
-      example of configuration, using
-      <link xlink:href="https://letsencrypt.org/">Let’s Encrypt</link>
-      for the TLS certificates
-    </para>
-    <programlisting>
-security.acme = {
-  email = &quot;root@example.net&quot;;
-  acceptTerms = true;
-};
-
-services.nginx = {
-  enable = true;
-  addSSL = true;
-
-  recommendedTlsSettings = true;
-  recommendedOptimisation = true;
-  recommendedGzipSettings = true;
-
-  recommendedProxySettings = false;
-  # NOTE: if enabled, the NixOS proxy optimizations will override the Pleroma
-  # specific settings, and they will enter in conflict.
-
-  virtualHosts = {
-    &quot;pleroma.example.net&quot; = {
-      http2 = true;
-      enableACME = true;
-      forceSSL = true;
-
-      locations.&quot;/&quot; = {
-        proxyPass = &quot;http://127.0.0.1:4000&quot;;
-
-        extraConfig = ''
-          etag on;
-          gzip on;
-
-          add_header 'Access-Control-Allow-Origin' '*' always;
-          add_header 'Access-Control-Allow-Methods' 'POST, PUT, DELETE, GET, PATCH, OPTIONS' always;
-          add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Idempotency-Key' always;
-          add_header 'Access-Control-Expose-Headers' 'Link, X-RateLimit-Reset, X-RateLimit-Limit, X-RateLimit-Remaining, X-Request-Id' always;
-          if ($request_method = OPTIONS) {
-            return 204;
-          }
-          add_header X-XSS-Protection &quot;1; mode=block&quot;;
-          add_header X-Permitted-Cross-Domain-Policies none;
-          add_header X-Frame-Options DENY;
-          add_header X-Content-Type-Options nosniff;
-          add_header Referrer-Policy same-origin;
-          add_header X-Download-Options noopen;
-          proxy_http_version 1.1;
-          proxy_set_header Upgrade $http_upgrade;
-          proxy_set_header Connection &quot;upgrade&quot;;
-          proxy_set_header Host $host;
-
-          client_max_body_size 16m;
-          # NOTE: increase if users need to upload very big files
-        '';
-      };
-    };
-  };
-};
-</programlisting>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/networking/prosody.nix b/nixos/modules/services/networking/prosody.nix
index 0746bbf184fce..9f68853f9fa83 100644
--- a/nixos/modules/services/networking/prosody.nix
+++ b/nixos/modules/services/networking/prosody.nix
@@ -905,5 +905,5 @@ in
 
   };
 
-  meta.doc = ./prosody.xml;
+  meta.doc = ./prosody.md;
 }
diff --git a/nixos/modules/services/networking/prosody.xml b/nixos/modules/services/networking/prosody.xml
deleted file mode 100644
index 5df046f814591..0000000000000
--- a/nixos/modules/services/networking/prosody.xml
+++ /dev/null
@@ -1,92 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-prosody">
-  <title>Prosody</title>
-  <para>
-    <link xlink:href="https://prosody.im/">Prosody</link> is an
-    open-source, modern XMPP server.
-  </para>
-  <section xml:id="module-services-prosody-basic-usage">
-    <title>Basic usage</title>
-    <para>
-      A common struggle for most XMPP newcomers is to find the right set
-      of XMPP Extensions (XEPs) to setup. Forget to activate a few of
-      those and your XMPP experience might turn into a nightmare!
-    </para>
-    <para>
-      The XMPP community tackles this problem by creating a meta-XEP
-      listing a decent set of XEPs you should implement. This meta-XEP
-      is issued every year, the 2020 edition being
-      <link xlink:href="https://xmpp.org/extensions/xep-0423.html">XEP-0423</link>.
-    </para>
-    <para>
-      The NixOS Prosody module will implement most of these recommendend
-      XEPs out of the box. That being said, two components still require
-      some manual configuration: the
-      <link xlink:href="https://xmpp.org/extensions/xep-0045.html">Multi
-      User Chat (MUC)</link> and the
-      <link xlink:href="https://xmpp.org/extensions/xep-0363.html">HTTP
-      File Upload</link> ones. You’ll need to create a DNS subdomain for
-      each of those. The current convention is to name your MUC endpoint
-      <literal>conference.example.org</literal> and your HTTP upload
-      domain <literal>upload.example.org</literal>.
-    </para>
-    <para>
-      A good configuration to start with, including a
-      <link xlink:href="https://xmpp.org/extensions/xep-0045.html">Multi
-      User Chat (MUC)</link> endpoint as well as a
-      <link xlink:href="https://xmpp.org/extensions/xep-0363.html">HTTP
-      File Upload</link> endpoint will look like this:
-    </para>
-    <programlisting>
-services.prosody = {
-  enable = true;
-  admins = [ &quot;root@example.org&quot; ];
-  ssl.cert = &quot;/var/lib/acme/example.org/fullchain.pem&quot;;
-  ssl.key = &quot;/var/lib/acme/example.org/key.pem&quot;;
-  virtualHosts.&quot;example.org&quot; = {
-      enabled = true;
-      domain = &quot;example.org&quot;;
-      ssl.cert = &quot;/var/lib/acme/example.org/fullchain.pem&quot;;
-      ssl.key = &quot;/var/lib/acme/example.org/key.pem&quot;;
-  };
-  muc = [ {
-      domain = &quot;conference.example.org&quot;;
-  } ];
-  uploadHttp = {
-      domain = &quot;upload.example.org&quot;;
-  };
-};
-</programlisting>
-  </section>
-  <section xml:id="module-services-prosody-letsencrypt">
-    <title>Let’s Encrypt Configuration</title>
-    <para>
-      As you can see in the code snippet from the
-      <link linkend="module-services-prosody-basic-usage">previous
-      section</link>, you’ll need a single TLS certificate covering your
-      main endpoint, the MUC one as well as the HTTP Upload one. We can
-      generate such a certificate by leveraging the ACME
-      <link linkend="opt-security.acme.certs._name_.extraDomainNames">extraDomainNames</link>
-      module option.
-    </para>
-    <para>
-      Provided the setup detailed in the previous section, you’ll need
-      the following acme configuration to generate a TLS certificate for
-      the three endponits:
-    </para>
-    <programlisting>
-security.acme = {
-  email = &quot;root@example.org&quot;;
-  acceptTerms = true;
-  certs = {
-    &quot;example.org&quot; = {
-      webroot = &quot;/var/www/example.org&quot;;
-      email = &quot;root@example.org&quot;;
-      extraDomainNames = [ &quot;conference.example.org&quot; &quot;upload.example.org&quot; ];
-    };
-  };
-};
-</programlisting>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/networking/soju.nix b/nixos/modules/services/networking/soju.nix
index d4c4ca47bc801..7f0ac3e3b8e69 100644
--- a/nixos/modules/services/networking/soju.nix
+++ b/nixos/modules/services/networking/soju.nix
@@ -120,5 +120,5 @@ in
     };
   };
 
-  meta.maintainers = with maintainers; [ malvo ];
+  meta.maintainers = with maintainers; [ malte-v ];
 }
diff --git a/nixos/modules/services/networking/ssh/sshd.nix b/nixos/modules/services/networking/ssh/sshd.nix
index f2b8d12ccc943..095c7de0b7aa1 100644
--- a/nixos/modules/services/networking/ssh/sshd.nix
+++ b/nixos/modules/services/networking/ssh/sshd.nix
@@ -13,11 +13,12 @@ let
     else pkgs.buildPackages.openssh;
 
   # reports boolean as yes / no
-  mkValueStringSshd = v:
+  mkValueStringSshd = with lib; v:
         if isInt           v then toString v
         else if isString   v then v
         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}";
 
   # dont use the "=" operator
@@ -104,6 +105,11 @@ in
     (mkRenamedOptionModule [ "services" "openssh" "useDns" ] [  "services" "openssh" "settings" "UseDns" ])
     (mkRenamedOptionModule [ "services" "openssh" "permitRootLogin" ] [  "services" "openssh" "settings" "PermitRootLogin" ])
     (mkRenamedOptionModule [ "services" "openssh" "logLevel" ] [  "services" "openssh" "settings" "LogLevel" ])
+    (mkRenamedOptionModule [ "services" "openssh" "macs" ] [  "services" "openssh" "settings" "Macs" ])
+    (mkRenamedOptionModule [ "services" "openssh" "ciphers" ] [  "services" "openssh" "settings" "Ciphers" ])
+    (mkRenamedOptionModule [ "services" "openssh" "kexAlgorithms" ] [  "services" "openssh" "settings" "KexAlgorithms" ])
+    (mkRenamedOptionModule [ "services" "openssh" "gatewayPorts" ] [  "services" "openssh" "settings" "GatewayPorts" ])
+    (mkRenamedOptionModule [ "services" "openssh" "forwardX11" ] [  "services" "openssh" "settings" "X11Forwarding" ])
   ];
 
   ###### interface
@@ -131,14 +137,6 @@ in
         '';
       };
 
-      forwardX11 = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          Whether to allow X11 connections to be forwarded.
-        '';
-      };
-
       allowSFTP = mkOption {
         type = types.bool;
         default = true;
@@ -167,16 +165,6 @@ in
         '';
       };
 
-      gatewayPorts = mkOption {
-        type = types.str;
-        default = "no";
-        description = lib.mdDoc ''
-          Specifies whether remote hosts are allowed to connect to
-          ports forwarded for the client.  See
-          {manpage}`sshd_config(5)`.
-        '';
-      };
-
       ports = mkOption {
         type = types.listOf types.port;
         default = [22];
@@ -286,63 +274,6 @@ in
         '';
       };
 
-      kexAlgorithms = mkOption {
-        type = types.listOf types.str;
-        default = [
-          "sntrup761x25519-sha512@openssh.com"
-          "curve25519-sha256"
-          "curve25519-sha256@libssh.org"
-          "diffie-hellman-group-exchange-sha256"
-        ];
-        description = lib.mdDoc ''
-          Allowed key exchange algorithms
-
-          Uses the lower bound recommended in both
-          <https://stribika.github.io/2015/01/04/secure-secure-shell.html>
-          and
-          <https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67>
-        '';
-      };
-
-      ciphers = mkOption {
-        type = types.listOf types.str;
-        default = [
-          "chacha20-poly1305@openssh.com"
-          "aes256-gcm@openssh.com"
-          "aes128-gcm@openssh.com"
-          "aes256-ctr"
-          "aes192-ctr"
-          "aes128-ctr"
-        ];
-        description = lib.mdDoc ''
-          Allowed ciphers
-
-          Defaults to recommended settings from both
-          <https://stribika.github.io/2015/01/04/secure-secure-shell.html>
-          and
-          <https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67>
-        '';
-      };
-
-      macs = mkOption {
-        type = types.listOf types.str;
-        default = [
-          "hmac-sha2-512-etm@openssh.com"
-          "hmac-sha2-256-etm@openssh.com"
-          "umac-128-etm@openssh.com"
-          "hmac-sha2-512"
-          "hmac-sha2-256"
-          "umac-128@openssh.com"
-        ];
-        description = lib.mdDoc ''
-          Allowed MACs
-
-          Defaults to recommended settings from both
-          <https://stribika.github.io/2015/01/04/secure-secure-shell.html>
-          and
-          <https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67>
-        '';
-      };
 
 
       settings = mkOption {
@@ -374,7 +305,13 @@ in
                 ~/.ssh/authorized_keys from and sshd_config Match Host directives.
               '';
             };
-
+            X11Forwarding = mkOption {
+              type = types.bool;
+              default = false;
+              description = lib.mdDoc ''
+                Whether to allow X11 connections to be forwarded.
+              '';
+            };
             PasswordAuthentication = mkOption {
               type = types.bool;
               default = true;
@@ -396,6 +333,70 @@ in
                 Specifies whether keyboard-interactive authentication is allowed.
               '';
             };
+            GatewayPorts = mkOption {
+              type = types.str;
+              default = "no";
+              description = lib.mdDoc ''
+                Specifies whether remote hosts are allowed to connect to
+                ports forwarded for the client.  See
+                {manpage}`sshd_config(5)`.
+              '';
+            };
+            KexAlgorithms = mkOption {
+              type = types.listOf types.str;
+              default = [
+                "sntrup761x25519-sha512@openssh.com"
+                "curve25519-sha256"
+                "curve25519-sha256@libssh.org"
+                "diffie-hellman-group-exchange-sha256"
+              ];
+              description = lib.mdDoc ''
+                Allowed key exchange algorithms
+
+                Uses the lower bound recommended in both
+                <https://stribika.github.io/2015/01/04/secure-secure-shell.html>
+                and
+                <https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67>
+              '';
+            };
+            Macs = mkOption {
+              type = types.listOf types.str;
+              default = [
+                "hmac-sha2-512-etm@openssh.com"
+                "hmac-sha2-256-etm@openssh.com"
+                "umac-128-etm@openssh.com"
+                "hmac-sha2-512"
+                "hmac-sha2-256"
+                "umac-128@openssh.com"
+              ];
+              description = lib.mdDoc ''
+                Allowed MACs
+
+                Defaults to recommended settings from both
+                <https://stribika.github.io/2015/01/04/secure-secure-shell.html>
+                and
+                <https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67>
+              '';
+            };
+            Ciphers = mkOption {
+              type = types.listOf types.str;
+              default = [
+                "chacha20-poly1305@openssh.com"
+                "aes256-gcm@openssh.com"
+                "aes128-gcm@openssh.com"
+                "aes256-ctr"
+                "aes192-ctr"
+                "aes128-ctr"
+              ];
+              description = lib.mdDoc ''
+                Allowed ciphers
+
+                Defaults to recommended settings from both
+                <https://stribika.github.io/2015/01/04/secure-secure-shell.html>
+                and
+                <https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67>
+              '';
+            };
           };
         });
       };
@@ -555,17 +556,10 @@ in
         ${optionalString cfgc.setXAuthLocation ''
             XAuthLocation ${pkgs.xorg.xauth}/bin/xauth
         ''}
-
-        X11Forwarding ${if cfg.forwardX11 then "yes" else "no"}
-
         ${optionalString cfg.allowSFTP ''
           Subsystem sftp ${cfg.sftpServerExecutable} ${concatStringsSep " " cfg.sftpFlags}
         ''}
-
-        GatewayPorts ${cfg.gatewayPorts}
-
         PrintMotd no # handled by pam_motd
-
         AuthorizedKeysFile ${toString cfg.authorizedKeysFiles}
         ${optionalString (cfg.authorizedKeysCommand != "none") ''
           AuthorizedKeysCommand ${cfg.authorizedKeysCommand}
@@ -575,13 +569,9 @@ in
         ${flip concatMapStrings cfg.hostKeys (k: ''
           HostKey ${k.path}
         '')}
-
-        KexAlgorithms ${concatStringsSep "," cfg.kexAlgorithms}
-        Ciphers ${concatStringsSep "," cfg.ciphers}
-        MACs ${concatStringsSep "," cfg.macs}
       '';
 
-    assertions = [{ assertion = if cfg.forwardX11 then cfgc.setXAuthLocation else true;
+    assertions = [{ assertion = if cfg.settings.X11Forwarding then cfgc.setXAuthLocation else true;
                     message = "cannot enable X11 forwarding without setting xauth location";}]
       ++ forEach cfg.listenAddresses ({ addr, ... }: {
         assertion = addr != null;
diff --git a/nixos/modules/services/networking/tailscale.nix b/nixos/modules/services/networking/tailscale.nix
index 233bfdf9ebf57..c81cf293ab6d9 100644
--- a/nixos/modules/services/networking/tailscale.nix
+++ b/nixos/modules/services/networking/tailscale.nix
@@ -82,8 +82,8 @@ in {
     };
 
     boot.kernel.sysctl = mkIf (cfg.useRoutingFeatures == "server" || cfg.useRoutingFeatures == "both") {
-      "net.ipv4.conf.all.forwarding" = mkDefault true;
-      "net.ipv6.conf.all.forwarding" = mkDefault true;
+      "net.ipv4.conf.all.forwarding" = mkOverride 97 true;
+      "net.ipv6.conf.all.forwarding" = mkOverride 97 true;
     };
 
     networking.firewall.checkReversePath = mkIf (cfg.useRoutingFeatures == "client" || cfg.useRoutingFeatures == "both") "loose";
diff --git a/nixos/modules/services/networking/unbound.nix b/nixos/modules/services/networking/unbound.nix
index c85dd03867f77..0426dbb0c83c3 100644
--- a/nixos/modules/services/networking/unbound.nix
+++ b/nixos/modules/services/networking/unbound.nix
@@ -286,6 +286,8 @@ in {
         LockPersonality = true;
         RestrictSUIDSGID = true;
 
+        ReadWritePaths = [ cfg.stateDir ];
+
         Restart = "on-failure";
         RestartSec = "5s";
       };
diff --git a/nixos/modules/services/networking/v2raya.nix b/nixos/modules/services/networking/v2raya.nix
index 2d697b4fb56f3..0bea73798daf1 100644
--- a/nixos/modules/services/networking/v2raya.nix
+++ b/nixos/modules/services/networking/v2raya.nix
@@ -12,27 +12,38 @@ with lib;
   config = mkIf config.services.v2raya.enable {
     environment.systemPackages = [ pkgs.v2raya ];
 
-    systemd.services.v2raya = {
-      unitConfig = {
-        Description = "v2rayA service";
-        Documentation = "https://github.com/v2rayA/v2rayA/wiki";
-        After = [ "network.target" "nss-lookup.target" "iptables.service" "ip6tables.service" ];
-        Wants = [ "network.target" ];
-      };
+    systemd.services.v2raya =
+      let
+        nftablesEnabled = config.networking.nftables.enable;
+        iptablesServices = [
+          "iptables.service"
+        ] ++ optional config.networking.enableIPv6 "ip6tables.service";
+        tableServices = if nftablesEnabled then [ "nftables.service" ] else iptablesServices;
+      in
+      {
+        unitConfig = {
+          Description = "v2rayA service";
+          Documentation = "https://github.com/v2rayA/v2rayA/wiki";
+          After = [
+            "network.target"
+            "nss-lookup.target"
+          ] ++ tableServices;
+          Wants = [ "network.target" ];
+        };
 
-      serviceConfig = {
-        User = "root";
-        ExecStart = "${getExe pkgs.v2raya} --log-disable-timestamp";
-        Environment = [ "V2RAYA_LOG_FILE=/var/log/v2raya/v2raya.log" ];
-        LimitNPROC = 500;
-        LimitNOFILE = 1000000;
-        Restart = "on-failure";
-        Type = "simple";
-      };
+        serviceConfig = {
+          User = "root";
+          ExecStart = "${getExe pkgs.v2raya} --log-disable-timestamp";
+          Environment = [ "V2RAYA_LOG_FILE=/var/log/v2raya/v2raya.log" ];
+          LimitNPROC = 500;
+          LimitNOFILE = 1000000;
+          Restart = "on-failure";
+          Type = "simple";
+        };
 
-      wantedBy = [ "multi-user.target" ];
-      path = with pkgs; [ iptables bash iproute2 ]; # required by v2rayA TProxy functionality
-    };
+        wantedBy = [ "multi-user.target" ];
+        path = with pkgs; [ iptables bash iproute2 ]; # required by v2rayA TProxy functionality
+      };
   };
 
   meta.maintainers = with maintainers; [ elliot ];
diff --git a/nixos/modules/services/networking/wireguard.nix b/nixos/modules/services/networking/wireguard.nix
index 1d6556f626be9..b08f1015e8b8a 100644
--- a/nixos/modules/services/networking/wireguard.nix
+++ b/nixos/modules/services/networking/wireguard.nix
@@ -176,7 +176,7 @@ let
 
       publicKey = mkOption {
         example = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
-        type = types.str;
+        type = types.singleLineStr;
         description = lib.mdDoc "The base64 public key of the peer.";
       };
 
diff --git a/nixos/modules/services/networking/wpa_supplicant.nix b/nixos/modules/services/networking/wpa_supplicant.nix
index 119575bdddb41..0595e9e6df238 100644
--- a/nixos/modules/services/networking/wpa_supplicant.nix
+++ b/nixos/modules/services/networking/wpa_supplicant.nix
@@ -121,11 +121,15 @@ let
         ''}
 
         # substitute environment variables
-        ${pkgs.gawk}/bin/awk '{
-          for(varname in ENVIRON)
-            gsub("@"varname"@", ENVIRON[varname])
-          print
-        }' "${configFile}" > "${finalConfig}"
+        if [ -f "${configFile}" ]; then
+          ${pkgs.gawk}/bin/awk '{
+            for(varname in ENVIRON)
+              gsub("@"varname"@", ENVIRON[varname])
+            print
+          }' "${configFile}" > "${finalConfig}"
+        else
+          touch "${finalConfig}"
+        fi
 
         iface_args="-s ${optionalString cfg.dbusControlled "-u"} -D${cfg.driver} ${configStr}"
 
diff --git a/nixos/modules/services/networking/yggdrasil.nix b/nixos/modules/services/networking/yggdrasil.nix
index 3d5cbdd2dc3ed..fd7193154c6c7 100644
--- a/nixos/modules/services/networking/yggdrasil.nix
+++ b/nixos/modules/services/networking/yggdrasil.nix
@@ -193,7 +193,7 @@ in {
     environment.systemPackages = [ cfg.package ];
   });
   meta = {
-    doc = ./yggdrasil.xml;
+    doc = ./yggdrasil.md;
     maintainers = with lib.maintainers; [ gazally ehmry ];
   };
 }
diff --git a/nixos/modules/services/networking/yggdrasil.xml b/nixos/modules/services/networking/yggdrasil.xml
deleted file mode 100644
index 39faacbf30efe..0000000000000
--- a/nixos/modules/services/networking/yggdrasil.xml
+++ /dev/null
@@ -1,157 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-networking-yggdrasil">
-  <title>Yggdrasil</title>
-  <para>
-    <emphasis>Source:</emphasis>
-    <filename>modules/services/networking/yggdrasil/default.nix</filename>
-  </para>
-  <para>
-    <emphasis>Upstream documentation:</emphasis>
-    <link xlink:href="https://yggdrasil-network.github.io/">https://yggdrasil-network.github.io/</link>
-  </para>
-  <para>
-    Yggdrasil is an early-stage implementation of a fully end-to-end
-    encrypted, self-arranging IPv6 network.
-  </para>
-  <section xml:id="module-services-networking-yggdrasil-configuration">
-    <title>Configuration</title>
-    <section xml:id="module-services-networking-yggdrasil-configuration-simple">
-      <title>Simple ephemeral node</title>
-      <para>
-        An annotated example of a simple configuration:
-      </para>
-      <programlisting>
-{
-  services.yggdrasil = {
-    enable = true;
-    persistentKeys = false;
-      # The NixOS module will generate new keys and a new IPv6 address each time
-      # it is started if persistentKeys is not enabled.
-
-    settings = {
-      Peers = [
-        # Yggdrasil will automatically connect and &quot;peer&quot; with other nodes it
-        # discovers via link-local multicast announcements. Unless this is the
-        # case (it probably isn't) a node needs peers within the existing
-        # network that it can tunnel to.
-        &quot;tcp://1.2.3.4:1024&quot;
-        &quot;tcp://1.2.3.5:1024&quot;
-        # Public peers can be found at
-        # https://github.com/yggdrasil-network/public-peers
-      ];
-    };
-  };
-}
-</programlisting>
-    </section>
-    <section xml:id="module-services-networking-yggdrasil-configuration-prefix">
-      <title>Persistent node with prefix</title>
-      <para>
-        A node with a fixed address that announces a prefix:
-      </para>
-      <programlisting>
-let
-  address = &quot;210:5217:69c0:9afc:1b95:b9f:8718:c3d2&quot;;
-  prefix = &quot;310:5217:69c0:9afc&quot;;
-  # taken from the output of &quot;yggdrasilctl getself&quot;.
-in {
-
-  services.yggdrasil = {
-    enable = true;
-    persistentKeys = true; # Maintain a fixed public key and IPv6 address.
-    settings = {
-      Peers = [ &quot;tcp://1.2.3.4:1024&quot; &quot;tcp://1.2.3.5:1024&quot; ];
-      NodeInfo = {
-        # This information is visible to the network.
-        name = config.networking.hostName;
-        location = &quot;The North Pole&quot;;
-      };
-    };
-  };
-
-  boot.kernel.sysctl.&quot;net.ipv6.conf.all.forwarding&quot; = 1;
-    # Forward traffic under the prefix.
-
-  networking.interfaces.${eth0}.ipv6.addresses = [{
-    # Set a 300::/8 address on the local physical device.
-    address = prefix + &quot;::1&quot;;
-    prefixLength = 64;
-  }];
-
-  services.radvd = {
-    # Announce the 300::/8 prefix to eth0.
-    enable = true;
-    config = ''
-      interface eth0
-      {
-        AdvSendAdvert on;
-        prefix ${prefix}::/64 {
-          AdvOnLink on;
-          AdvAutonomous on;
-        };
-        route 200::/8 {};
-      };
-    '';
-  };
-}
-</programlisting>
-    </section>
-    <section xml:id="module-services-networking-yggdrasil-configuration-container">
-      <title>Yggdrasil attached Container</title>
-      <para>
-        A NixOS container attached to the Yggdrasil network via a node
-        running on the host:
-      </para>
-      <programlisting>
-let
-  yggPrefix64 = &quot;310:5217:69c0:9afc&quot;;
-    # Again, taken from the output of &quot;yggdrasilctl getself&quot;.
-in
-{
-  boot.kernel.sysctl.&quot;net.ipv6.conf.all.forwarding&quot; = 1;
-  # Enable IPv6 forwarding.
-
-  networking = {
-    bridges.br0.interfaces = [ ];
-    # A bridge only to containers…
-
-    interfaces.br0 = {
-      # … configured with a prefix address.
-      ipv6.addresses = [{
-        address = &quot;${yggPrefix64}::1&quot;;
-        prefixLength = 64;
-      }];
-    };
-  };
-
-  containers.foo = {
-    autoStart = true;
-    privateNetwork = true;
-    hostBridge = &quot;br0&quot;;
-    # Attach the container to the bridge only.
-    config = { config, pkgs, ... }: {
-      networking.interfaces.eth0.ipv6 = {
-        addresses = [{
-          # Configure a prefix address.
-          address = &quot;${yggPrefix64}::2&quot;;
-          prefixLength = 64;
-        }];
-        routes = [{
-          # Configure the prefix route.
-          address = &quot;200::&quot;;
-          prefixLength = 7;
-          via = &quot;${yggPrefix64}::1&quot;;
-        }];
-      };
-
-      services.httpd.enable = true;
-      networking.firewall.allowedTCPPorts = [ 80 ];
-    };
-  };
-
-}
-</programlisting>
-    </section>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/search/meilisearch.nix b/nixos/modules/services/search/meilisearch.nix
index 9b727b76b1c69..7c9fa62ae9542 100644
--- a/nixos/modules/services/search/meilisearch.nix
+++ b/nixos/modules/services/search/meilisearch.nix
@@ -9,7 +9,7 @@ in
 {
 
   meta.maintainers = with maintainers; [ Br1ght0ne happysalada ];
-  meta.doc = ./meilisearch.xml;
+  meta.doc = ./meilisearch.md;
 
   ###### interface
 
@@ -115,7 +115,7 @@ in
         MEILI_HTTP_ADDR = "${cfg.listenAddress}:${toString cfg.listenPort}";
         MEILI_NO_ANALYTICS = toString cfg.noAnalytics;
         MEILI_ENV = cfg.environment;
-        MEILI_DUMPS_DIR = "/var/lib/meilisearch/dumps";
+        MEILI_DUMP_DIR = "/var/lib/meilisearch/dumps";
         MEILI_LOG_LEVEL = cfg.logLevel;
         MEILI_MAX_INDEX_SIZE = cfg.maxIndexSize;
       };
diff --git a/nixos/modules/services/search/meilisearch.xml b/nixos/modules/services/search/meilisearch.xml
deleted file mode 100644
index 8bfd64920b039..0000000000000
--- a/nixos/modules/services/search/meilisearch.xml
+++ /dev/null
@@ -1,87 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-meilisearch">
-  <title>Meilisearch</title>
-  <para>
-    Meilisearch is a lightweight, fast and powerful search engine. Think
-    elastic search with a much smaller footprint.
-  </para>
-  <section xml:id="module-services-meilisearch-quickstart">
-    <title>Quickstart</title>
-    <para>
-      the minimum to start meilisearch is
-    </para>
-    <programlisting language="nix">
-services.meilisearch.enable = true;
-</programlisting>
-    <para>
-      this will start the http server included with meilisearch on port
-      7700.
-    </para>
-    <para>
-      test with
-      <literal>curl -X GET 'http://localhost:7700/health'</literal>
-    </para>
-  </section>
-  <section xml:id="module-services-meilisearch-usage">
-    <title>Usage</title>
-    <para>
-      you first need to add documents to an index before you can search
-      for documents.
-    </para>
-    <section xml:id="module-services-meilisearch-quickstart-add">
-      <title>Add a documents to the <literal>movies</literal>
-      index</title>
-      <para>
-        <literal>curl -X POST 'http://127.0.0.1:7700/indexes/movies/documents' --data '[{&quot;id&quot;: &quot;123&quot;, &quot;title&quot;: &quot;Superman&quot;}, {&quot;id&quot;: 234, &quot;title&quot;: &quot;Batman&quot;}]'</literal>
-      </para>
-    </section>
-    <section xml:id="module-services-meilisearch-quickstart-search">
-      <title>Search documents in the <literal>movies</literal>
-      index</title>
-      <para>
-        <literal>curl 'http://127.0.0.1:7700/indexes/movies/search' --data '{ &quot;q&quot;: &quot;botman&quot; }'</literal>
-        (note the typo is intentional and there to demonstrate the typo
-        tolerant capabilities)
-      </para>
-    </section>
-  </section>
-  <section xml:id="module-services-meilisearch-defaults">
-    <title>Defaults</title>
-    <itemizedlist>
-      <listitem>
-        <para>
-          The default nixos package doesn’t come with the
-          <link xlink:href="https://docs.meilisearch.com/learn/getting_started/quick_start.html#search">dashboard</link>,
-          since the dashboard features makes some assets downloads at
-          compile time.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Anonimized Analytics sent to meilisearch are disabled by
-          default.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Default deployment is development mode. It doesn’t require a
-          secret master key. All routes are not protected and
-          accessible.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="module-services-meilisearch-missing">
-    <title>Missing</title>
-    <itemizedlist spacing="compact">
-      <listitem>
-        <para>
-          the snapshot feature is not yet configurable from the module,
-          it’s just a matter of adding the relevant environment
-          variables.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/search/opensearch.nix b/nixos/modules/services/search/opensearch.nix
new file mode 100644
index 0000000000000..9a50e79631380
--- /dev/null
+++ b/nixos/modules/services/search/opensearch.nix
@@ -0,0 +1,248 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.opensearch;
+
+  settingsFormat = pkgs.formats.yaml {};
+
+  configDir = cfg.dataDir + "/config";
+
+  usingDefaultDataDir = cfg.dataDir == "/var/lib/opensearch";
+  usingDefaultUserAndGroup = cfg.user == "opensearch" && cfg.group == "opensearch";
+
+  opensearchYml = settingsFormat.generate "opensearch.yml" cfg.settings;
+
+  loggingConfigFilename = "log4j2.properties";
+  loggingConfigFile = pkgs.writeTextFile {
+    name = loggingConfigFilename;
+    text = cfg.logging;
+  };
+in
+{
+
+  options.services.opensearch = {
+    enable = mkEnableOption (lib.mdDoc "OpenSearch");
+
+    package = lib.mkPackageOptionMD pkgs "OpenSearch" {
+      default = [ "opensearch" ];
+    };
+
+    settings = lib.mkOption {
+      type = lib.types.submodule {
+        freeformType = settingsFormat.type;
+
+        options."network.host" = lib.mkOption {
+          type = lib.types.str;
+          default = "127.0.0.1";
+          description = lib.mdDoc ''
+            Which port this service should listen on.
+          '';
+        };
+
+        options."cluster.name" = lib.mkOption {
+          type = lib.types.str;
+          default = "opensearch";
+          description = lib.mdDoc ''
+            The name of the cluster.
+          '';
+        };
+
+        options."discovery.type" = lib.mkOption {
+          type = lib.types.str;
+          default = "single-node";
+          description = lib.mdDoc ''
+            The type of discovery to use.
+          '';
+        };
+
+        options."http.port" = lib.mkOption {
+          type = lib.types.port;
+          default = 9200;
+          description = lib.mdDoc ''
+            The port to listen on for HTTP traffic.
+          '';
+        };
+
+        options."transport.port" = lib.mkOption {
+          type = lib.types.port;
+          default = 9300;
+          description = lib.mdDoc ''
+            The port to listen on for transport traffic.
+          '';
+        };
+      };
+
+      default = {};
+
+      description = lib.mdDoc ''
+        OpenSearch configuration.
+      '';
+    };
+
+    logging = lib.mkOption {
+      description = lib.mdDoc "opensearch logging configuration.";
+
+      default = ''
+        logger.action.name = org.opensearch.action
+        logger.action.level = info
+
+        appender.console.type = Console
+        appender.console.name = console
+        appender.console.layout.type = PatternLayout
+        appender.console.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] %marker%m%n
+
+        rootLogger.level = info
+        rootLogger.appenderRef.console.ref = console
+      '';
+      type = types.str;
+    };
+
+    dataDir = lib.mkOption {
+      type = lib.types.path;
+      default = "/var/lib/opensearch";
+      apply = converge (removeSuffix "/");
+      description = lib.mdDoc ''
+        Data directory for OpenSearch. If you change this, you need to
+        manually create the directory. You also need to create the
+        `opensearch` user and group, or change
+        [](#opt-services.opensearch.user) and
+        [](#opt-services.opensearch.group) to existing ones with
+        access to the directory.
+      '';
+    };
+
+    user = lib.mkOption {
+      type = lib.types.str;
+      default = "opensearch";
+      description = lib.mdDoc ''
+        The user OpenSearch runs as. Should be left at default unless
+        you have very specific needs.
+      '';
+    };
+
+    group = lib.mkOption {
+      type = lib.types.str;
+      default = "opensearch";
+      description = lib.mdDoc ''
+        The group OpenSearch runs as. Should be left at default unless
+        you have very specific needs.
+      '';
+    };
+
+    extraCmdLineOptions = lib.mkOption {
+      description = lib.mdDoc "Extra command line options for the OpenSearch launcher.";
+      default = [ ];
+      type = lib.types.listOf lib.types.str;
+    };
+
+    extraJavaOptions = lib.mkOption {
+      description = lib.mdDoc "Extra command line options for Java.";
+      default = [ ];
+      type = lib.types.listOf lib.types.str;
+      example = [ "-Djava.net.preferIPv4Stack=true" ];
+    };
+
+    restartIfChanged = lib.mkOption {
+      type = lib.types.bool;
+      description = lib.mdDoc ''
+        Automatically restart the service on config change.
+        This can be set to false to defer restarts on a server or cluster.
+        Please consider the security implications of inadvertently running an older version,
+        and the possibility of unexpected behavior caused by inconsistent versions across a cluster when disabling this option.
+      '';
+      default = true;
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.opensearch = {
+      description = "OpenSearch Daemon";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      path = [ pkgs.inetutils ];
+      inherit (cfg) restartIfChanged;
+      environment = {
+        OPENSEARCH_HOME = cfg.dataDir;
+        OPENSEARCH_JAVA_OPTS = toString cfg.extraJavaOptions;
+        OPENSEARCH_PATH_CONF = configDir;
+      };
+      serviceConfig = {
+        ExecStartPre =
+          let
+            startPreFullPrivileges = ''
+              set -o errexit -o pipefail -o nounset -o errtrace
+              shopt -s inherit_errexit
+            '' + (optionalString (!config.boot.isContainer) ''
+              # Only set vm.max_map_count if lower than ES required minimum
+              # This avoids conflict if configured via boot.kernel.sysctl
+              if [ $(${pkgs.procps}/bin/sysctl -n vm.max_map_count) -lt 262144 ]; then
+                ${pkgs.procps}/bin/sysctl -w vm.max_map_count=262144
+              fi
+            '');
+            startPreUnprivileged = ''
+              set -o errexit -o pipefail -o nounset -o errtrace
+              shopt -s inherit_errexit
+
+              # Install plugins
+              ln -sfT ${cfg.package}/lib ${cfg.dataDir}/lib
+              ln -sfT ${cfg.package}/modules ${cfg.dataDir}/modules
+
+              # opensearch needs to create the opensearch.keystore in the config directory
+              # so this directory needs to be writable.
+              mkdir -p ${configDir}
+              chmod 0700 ${configDir}
+
+              # Note that we copy config files from the nix store instead of symbolically linking them
+              # because otherwise X-Pack Security will raise the following exception:
+              # java.security.AccessControlException:
+              # access denied ("java.io.FilePermission" "/var/lib/opensearch/config/opensearch.yml" "read")
+
+              rm -f ${configDir}/opensearch.yml
+              cp ${opensearchYml} ${configDir}/opensearch.yml
+
+              # Make sure the logging configuration for old OpenSearch versions is removed:
+              rm -f "${configDir}/logging.yml"
+              rm -f ${configDir}/${loggingConfigFilename}
+              cp ${loggingConfigFile} ${configDir}/${loggingConfigFilename}
+              mkdir -p ${configDir}/scripts
+
+              rm -f ${configDir}/jvm.options
+              cp ${cfg.package}/config/jvm.options ${configDir}/jvm.options
+
+              # redirect jvm logs to the data directory
+              mkdir -p ${cfg.dataDir}/logs
+              chmod 0700 ${cfg.dataDir}/logs
+              sed -e '#logs/gc.log#${cfg.dataDir}/logs/gc.log#' -i ${configDir}/jvm.options
+            '';
+          in [
+            "+${pkgs.writeShellScript "opensearch-start-pre-full-privileges" startPreFullPrivileges}"
+            "${pkgs.writeShellScript "opensearch-start-pre-unprivileged" startPreUnprivileged}"
+          ];
+        ExecStartPost = pkgs.writeShellScript "opensearch-start-post" ''
+          set -o errexit -o pipefail -o nounset -o errtrace
+          shopt -s inherit_errexit
+
+          # Make sure opensearch is up and running before dependents
+          # are started
+          while ! ${pkgs.curl}/bin/curl -sS -f http://${cfg.settings."network.host"}:${toString cfg.settings."http.port"} 2>/dev/null; do
+            sleep 1
+          done
+        '';
+        ExecStart = "${cfg.package}/bin/opensearch ${toString cfg.extraCmdLineOptions}";
+        User = cfg.user;
+        Group = cfg.group;
+        LimitNOFILE = "1024000";
+        Restart = "always";
+        TimeoutStartSec = "infinity";
+        DynamicUser = usingDefaultUserAndGroup && usingDefaultDataDir;
+      } // (optionalAttrs (usingDefaultDataDir) {
+        StateDirectory = "opensearch";
+        StateDirectoryMode = "0700";
+      });
+    };
+
+    environment.systemPackages = [ cfg.package ];
+  };
+}
diff --git a/nixos/modules/services/search/qdrant.nix b/nixos/modules/services/search/qdrant.nix
new file mode 100644
index 0000000000000..a843c44dbb5f9
--- /dev/null
+++ b/nixos/modules/services/search/qdrant.nix
@@ -0,0 +1,128 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+
+  cfg = config.services.qdrant;
+
+  settingsFormat = pkgs.formats.yaml { };
+  configFile = settingsFormat.generate "config.yaml" cfg.settings;
+in {
+
+  options = {
+    services.qdrant = {
+      enable = mkEnableOption (lib.mdDoc "Vector Search Engine for the next generation of AI applications");
+
+      settings = mkOption {
+        description = lib.mdDoc ''
+          Configuration for Qdrant
+          Refer to <https://github.com/qdrant/qdrant/blob/master/config/config.yaml> for details on supported values.
+        '';
+
+        type = settingsFormat.type;
+
+        example = {
+          storage = {
+            storage_path = "/var/lib/qdrant/storage";
+            snapshots_path = "/var/lib/qdrant/snapshots";
+          };
+          hsnw_index = {
+            on_disk = true;
+          };
+          service = {
+            host = "127.0.0.1";
+            http_port = 6333;
+            grpc_port = 6334;
+          };
+          telemetry_disabled = true;
+        };
+
+        defaultText = literalExpression ''
+          {
+            storage = {
+              storage_path = "/var/lib/qdrant/storage";
+              snapshots_path = "/var/lib/qdrant/snapshots";
+            };
+            hsnw_index = {
+              on_disk = true;
+            };
+            service = {
+              host = "127.0.0.1";
+              http_port = 6333;
+              grpc_port = 6334;
+            };
+            telemetry_disabled = true;
+          }
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.qdrant.settings = {
+      storage.storage_path = mkDefault "/var/lib/qdrant/storage";
+      storage.snapshots_path = mkDefault "/var/lib/qdrant/snapshots";
+      # The following default values are the same as in the default config,
+      # they are just written here for convenience.
+      storage.on_disk_payload = mkDefault true;
+      storage.wal.wal_capacity_mb = mkDefault 32;
+      storage.wal.wal_segments_ahead = mkDefault 0;
+      storage.performance.max_search_threads = mkDefault 0;
+      storage.performance.max_optimization_threads = mkDefault 1;
+      storage.optimizers.deleted_threshold = mkDefault 0.2;
+      storage.optimizers.vacuum_min_vector_number = mkDefault 1000;
+      storage.optimizers.default_segment_number = mkDefault 0;
+      storage.optimizers.max_segment_size_kb = mkDefault null;
+      storage.optimizers.memmap_threshold_kb = mkDefault null;
+      storage.optimizers.indexing_threshold_kb = mkDefault 20000;
+      storage.optimizers.flush_interval_sec = mkDefault 5;
+      storage.optimizers.max_optimization_threads = mkDefault 1;
+      storage.hnsw_index.m = mkDefault 16;
+      storage.hnsw_index.ef_construct = mkDefault 100;
+      storage.hnsw_index.full_scan_threshold_kb = mkDefault 10000;
+      storage.hnsw_index.max_indexing_threads = mkDefault 0;
+      storage.hnsw_index.on_disk = mkDefault false;
+      storage.hnsw_index.payload_m = mkDefault null;
+      service.max_request_size_mb = mkDefault 32;
+      service.max_workers = mkDefault 0;
+      service.http_port = mkDefault 6333;
+      service.grpc_port = mkDefault 6334;
+      service.enable_cors = mkDefault true;
+      cluster.enabled = mkDefault false;
+      # the following have been altered for security
+      service.host = mkDefault "127.0.0.1";
+      telemetry_disabled = mkDefault true;
+    };
+
+    systemd.services.qdrant = {
+      description = "Vector Search Engine for the next generation of AI applications";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        ExecStart = "${pkgs.qdrant}/bin/qdrant --config-path ${configFile}";
+        DynamicUser = true;
+        Restart = "on-failure";
+        StateDirectory = "qdrant";
+        CapabilityBoundingSet = "";
+        NoNewPrivileges = true;
+        PrivateTmp = 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;
+        SystemCallFilter = [ "@system-service" "~@privileged" ];
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/security/kanidm.nix b/nixos/modules/services/security/kanidm.nix
index 55120799c9934..5583c39368f77 100644
--- a/nixos/modules/services/security/kanidm.nix
+++ b/nixos/modules/services/security/kanidm.nix
@@ -55,7 +55,7 @@ in
   options.services.kanidm = {
     enableClient = lib.mkEnableOption (lib.mdDoc "the Kanidm client");
     enableServer = lib.mkEnableOption (lib.mdDoc "the Kanidm server");
-    enablePam = lib.mkEnableOption (lib.mdDoc "the Kanidm PAM and NSS integration.");
+    enablePam = lib.mkEnableOption (lib.mdDoc "the Kanidm PAM and NSS integration");
 
     serverSettings = lib.mkOption {
       type = lib.types.submodule {
diff --git a/nixos/modules/services/security/privacyidea.nix b/nixos/modules/services/security/privacyidea.nix
index e446e606cad8b..664335cb58e89 100644
--- a/nixos/modules/services/security/privacyidea.nix
+++ b/nixos/modules/services/security/privacyidea.nix
@@ -6,7 +6,7 @@ let
   cfg = config.services.privacyidea;
   opt = options.services.privacyidea;
 
-  uwsgi = pkgs.uwsgi.override { plugins = [ "python3" ]; python3 = pkgs.python39; };
+  uwsgi = pkgs.uwsgi.override { plugins = [ "python3" ]; python3 = pkgs.python310; };
   python = uwsgi.python3;
   penv = python.withPackages (const [ pkgs.privacyidea ]);
   logCfg = pkgs.writeText "privacyidea-log.cfg" ''
@@ -41,7 +41,7 @@ let
 
   piCfgFile = pkgs.writeText "privacyidea.cfg" ''
     SUPERUSER_REALM = [ '${concatStringsSep "', '" cfg.superuserRealm}' ]
-    SQLALCHEMY_DATABASE_URI = 'postgresql:///privacyidea'
+    SQLALCHEMY_DATABASE_URI = 'postgresql+psycopg2:///privacyidea'
     SECRET_KEY = '${cfg.secretKey}'
     PI_PEPPER = '${cfg.pepper}'
     PI_ENCFILE = '${cfg.encFile}'
diff --git a/nixos/modules/services/security/yubikey-agent.nix b/nixos/modules/services/security/yubikey-agent.nix
index c91ff3e69a0a4..ee57ec8bf8120 100644
--- a/nixos/modules/services/security/yubikey-agent.nix
+++ b/nixos/modules/services/security/yubikey-agent.nix
@@ -57,6 +57,9 @@ in
       ];
     };
 
+    # Yubikey-agent expects pcsd to be running in order to function.
+    services.pcscd.enable = true;
+
     environment.extraInit = ''
       if [ -z "$SSH_AUTH_SOCK" -a -n "$XDG_RUNTIME_DIR" ]; then
         export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/yubikey-agent/yubikey-agent.sock"
diff --git a/nixos/modules/services/system/nscd.nix b/nixos/modules/services/system/nscd.nix
index fdc5190d084b3..971dffbadc132 100644
--- a/nixos/modules/services/system/nscd.nix
+++ b/nixos/modules/services/system/nscd.nix
@@ -29,10 +29,11 @@ in
 
       enableNsncd = mkOption {
         type = types.bool;
-        default = false;
+        default = true;
         description = lib.mdDoc ''
-          Whether to use nsncd instead of nscd.
+          Whether to use nsncd instead of nscd from glibc.
           This is a nscd-compatible daemon, that proxies lookups, without any caching.
+          Using nscd from glibc is discouraged.
         '';
       };
 
@@ -55,7 +56,10 @@ in
       config = mkOption {
         type = types.lines;
         default = builtins.readFile ./nscd.conf;
-        description = lib.mdDoc "Configuration to use for Name Service Cache Daemon.";
+        description = lib.mdDoc ''
+          Configuration to use for Name Service Cache Daemon.
+          Only used in case glibc-nscd is used.
+        '';
       };
 
       package = mkOption {
diff --git a/nixos/modules/services/torrent/rtorrent.nix b/nixos/modules/services/torrent/rtorrent.nix
index 627439e1079bf..64cda7fb675f8 100644
--- a/nixos/modules/services/torrent/rtorrent.nix
+++ b/nixos/modules/services/torrent/rtorrent.nix
@@ -19,6 +19,15 @@ in {
       '';
     };
 
+    dataPermissions = mkOption {
+      type = types.str;
+      default = "0750";
+      example = "0755";
+      description = lib.mdDoc ''
+        Unix Permissions in octal on the rtorrent directory.
+      '';
+    };
+
     downloadDir = mkOption {
       type = types.str;
       default = "${cfg.dataDir}/download";
@@ -205,7 +214,7 @@ in {
         };
       };
 
-      tmpfiles.rules = [ "d '${cfg.dataDir}' 0750 ${cfg.user} ${cfg.group} -" ];
+      tmpfiles.rules = [ "d '${cfg.dataDir}' ${cfg.dataPermissions} ${cfg.user} ${cfg.group} -" ];
     };
   };
 }
diff --git a/nixos/modules/services/web-apps/akkoma.md b/nixos/modules/services/web-apps/akkoma.md
index fc849be0c8726..5419940a68d63 100644
--- a/nixos/modules/services/web-apps/akkoma.md
+++ b/nixos/modules/services/web-apps/akkoma.md
@@ -152,7 +152,7 @@ services.akkoma.config.":pleroma".":media_preview_proxy" = {
 
 ## Frontend management {#modules-services-akkoma-frontend-management}
 
-Akkoma will be deployed with the `pleroma-fe` and `admin-fe` frontends by default. These can be
+Akkoma will be deployed with the `akkoma-fe` and `admin-fe` frontends by default. These can be
 modified by setting
 [{option}`services.akkoma.frontends`](options.html#opt-services.akkoma.frontends).
 
@@ -160,7 +160,7 @@ The following example overrides the primary frontend’s default configuration u
 derivation.
 
 ```nix
-services.akkoma.frontends.primary.package = pkgs.runCommand "pleroma-fe" {
+services.akkoma.frontends.primary.package = pkgs.runCommand "akkoma-fe" {
   config = builtins.toJSON {
     expertLevel = 1;
     collapseMessageWithSubject = false;
@@ -177,10 +177,10 @@ services.akkoma.frontends.primary.package = pkgs.runCommand "pleroma-fe" {
   passAsFile = [ "config" ];
 } ''
   mkdir $out
-  lndir ${pkgs.akkoma-frontends.pleroma-fe} $out
+  lndir ${pkgs.akkoma-frontends.akkoma-fe} $out
 
   rm $out/static/config.json
-  jq -s add ${pkgs.akkoma-frontends.pleroma-fe}/static/config.json ${config} \
+  jq -s add ${pkgs.akkoma-frontends.akkoma-fe}/static/config.json ${config} \
     >$out/static/config.json
 '';
 ```
diff --git a/nixos/modules/services/web-apps/akkoma.nix b/nixos/modules/services/web-apps/akkoma.nix
index 47ba53e42221a..8d17752586128 100644
--- a/nixos/modules/services/web-apps/akkoma.nix
+++ b/nixos/modules/services/web-apps/akkoma.nix
@@ -51,13 +51,13 @@ let
       package = mkOption {
         type = types.package;
         description = mdDoc "Akkoma frontend package.";
-        example = literalExpression "pkgs.akkoma-frontends.pleroma-fe";
+        example = literalExpression "pkgs.akkoma-frontends.akkoma-fe";
       };
 
       name = mkOption {
         type = types.nonEmptyStr;
         description = mdDoc "Akkoma frontend name.";
-        example = "pleroma-fe";
+        example = "akkoma-fe";
       };
 
       ref = mkOption {
@@ -476,8 +476,8 @@ in {
         type = with types; attrsOf (submodule frontend);
         default = {
           primary = {
-            package = pkgs.akkoma-frontends.pleroma-fe;
-            name = "pleroma-fe";
+            package = pkgs.akkoma-frontends.akkoma-fe;
+            name = "akkoma-fe";
             ref = "stable";
           };
           admin = {
@@ -489,8 +489,8 @@ in {
         defaultText = literalExpression ''
           {
             primary = {
-              package = pkgs.akkoma-frontends.pleroma-fe;
-              name = "pleroma-fe";
+              package = pkgs.akkoma-frontends.akkoma-fe;
+              name = "akkoma-fe";
               ref = "stable";
             };
             admin = {
@@ -1082,5 +1082,5 @@ in {
   };
 
   meta.maintainers = with maintainers; [ mvs ];
-  meta.doc = ./akkoma.xml;
+  meta.doc = ./akkoma.md;
 }
diff --git a/nixos/modules/services/web-apps/akkoma.xml b/nixos/modules/services/web-apps/akkoma.xml
deleted file mode 100644
index 49cbcc911e1d1..0000000000000
--- a/nixos/modules/services/web-apps/akkoma.xml
+++ /dev/null
@@ -1,398 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-akkoma">
-  <title>Akkoma</title>
-  <para>
-    <link xlink:href="https://akkoma.dev/">Akkoma</link> is a
-    lightweight ActivityPub microblogging server forked from Pleroma.
-  </para>
-  <section xml:id="modules-services-akkoma-service-configuration">
-    <title>Service configuration</title>
-    <para>
-      The Elixir configuration file required by Akkoma is generated
-      automatically from
-      <link xlink:href="options.html#opt-services.akkoma.config"><option>services.akkoma.config</option></link>.
-      Secrets must be included from external files outside of the Nix
-      store by setting the configuration option to an attribute set
-      containing the attribute <option>_secret</option> – a string
-      pointing to the file containing the actual value of the option.
-    </para>
-    <para>
-      For the mandatory configuration settings these secrets will be
-      generated automatically if the referenced file does not exist
-      during startup, unless disabled through
-      <link xlink:href="options.html#opt-services.akkoma.initSecrets"><option>services.akkoma.initSecrets</option></link>.
-    </para>
-    <para>
-      The following configuration binds Akkoma to the Unix socket
-      <literal>/run/akkoma/socket</literal>, expecting to be run behind
-      a HTTP proxy on <literal>fediverse.example.com</literal>.
-    </para>
-    <programlisting language="nix">
-services.akkoma.enable = true;
-services.akkoma.config = {
-  &quot;:pleroma&quot; = {
-    &quot;:instance&quot; = {
-      name = &quot;My Akkoma instance&quot;;
-      description = &quot;More detailed description&quot;;
-      email = &quot;admin@example.com&quot;;
-      registration_open = false;
-    };
-
-    &quot;Pleroma.Web.Endpoint&quot; = {
-      url.host = &quot;fediverse.example.com&quot;;
-    };
-  };
-};
-</programlisting>
-    <para>
-      Please refer to the
-      <link xlink:href="https://docs.akkoma.dev/stable/configuration/cheatsheet/">configuration
-      cheat sheet</link> for additional configuration options.
-    </para>
-  </section>
-  <section xml:id="modules-services-akkoma-user-management">
-    <title>User management</title>
-    <para>
-      After the Akkoma service is running, the administration utility
-      can be used to
-      <link xlink:href="https://docs.akkoma.dev/stable/administration/CLI_tasks/user/">manage
-      users</link>. In particular an administrative user can be created
-      with
-    </para>
-    <programlisting>
-$ pleroma_ctl user new &lt;nickname&gt; &lt;email&gt; --admin --moderator --password &lt;password&gt;
-</programlisting>
-  </section>
-  <section xml:id="modules-services-akkoma-proxy-configuration">
-    <title>Proxy configuration</title>
-    <para>
-      Although it is possible to expose Akkoma directly, it is common
-      practice to operate it behind an HTTP reverse proxy such as nginx.
-    </para>
-    <programlisting language="nix">
-services.akkoma.nginx = {
-  enableACME = true;
-  forceSSL = true;
-};
-
-services.nginx = {
-  enable = true;
-
-  clientMaxBodySize = &quot;16m&quot;;
-  recommendedTlsSettings = true;
-  recommendedOptimisation = true;
-  recommendedGzipSettings = true;
-};
-</programlisting>
-    <para>
-      Please refer to <xref linkend="module-security-acme" /> for
-      details on how to provision an SSL/TLS certificate.
-    </para>
-    <section xml:id="modules-services-akkoma-media-proxy">
-      <title>Media proxy</title>
-      <para>
-        Without the media proxy function, Akkoma does not store any
-        remote media like pictures or video locally, and clients have to
-        fetch them directly from the source server.
-      </para>
-      <programlisting language="nix">
-# Enable nginx slice module distributed with Tengine
-services.nginx.package = pkgs.tengine;
-
-# Enable media proxy
-services.akkoma.config.&quot;:pleroma&quot;.&quot;:media_proxy&quot; = {
-  enabled = true;
-  proxy_opts.redirect_on_failure = true;
-};
-
-# Adjust the persistent cache size as needed:
-#  Assuming an average object size of 128 KiB, around 1 MiB
-#  of memory is required for the key zone per GiB of cache.
-# Ensure that the cache directory exists and is writable by nginx.
-services.nginx.commonHttpConfig = ''
-  proxy_cache_path /var/cache/nginx/cache/akkoma-media-cache
-    levels= keys_zone=akkoma_media_cache:16m max_size=16g
-    inactive=1y use_temp_path=off;
-'';
-
-services.akkoma.nginx = {
-  locations.&quot;/proxy&quot; = {
-    proxyPass = &quot;http://unix:/run/akkoma/socket&quot;;
-
-    extraConfig = ''
-      proxy_cache akkoma_media_cache;
-
-      # Cache objects in slices of 1 MiB
-      slice 1m;
-      proxy_cache_key $host$uri$is_args$args$slice_range;
-      proxy_set_header Range $slice_range;
-
-      # Decouple proxy and upstream responses
-      proxy_buffering on;
-      proxy_cache_lock on;
-      proxy_ignore_client_abort on;
-
-      # Default cache times for various responses
-      proxy_cache_valid 200 1y;
-      proxy_cache_valid 206 301 304 1h;
-
-      # Allow serving of stale items
-      proxy_cache_use_stale error timeout invalid_header updating;
-    '';
-  };
-};
-</programlisting>
-      <section xml:id="modules-services-akkoma-prefetch-remote-media">
-        <title>Prefetch remote media</title>
-        <para>
-          The following example enables the
-          <literal>MediaProxyWarmingPolicy</literal> MRF policy which
-          automatically fetches all media associated with a post through
-          the media proxy, as soon as the post is received by the
-          instance.
-        </para>
-        <programlisting language="nix">
-services.akkoma.config.&quot;:pleroma&quot;.&quot;:mrf&quot;.policies =
-  map (pkgs.formats.elixirConf { }).lib.mkRaw [
-    &quot;Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy&quot;
-];
-</programlisting>
-      </section>
-      <section xml:id="modules-services-akkoma-media-previews">
-        <title>Media previews</title>
-        <para>
-          Akkoma can generate previews for media.
-        </para>
-        <programlisting language="nix">
-services.akkoma.config.&quot;:pleroma&quot;.&quot;:media_preview_proxy&quot; = {
-  enabled = true;
-  thumbnail_max_width = 1920;
-  thumbnail_max_height = 1080;
-};
-</programlisting>
-      </section>
-    </section>
-  </section>
-  <section xml:id="modules-services-akkoma-frontend-management">
-    <title>Frontend management</title>
-    <para>
-      Akkoma will be deployed with the <literal>pleroma-fe</literal> and
-      <literal>admin-fe</literal> frontends by default. These can be
-      modified by setting
-      <link xlink:href="options.html#opt-services.akkoma.frontends"><option>services.akkoma.frontends</option></link>.
-    </para>
-    <para>
-      The following example overrides the primary frontend’s default
-      configuration using a custom derivation.
-    </para>
-    <programlisting language="nix">
-services.akkoma.frontends.primary.package = pkgs.runCommand &quot;pleroma-fe&quot; {
-  config = builtins.toJSON {
-    expertLevel = 1;
-    collapseMessageWithSubject = false;
-    stopGifs = false;
-    replyVisibility = &quot;following&quot;;
-    webPushHideIfCW = true;
-    hideScopeNotice = true;
-    renderMisskeyMarkdown = false;
-    hideSiteFavicon = true;
-    postContentType = &quot;text/markdown&quot;;
-    showNavShortcuts = false;
-  };
-  nativeBuildInputs = with pkgs; [ jq xorg.lndir ];
-  passAsFile = [ &quot;config&quot; ];
-} ''
-  mkdir $out
-  lndir ${pkgs.akkoma-frontends.pleroma-fe} $out
-
-  rm $out/static/config.json
-  jq -s add ${pkgs.akkoma-frontends.pleroma-fe}/static/config.json ${config} \
-    &gt;$out/static/config.json
-'';
-</programlisting>
-  </section>
-  <section xml:id="modules-services-akkoma-federation-policies">
-    <title>Federation policies</title>
-    <para>
-      Akkoma comes with a number of modules to police federation with
-      other ActivityPub instances. The most valuable for typical users
-      is the
-      <link xlink:href="https://docs.akkoma.dev/stable/configuration/cheatsheet/#mrf_simple"><literal>:mrf_simple</literal></link>
-      module which allows limiting federation based on instance
-      hostnames.
-    </para>
-    <para>
-      This configuration snippet provides an example on how these can be
-      used. Choosing an adequate federation policy is not trivial and
-      entails finding a balance between connectivity to the rest of the
-      fediverse and providing a pleasant experience to the users of an
-      instance.
-    </para>
-    <programlisting language="nix">
-services.akkoma.config.&quot;:pleroma&quot; = with (pkgs.formats.elixirConf { }).lib; {
-  &quot;:mrf&quot;.policies = map mkRaw [
-    &quot;Pleroma.Web.ActivityPub.MRF.SimplePolicy&quot;
-  ];
-
-  &quot;:mrf_simple&quot; = {
-    # Tag all media as sensitive
-    media_nsfw = mkMap {
-      &quot;nsfw.weird.kinky&quot; = &quot;Untagged NSFW content&quot;;
-    };
-
-    # Reject all activities except deletes
-    reject = mkMap {
-      &quot;kiwifarms.cc&quot; = &quot;Persistent harassment of users, no moderation&quot;;
-    };
-
-    # Force posts to be visible by followers only
-    followers_only = mkMap {
-      &quot;beta.birdsite.live&quot; = &quot;Avoid polluting timelines with Twitter posts&quot;;
-    };
-  };
-};
-</programlisting>
-  </section>
-  <section xml:id="modules-services-akkoma-upload-filters">
-    <title>Upload filters</title>
-    <para>
-      This example strips GPS and location metadata from uploads,
-      deduplicates them and anonymises the the file name.
-    </para>
-    <programlisting language="nix">
-services.akkoma.config.&quot;:pleroma&quot;.&quot;Pleroma.Upload&quot;.filters =
-  map (pkgs.formats.elixirConf { }).lib.mkRaw [
-    &quot;Pleroma.Upload.Filter.Exiftool&quot;
-    &quot;Pleroma.Upload.Filter.Dedupe&quot;
-    &quot;Pleroma.Upload.Filter.AnonymizeFilename&quot;
-  ];
-</programlisting>
-  </section>
-  <section xml:id="modules-services-akkoma-migration-pleroma">
-    <title>Migration from Pleroma</title>
-    <para>
-      Pleroma instances can be migrated to Akkoma either by copying the
-      database and upload data or by pointing Akkoma to the existing
-      data. The necessary database migrations are run automatically
-      during startup of the service.
-    </para>
-    <para>
-      The configuration has to be copy‐edited manually.
-    </para>
-    <para>
-      Depending on the size of the database, the initial migration may
-      take a long time and exceed the startup timeout of the system
-      manager. To work around this issue one may adjust the startup
-      timeout
-      <option>systemd.services.akkoma.serviceConfig.TimeoutStartSec</option>
-      or simply run the migrations manually:
-    </para>
-    <programlisting>
-pleroma_ctl migrate
-</programlisting>
-    <section xml:id="modules-services-akkoma-migration-pleroma-copy">
-      <title>Copying data</title>
-      <para>
-        Copying the Pleroma data instead of re‐using it in place may
-        permit easier reversion to Pleroma, but allows the two data sets
-        to diverge.
-      </para>
-      <para>
-        First disable Pleroma and then copy its database and upload
-        data:
-      </para>
-      <programlisting>
-# Create a copy of the database
-nix-shell -p postgresql --run 'createdb -T pleroma akkoma'
-
-# Copy upload data
-mkdir /var/lib/akkoma
-cp -R --reflink=auto /var/lib/pleroma/uploads /var/lib/akkoma/
-</programlisting>
-      <para>
-        After the data has been copied, enable the Akkoma service and
-        verify that the migration has been successful. If no longer
-        required, the original data may then be deleted:
-      </para>
-      <programlisting>
-# Delete original database
-nix-shell -p postgresql --run 'dropdb pleroma'
-
-# Delete original Pleroma state
-rm -r /var/lib/pleroma
-</programlisting>
-    </section>
-    <section xml:id="modules-services-akkoma-migration-pleroma-reuse">
-      <title>Re‐using data</title>
-      <para>
-        To re‐use the Pleroma data in place, disable Pleroma and enable
-        Akkoma, pointing it to the Pleroma database and upload
-        directory.
-      </para>
-      <programlisting language="nix">
-# Adjust these settings according to the database name and upload directory path used by Pleroma
-services.akkoma.config.&quot;:pleroma&quot;.&quot;Pleroma.Repo&quot;.database = &quot;pleroma&quot;;
-services.akkoma.config.&quot;:pleroma&quot;.&quot;:instance&quot;.upload_dir = &quot;/var/lib/pleroma/uploads&quot;;
-</programlisting>
-      <para>
-        Please keep in mind that after the Akkoma service has been
-        started, any migrations applied by Akkoma have to be rolled back
-        before the database can be used again with Pleroma. This can be
-        achieved through <literal>pleroma_ctl ecto.rollback</literal>.
-        Refer to the
-        <link xlink:href="https://hexdocs.pm/ecto_sql/Mix.Tasks.Ecto.Rollback.html">Ecto
-        SQL documentation</link> for details.
-      </para>
-    </section>
-  </section>
-  <section xml:id="modules-services-akkoma-advanced-deployment">
-    <title>Advanced deployment options</title>
-    <section xml:id="modules-services-akkoma-confinement">
-      <title>Confinement</title>
-      <para>
-        The Akkoma systemd service may be confined to a chroot with
-      </para>
-      <programlisting language="nix">
-services.systemd.akkoma.confinement.enable = true;
-</programlisting>
-      <para>
-        Confinement of services is not generally supported in NixOS and
-        therefore disabled by default. Depending on the Akkoma
-        configuration, the default confinement settings may be
-        insufficient and lead to subtle errors at run time, requiring
-        adjustment:
-      </para>
-      <para>
-        Use
-        <link xlink:href="options.html#opt-systemd.services._name_.confinement.packages"><option>services.systemd.akkoma.confinement.packages</option></link>
-        to make packages available in the chroot.
-      </para>
-      <para>
-        <option>services.systemd.akkoma.serviceConfig.BindPaths</option>
-        and
-        <option>services.systemd.akkoma.serviceConfig.BindReadOnlyPaths</option>
-        permit access to outside paths through bind mounts. Refer to
-        <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemd.exec.html#BindPaths="><citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry></link>
-        for details.
-      </para>
-    </section>
-    <section xml:id="modules-services-akkoma-distributed-deployment">
-      <title>Distributed deployment</title>
-      <para>
-        Being an Elixir application, Akkoma can be deployed in a
-        distributed fashion.
-      </para>
-      <para>
-        This requires setting
-        <link xlink:href="options.html#opt-services.akkoma.dist.address"><option>services.akkoma.dist.address</option></link>
-        and
-        <link xlink:href="options.html#opt-services.akkoma.dist.cookie"><option>services.akkoma.dist.cookie</option></link>.
-        The specifics depend strongly on the deployment environment. For
-        more information please check the relevant
-        <link xlink:href="https://www.erlang.org/doc/reference_manual/distributed.html">Erlang
-        documentation</link>.
-      </para>
-    </section>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/web-apps/alps.nix b/nixos/modules/services/web-apps/alps.nix
index 1a58df2da1d29..05fb676102df4 100644
--- a/nixos/modules/services/web-apps/alps.nix
+++ b/nixos/modules/services/web-apps/alps.nix
@@ -84,7 +84,7 @@ in {
         "-addr" "${cfg.bindIP}:${toString cfg.port}"
         "-theme" "${cfg.theme}"
         "imaps://${cfg.imaps.host}:${toString cfg.imaps.port}"
-        "smpts://${cfg.smtps.host}:${toString cfg.smtps.port}"
+        "smtps://${cfg.smtps.host}:${toString cfg.smtps.port}"
       ];
     };
   };
diff --git a/nixos/modules/services/web-apps/cloudlog.nix b/nixos/modules/services/web-apps/cloudlog.nix
index ffd1085bde653..9c6284fd1b57a 100644
--- a/nixos/modules/services/web-apps/cloudlog.nix
+++ b/nixos/modules/services/web-apps/cloudlog.nix
@@ -68,7 +68,7 @@ let
 in
 {
   options.services.cloudlog = with types; {
-    enable = mkEnableOption (mdDoc "Whether to enable Cloudlog.");
+    enable = mkEnableOption (mdDoc "Whether to enable Cloudlog");
     dataDir = mkOption {
       type = str;
       default = "/var/lib/cloudlog";
diff --git a/nixos/modules/services/web-apps/discourse.nix b/nixos/modules/services/web-apps/discourse.nix
index b8104ade4676d..151fb812ddea6 100644
--- a/nixos/modules/services/web-apps/discourse.nix
+++ b/nixos/modules/services/web-apps/discourse.nix
@@ -615,6 +615,7 @@ in
       s3_endpoint = null;
       s3_http_continue_timeout = null;
       s3_install_cors_rule = null;
+      s3_asset_cdn_url = null;
 
       max_user_api_reqs_per_minute = 20;
       max_user_api_reqs_per_day = 2880;
@@ -647,6 +648,9 @@ in
       multisite_config_path = "config/multisite.yml";
       enable_long_polling = null;
       long_polling_interval = null;
+      preload_link_header = false;
+      redirect_avatar_requests = false;
+      pg_force_readonly_mode = false;
     };
 
     services.redis.servers.discourse =
@@ -1011,6 +1015,7 @@ in
         notification_email = cfg.mail.notificationEmailAddress;
         contact_email = cfg.mail.contactEmailAddress;
       };
+      security.force_https = tlsEnabled;
       email = {
         manual_polling_enabled = cfg.mail.incoming.enable;
         reply_by_email_enabled = cfg.mail.incoming.enable;
@@ -1080,6 +1085,6 @@ in
     ];
   };
 
-  meta.doc = ./discourse.xml;
+  meta.doc = ./discourse.md;
   meta.maintainers = [ lib.maintainers.talyz ];
 }
diff --git a/nixos/modules/services/web-apps/discourse.xml b/nixos/modules/services/web-apps/discourse.xml
deleted file mode 100644
index a5e8b3656b7de..0000000000000
--- a/nixos/modules/services/web-apps/discourse.xml
+++ /dev/null
@@ -1,331 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-discourse">
-  <title>Discourse</title>
-  <para>
-    <link xlink:href="https://www.discourse.org/">Discourse</link> is a
-    modern and open source discussion platform.
-  </para>
-  <section xml:id="module-services-discourse-basic-usage">
-    <title>Basic usage</title>
-    <para>
-      A minimal configuration using Let’s Encrypt for TLS certificates
-      looks like this:
-    </para>
-    <programlisting>
-services.discourse = {
-  enable = true;
-  hostname = &quot;discourse.example.com&quot;;
-  admin = {
-    email = &quot;admin@example.com&quot;;
-    username = &quot;admin&quot;;
-    fullName = &quot;Administrator&quot;;
-    passwordFile = &quot;/path/to/password_file&quot;;
-  };
-  secretKeyBaseFile = &quot;/path/to/secret_key_base_file&quot;;
-};
-security.acme.email = &quot;me@example.com&quot;;
-security.acme.acceptTerms = true;
-</programlisting>
-    <para>
-      Provided a proper DNS setup, you’ll be able to connect to the
-      instance at <literal>discourse.example.com</literal> and log in
-      using the credentials provided in
-      <literal>services.discourse.admin</literal>.
-    </para>
-  </section>
-  <section xml:id="module-services-discourse-tls">
-    <title>Using a regular TLS certificate</title>
-    <para>
-      To set up TLS using a regular certificate and key on file, use the
-      <xref linkend="opt-services.discourse.sslCertificate" /> and
-      <xref linkend="opt-services.discourse.sslCertificateKey" />
-      options:
-    </para>
-    <programlisting>
-services.discourse = {
-  enable = true;
-  hostname = &quot;discourse.example.com&quot;;
-  sslCertificate = &quot;/path/to/ssl_certificate&quot;;
-  sslCertificateKey = &quot;/path/to/ssl_certificate_key&quot;;
-  admin = {
-    email = &quot;admin@example.com&quot;;
-    username = &quot;admin&quot;;
-    fullName = &quot;Administrator&quot;;
-    passwordFile = &quot;/path/to/password_file&quot;;
-  };
-  secretKeyBaseFile = &quot;/path/to/secret_key_base_file&quot;;
-};
-</programlisting>
-  </section>
-  <section xml:id="module-services-discourse-database">
-    <title>Database access</title>
-    <para>
-      Discourse uses PostgreSQL to store most of its data. A database
-      will automatically be enabled and a database and role created
-      unless <xref linkend="opt-services.discourse.database.host" /> is
-      changed from its default of <literal>null</literal> or
-      <xref linkend="opt-services.discourse.database.createLocally" />
-      is set to <literal>false</literal>.
-    </para>
-    <para>
-      External database access can also be configured by setting
-      <xref linkend="opt-services.discourse.database.host" />,
-      <xref linkend="opt-services.discourse.database.username" /> and
-      <xref linkend="opt-services.discourse.database.passwordFile" /> as
-      appropriate. Note that you need to manually create a database
-      called <literal>discourse</literal> (or the name you chose in
-      <xref linkend="opt-services.discourse.database.name" />) and allow
-      the configured database user full access to it.
-    </para>
-  </section>
-  <section xml:id="module-services-discourse-mail">
-    <title>Email</title>
-    <para>
-      In addition to the basic setup, you’ll want to configure an SMTP
-      server Discourse can use to send user registration and password
-      reset emails, among others. You can also optionally let Discourse
-      receive email, which enables people to reply to threads and
-      conversations via email.
-    </para>
-    <para>
-      A basic setup which assumes you want to use your configured
-      <link linkend="opt-services.discourse.hostname">hostname</link> as
-      email domain can be done like this:
-    </para>
-    <programlisting>
-services.discourse = {
-  enable = true;
-  hostname = &quot;discourse.example.com&quot;;
-  sslCertificate = &quot;/path/to/ssl_certificate&quot;;
-  sslCertificateKey = &quot;/path/to/ssl_certificate_key&quot;;
-  admin = {
-    email = &quot;admin@example.com&quot;;
-    username = &quot;admin&quot;;
-    fullName = &quot;Administrator&quot;;
-    passwordFile = &quot;/path/to/password_file&quot;;
-  };
-  mail.outgoing = {
-    serverAddress = &quot;smtp.emailprovider.com&quot;;
-    port = 587;
-    username = &quot;user@emailprovider.com&quot;;
-    passwordFile = &quot;/path/to/smtp_password_file&quot;;
-  };
-  mail.incoming.enable = true;
-  secretKeyBaseFile = &quot;/path/to/secret_key_base_file&quot;;
-};
-</programlisting>
-    <para>
-      This assumes you have set up an MX record for the address you’ve
-      set in
-      <link linkend="opt-services.discourse.hostname">hostname</link>
-      and requires proper SPF, DKIM and DMARC configuration to be done
-      for the domain you’re sending from, in order for email to be
-      reliably delivered.
-    </para>
-    <para>
-      If you want to use a different domain for your outgoing email (for
-      example <literal>example.com</literal> instead of
-      <literal>discourse.example.com</literal>) you should set
-      <xref linkend="opt-services.discourse.mail.notificationEmailAddress" />
-      and
-      <xref linkend="opt-services.discourse.mail.contactEmailAddress" />
-      manually.
-    </para>
-    <note>
-      <para>
-        Setup of TLS for incoming email is currently only configured
-        automatically when a regular TLS certificate is used, i.e. when
-        <xref linkend="opt-services.discourse.sslCertificate" /> and
-        <xref linkend="opt-services.discourse.sslCertificateKey" /> are
-        set.
-      </para>
-    </note>
-  </section>
-  <section xml:id="module-services-discourse-settings">
-    <title>Additional settings</title>
-    <para>
-      Additional site settings and backend settings, for which no
-      explicit NixOS options are provided, can be set in
-      <xref linkend="opt-services.discourse.siteSettings" /> and
-      <xref linkend="opt-services.discourse.backendSettings" />
-      respectively.
-    </para>
-    <section xml:id="module-services-discourse-site-settings">
-      <title>Site settings</title>
-      <para>
-        <quote>Site settings</quote> are the settings that can be
-        changed through the Discourse UI. Their
-        <emphasis>default</emphasis> values can be set using
-        <xref linkend="opt-services.discourse.siteSettings" />.
-      </para>
-      <para>
-        Settings are expressed as a Nix attribute set which matches the
-        structure of the configuration in
-        <link xlink:href="https://github.com/discourse/discourse/blob/master/config/site_settings.yml">config/site_settings.yml</link>.
-        To find a setting’s path, you only need to care about the first
-        two levels; i.e. its category (e.g. <literal>login</literal>)
-        and name (e.g. <literal>invite_only</literal>).
-      </para>
-      <para>
-        Settings containing secret data should be set to an attribute
-        set containing the attribute <literal>_secret</literal> - a
-        string pointing to a file containing the value the option should
-        be set to. See the example.
-      </para>
-    </section>
-    <section xml:id="module-services-discourse-backend-settings">
-      <title>Backend settings</title>
-      <para>
-        Settings are expressed as a Nix attribute set which matches the
-        structure of the configuration in
-        <link xlink:href="https://github.com/discourse/discourse/blob/stable/config/discourse_defaults.conf">config/discourse.conf</link>.
-        Empty parameters can be defined by setting them to
-        <literal>null</literal>.
-      </para>
-    </section>
-    <section xml:id="module-services-discourse-settings-example">
-      <title>Example</title>
-      <para>
-        The following example sets the title and description of the
-        Discourse instance and enables GitHub login in the site
-        settings, and changes a few request limits in the backend
-        settings:
-      </para>
-      <programlisting>
-services.discourse = {
-  enable = true;
-  hostname = &quot;discourse.example.com&quot;;
-  sslCertificate = &quot;/path/to/ssl_certificate&quot;;
-  sslCertificateKey = &quot;/path/to/ssl_certificate_key&quot;;
-  admin = {
-    email = &quot;admin@example.com&quot;;
-    username = &quot;admin&quot;;
-    fullName = &quot;Administrator&quot;;
-    passwordFile = &quot;/path/to/password_file&quot;;
-  };
-  mail.outgoing = {
-    serverAddress = &quot;smtp.emailprovider.com&quot;;
-    port = 587;
-    username = &quot;user@emailprovider.com&quot;;
-    passwordFile = &quot;/path/to/smtp_password_file&quot;;
-  };
-  mail.incoming.enable = true;
-  siteSettings = {
-    required = {
-      title = &quot;My Cats&quot;;
-      site_description = &quot;Discuss My Cats (and be nice plz)&quot;;
-    };
-    login = {
-      enable_github_logins = true;
-      github_client_id = &quot;a2f6dfe838cb3206ce20&quot;;
-      github_client_secret._secret = /run/keys/discourse_github_client_secret;
-    };
-  };
-  backendSettings = {
-    max_reqs_per_ip_per_minute = 300;
-    max_reqs_per_ip_per_10_seconds = 60;
-    max_asset_reqs_per_ip_per_10_seconds = 250;
-    max_reqs_per_ip_mode = &quot;warn+block&quot;;
-  };
-  secretKeyBaseFile = &quot;/path/to/secret_key_base_file&quot;;
-};
-</programlisting>
-      <para>
-        In the resulting site settings file, the
-        <literal>login.github_client_secret</literal> key will be set to
-        the contents of the
-        <filename>/run/keys/discourse_github_client_secret</filename>
-        file.
-      </para>
-    </section>
-  </section>
-  <section xml:id="module-services-discourse-plugins">
-    <title>Plugins</title>
-    <para>
-      You can install Discourse plugins using the
-      <xref linkend="opt-services.discourse.plugins" /> option.
-      Pre-packaged plugins are provided in
-      <literal>&lt;your_discourse_package_here&gt;.plugins</literal>. If
-      you want the full suite of plugins provided through
-      <literal>nixpkgs</literal>, you can also set the
-      <xref linkend="opt-services.discourse.package" /> option to
-      <literal>pkgs.discourseAllPlugins</literal>.
-    </para>
-    <para>
-      Plugins can be built with the
-      <literal>&lt;your_discourse_package_here&gt;.mkDiscoursePlugin</literal>
-      function. Normally, it should suffice to provide a
-      <literal>name</literal> and <literal>src</literal> attribute. If
-      the plugin has Ruby dependencies, however, they need to be
-      packaged in accordance with the
-      <link xlink:href="https://nixos.org/manual/nixpkgs/stable/#developing-with-ruby">Developing
-      with Ruby</link> section of the Nixpkgs manual and the appropriate
-      gem options set in <literal>bundlerEnvArgs</literal> (normally
-      <literal>gemdir</literal> is sufficient). A plugin’s Ruby
-      dependencies are listed in its <filename>plugin.rb</filename> file
-      as function calls to <literal>gem</literal>. To construct the
-      corresponding <filename>Gemfile</filename> manually, run
-      <command>bundle init</command>, then add the
-      <literal>gem</literal> lines to it verbatim.
-    </para>
-    <para>
-      Much of the packaging can be done automatically by the
-      <filename>nixpkgs/pkgs/servers/web-apps/discourse/update.py</filename>
-      script - just add the plugin to the <literal>plugins</literal>
-      list in the <literal>update_plugins</literal> function and run the
-      script:
-    </para>
-    <programlisting language="bash">
-./update.py update-plugins
-</programlisting>
-    <para>
-      Some plugins provide
-      <link linkend="module-services-discourse-site-settings">site
-      settings</link>. Their defaults can be configured using
-      <xref linkend="opt-services.discourse.siteSettings" />, just like
-      regular site settings. To find the names of these settings, look
-      in the <literal>config/settings.yml</literal> file of the plugin
-      repo.
-    </para>
-    <para>
-      For example, to add the
-      <link xlink:href="https://github.com/discourse/discourse-spoiler-alert">discourse-spoiler-alert</link>
-      and
-      <link xlink:href="https://github.com/discourse/discourse-solved">discourse-solved</link>
-      plugins, and disable <literal>discourse-spoiler-alert</literal> by
-      default:
-    </para>
-    <programlisting>
-services.discourse = {
-  enable = true;
-  hostname = &quot;discourse.example.com&quot;;
-  sslCertificate = &quot;/path/to/ssl_certificate&quot;;
-  sslCertificateKey = &quot;/path/to/ssl_certificate_key&quot;;
-  admin = {
-    email = &quot;admin@example.com&quot;;
-    username = &quot;admin&quot;;
-    fullName = &quot;Administrator&quot;;
-    passwordFile = &quot;/path/to/password_file&quot;;
-  };
-  mail.outgoing = {
-    serverAddress = &quot;smtp.emailprovider.com&quot;;
-    port = 587;
-    username = &quot;user@emailprovider.com&quot;;
-    passwordFile = &quot;/path/to/smtp_password_file&quot;;
-  };
-  mail.incoming.enable = true;
-  plugins = with config.services.discourse.package.plugins; [
-    discourse-spoiler-alert
-    discourse-solved
-  ];
-  siteSettings = {
-    plugins = {
-      spoiler_enabled = false;
-    };
-  };
-  secretKeyBaseFile = &quot;/path/to/secret_key_base_file&quot;;
-};
-</programlisting>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/web-apps/dokuwiki.nix b/nixos/modules/services/web-apps/dokuwiki.nix
index 54cd5fd33bef7..c85109b1479ed 100644
--- a/nixos/modules/services/web-apps/dokuwiki.nix
+++ b/nixos/modules/services/web-apps/dokuwiki.nix
@@ -82,7 +82,7 @@ let
     basePackage = cfg.package;
     localConfig = dokuwikiLocalConfig hostName cfg;
     pluginsConfig = dokuwikiPluginsLocalConfig hostName cfg;
-    aclConfig = if cfg.aclUse && cfg.acl != null then dokuwikiAclAuthConfig hostName cfg else null;
+    aclConfig = if cfg.settings.useacl && cfg.acl != null then dokuwikiAclAuthConfig hostName cfg else null;
   };
 
   aclOpts = { ... }: {
@@ -148,7 +148,7 @@ let
       ];
 
       options = {
-        enable = mkEnableOption (lib.mdDoc "DokuWiki web application.");
+        enable = mkEnableOption (lib.mdDoc "DokuWiki web application");
 
         package = mkOption {
           type = types.package;
diff --git a/nixos/modules/services/web-apps/galene.nix b/nixos/modules/services/web-apps/galene.nix
index 15ef09aa0b879..747b85f94c65a 100644
--- a/nixos/modules/services/web-apps/galene.nix
+++ b/nixos/modules/services/web-apps/galene.nix
@@ -12,7 +12,7 @@ in
 {
   options = {
     services.galene = {
-      enable = mkEnableOption (lib.mdDoc "Galene Service.");
+      enable = mkEnableOption (lib.mdDoc "Galene Service");
 
       stateDir = mkOption {
         default = defaultstateDir;
diff --git a/nixos/modules/services/web-apps/grocy.nix b/nixos/modules/services/web-apps/grocy.nix
index 6efc2ccfd3020..3bcda3caedace 100644
--- a/nixos/modules/services/web-apps/grocy.nix
+++ b/nixos/modules/services/web-apps/grocy.nix
@@ -167,6 +167,6 @@ in {
 
   meta = {
     maintainers = with maintainers; [ ma27 ];
-    doc = ./grocy.xml;
+    doc = ./grocy.md;
   };
 }
diff --git a/nixos/modules/services/web-apps/grocy.xml b/nixos/modules/services/web-apps/grocy.xml
deleted file mode 100644
index 08de25b4ce2b8..0000000000000
--- a/nixos/modules/services/web-apps/grocy.xml
+++ /dev/null
@@ -1,84 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-grocy">
-  <title>Grocy</title>
-  <para>
-    <link xlink:href="https://grocy.info/">Grocy</link> is a web-based
-    self-hosted groceries &amp; household management solution for your
-    home.
-  </para>
-  <section xml:id="module-services-grocy-basic-usage">
-    <title>Basic usage</title>
-    <para>
-      A very basic configuration may look like this:
-    </para>
-    <programlisting>
-{ pkgs, ... }:
-{
-  services.grocy = {
-    enable = true;
-    hostName = &quot;grocy.tld&quot;;
-  };
-}
-</programlisting>
-    <para>
-      This configures a simple vhost using
-      <link linkend="opt-services.nginx.enable">nginx</link> which
-      listens to <literal>grocy.tld</literal> with fully configured
-      ACME/LE (this can be disabled by setting
-      <link linkend="opt-services.grocy.nginx.enableSSL">services.grocy.nginx.enableSSL</link>
-      to <literal>false</literal>). After the initial setup the
-      credentials <literal>admin:admin</literal> can be used to login.
-    </para>
-    <para>
-      The application’s state is persisted at
-      <literal>/var/lib/grocy/grocy.db</literal> in a
-      <literal>sqlite3</literal> database. The migration is applied when
-      requesting the <literal>/</literal>-route of the application.
-    </para>
-  </section>
-  <section xml:id="module-services-grocy-settings">
-    <title>Settings</title>
-    <para>
-      The configuration for <literal>grocy</literal> is located at
-      <literal>/etc/grocy/config.php</literal>. By default, the
-      following settings can be defined in the NixOS-configuration:
-    </para>
-    <programlisting>
-{ pkgs, ... }:
-{
-  services.grocy.settings = {
-    # The default currency in the system for invoices etc.
-    # Please note that exchange rates aren't taken into account, this
-    # is just the setting for what's shown in the frontend.
-    currency = &quot;EUR&quot;;
-
-    # The display language (and locale configuration) for grocy.
-    culture = &quot;de&quot;;
-
-    calendar = {
-      # Whether or not to show the week-numbers
-      # in the calendar.
-      showWeekNumber = true;
-
-      # Index of the first day to be shown in the calendar (0=Sunday, 1=Monday,
-      # 2=Tuesday and so on).
-      firstDayOfWeek = 2;
-    };
-  };
-}
-</programlisting>
-    <para>
-      If you want to alter the configuration file on your own, you can
-      do this manually with an expression like this:
-    </para>
-    <programlisting>
-{ lib, ... }:
-{
-  environment.etc.&quot;grocy/config.php&quot;.text = lib.mkAfter ''
-    // Arbitrary PHP code in grocy's configuration file
-  '';
-}
-</programlisting>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/web-apps/hledger-web.nix b/nixos/modules/services/web-apps/hledger-web.nix
index 86716a02649c5..0fc283ff52191 100644
--- a/nixos/modules/services/web-apps/hledger-web.nix
+++ b/nixos/modules/services/web-apps/hledger-web.nix
@@ -7,7 +7,7 @@ in {
 
     enable = mkEnableOption (lib.mdDoc "hledger-web service");
 
-    serveApi = mkEnableOption (lib.mdDoc "Serve only the JSON web API, without the web UI.");
+    serveApi = mkEnableOption (lib.mdDoc "Serve only the JSON web API, without the web UI");
 
     host = mkOption {
       type = types.str;
diff --git a/nixos/modules/services/web-apps/jirafeau.nix b/nixos/modules/services/web-apps/jirafeau.nix
index 293cbb3af4252..b2e2741671641 100644
--- a/nixos/modules/services/web-apps/jirafeau.nix
+++ b/nixos/modules/services/web-apps/jirafeau.nix
@@ -36,7 +36,7 @@ in
       description = lib.mdDoc "Location of Jirafeau storage directory.";
     };
 
-    enable = mkEnableOption (lib.mdDoc "Jirafeau file upload application.");
+    enable = mkEnableOption (lib.mdDoc "Jirafeau file upload application");
 
     extraConfig = mkOption {
       type = types.lines;
diff --git a/nixos/modules/services/web-apps/jitsi-meet.nix b/nixos/modules/services/web-apps/jitsi-meet.nix
index 5b0934b2fb76f..28be3a3702eb6 100644
--- a/nixos/modules/services/web-apps/jitsi-meet.nix
+++ b/nixos/modules/services/web-apps/jitsi-meet.nix
@@ -451,6 +451,6 @@ in
     };
   };
 
-  meta.doc = ./jitsi-meet.xml;
+  meta.doc = ./jitsi-meet.md;
   meta.maintainers = lib.teams.jitsi.members;
 }
diff --git a/nixos/modules/services/web-apps/jitsi-meet.xml b/nixos/modules/services/web-apps/jitsi-meet.xml
deleted file mode 100644
index 4d2d8aa55e19f..0000000000000
--- a/nixos/modules/services/web-apps/jitsi-meet.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-jitsi-meet">
-  <title>Jitsi Meet</title>
-  <para>
-    With Jitsi Meet on NixOS you can quickly configure a complete,
-    private, self-hosted video conferencing solution.
-  </para>
-  <section xml:id="module-services-jitsi-basic-usage">
-    <title>Basic usage</title>
-    <para>
-      A minimal configuration using Let’s Encrypt for TLS certificates
-      looks like this:
-    </para>
-    <programlisting>
-{
-  services.jitsi-meet = {
-    enable = true;
-    hostName = &quot;jitsi.example.com&quot;;
-  };
-  services.jitsi-videobridge.openFirewall = true;
-  networking.firewall.allowedTCPPorts = [ 80 443 ];
-  security.acme.email = &quot;me@example.com&quot;;
-  security.acme.acceptTerms = true;
-}
-</programlisting>
-  </section>
-  <section xml:id="module-services-jitsi-configuration">
-    <title>Configuration</title>
-    <para>
-      Here is the minimal configuration with additional configurations:
-    </para>
-    <programlisting>
-{
-  services.jitsi-meet = {
-    enable = true;
-    hostName = &quot;jitsi.example.com&quot;;
-    config = {
-      enableWelcomePage = false;
-      prejoinPageEnabled = true;
-      defaultLang = &quot;fi&quot;;
-    };
-    interfaceConfig = {
-      SHOW_JITSI_WATERMARK = false;
-      SHOW_WATERMARK_FOR_GUESTS = false;
-    };
-  };
-  services.jitsi-videobridge.openFirewall = true;
-  networking.firewall.allowedTCPPorts = [ 80 443 ];
-  security.acme.email = &quot;me@example.com&quot;;
-  security.acme.acceptTerms = true;
-}
-</programlisting>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/web-apps/keycloak.nix b/nixos/modules/services/web-apps/keycloak.nix
index d52190a28648e..a7e4fab8ea287 100644
--- a/nixos/modules/services/web-apps/keycloak.nix
+++ b/nixos/modules/services/web-apps/keycloak.nix
@@ -674,6 +674,6 @@ in
           mkIf createLocalMySQL (mkDefault dbPkg);
       };
 
-  meta.doc = ./keycloak.xml;
+  meta.doc = ./keycloak.md;
   meta.maintainers = [ maintainers.talyz ];
 }
diff --git a/nixos/modules/services/web-apps/keycloak.xml b/nixos/modules/services/web-apps/keycloak.xml
deleted file mode 100644
index 148782d30f39e..0000000000000
--- a/nixos/modules/services/web-apps/keycloak.xml
+++ /dev/null
@@ -1,177 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-keycloak">
-  <title>Keycloak</title>
-  <para>
-    <link xlink:href="https://www.keycloak.org/">Keycloak</link> is an
-    open source identity and access management server with support for
-    <link xlink:href="https://openid.net/connect/">OpenID
-    Connect</link>, <link xlink:href="https://oauth.net/2/">OAUTH
-    2.0</link> and
-    <link xlink:href="https://en.wikipedia.org/wiki/SAML_2.0">SAML
-    2.0</link>.
-  </para>
-  <section xml:id="module-services-keycloak-admin">
-    <title>Administration</title>
-    <para>
-      An administrative user with the username <literal>admin</literal>
-      is automatically created in the <literal>master</literal> realm.
-      Its initial password can be configured by setting
-      <xref linkend="opt-services.keycloak.initialAdminPassword" /> and
-      defaults to <literal>changeme</literal>. The password is not
-      stored safely and should be changed immediately in the admin
-      panel.
-    </para>
-    <para>
-      Refer to the
-      <link xlink:href="https://www.keycloak.org/docs/latest/server_admin/index.html">Keycloak
-      Server Administration Guide</link> for information on how to
-      administer your Keycloak instance.
-    </para>
-  </section>
-  <section xml:id="module-services-keycloak-database">
-    <title>Database access</title>
-    <para>
-      Keycloak can be used with either PostgreSQL, MariaDB or MySQL.
-      Which one is used can be configured in
-      <xref linkend="opt-services.keycloak.database.type" />. The
-      selected database will automatically be enabled and a database and
-      role created unless
-      <xref linkend="opt-services.keycloak.database.host" /> is changed
-      from its default of <literal>localhost</literal> or
-      <xref linkend="opt-services.keycloak.database.createLocally" /> is
-      set to <literal>false</literal>.
-    </para>
-    <para>
-      External database access can also be configured by setting
-      <xref linkend="opt-services.keycloak.database.host" />,
-      <xref linkend="opt-services.keycloak.database.name" />,
-      <xref linkend="opt-services.keycloak.database.username" />,
-      <xref linkend="opt-services.keycloak.database.useSSL" /> and
-      <xref linkend="opt-services.keycloak.database.caCert" /> as
-      appropriate. Note that you need to manually create the database
-      and allow the configured database user full access to it.
-    </para>
-    <para>
-      <xref linkend="opt-services.keycloak.database.passwordFile" />
-      must be set to the path to a file containing the password used to
-      log in to the database. If
-      <xref linkend="opt-services.keycloak.database.host" /> and
-      <xref linkend="opt-services.keycloak.database.createLocally" />
-      are kept at their defaults, the database role
-      <literal>keycloak</literal> with that password is provisioned on
-      the local database instance.
-    </para>
-    <warning>
-      <para>
-        The path should be provided as a string, not a Nix path, since
-        Nix paths are copied into the world readable Nix store.
-      </para>
-    </warning>
-  </section>
-  <section xml:id="module-services-keycloak-hostname">
-    <title>Hostname</title>
-    <para>
-      The hostname is used to build the public URL used as base for all
-      frontend requests and must be configured through
-      <xref linkend="opt-services.keycloak.settings.hostname" />.
-    </para>
-    <note>
-      <para>
-        If you’re migrating an old Wildfly based Keycloak instance and
-        want to keep compatibility with your current clients, you’ll
-        likely want to set
-        <xref linkend="opt-services.keycloak.settings.http-relative-path" />
-        to <literal>/auth</literal>. See the option description for more
-        details.
-      </para>
-    </note>
-    <para>
-      <xref linkend="opt-services.keycloak.settings.hostname-strict-backchannel" />
-      determines whether Keycloak should force all requests to go
-      through the frontend URL. By default, Keycloak allows backend
-      requests to instead use its local hostname or IP address and may
-      also advertise it to clients through its OpenID Connect Discovery
-      endpoint.
-    </para>
-    <para>
-      For more information on hostname configuration, see the
-      <link xlink:href="https://www.keycloak.org/server/hostname">Hostname
-      section of the Keycloak Server Installation and Configuration
-      Guide</link>.
-    </para>
-  </section>
-  <section xml:id="module-services-keycloak-tls">
-    <title>Setting up TLS/SSL</title>
-    <para>
-      By default, Keycloak won’t accept unsecured HTTP connections
-      originating from outside its local network.
-    </para>
-    <para>
-      HTTPS support requires a TLS/SSL certificate and a private key,
-      both
-      <link xlink:href="https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail">PEM
-      formatted</link>. Their paths should be set through
-      <xref linkend="opt-services.keycloak.sslCertificate" /> and
-      <xref linkend="opt-services.keycloak.sslCertificateKey" />.
-    </para>
-    <warning>
-      <para>
-        The paths should be provided as a strings, not a Nix paths,
-        since Nix paths are copied into the world readable Nix store.
-      </para>
-    </warning>
-  </section>
-  <section xml:id="module-services-keycloak-themes">
-    <title>Themes</title>
-    <para>
-      You can package custom themes and make them visible to Keycloak
-      through <xref linkend="opt-services.keycloak.themes" />. See the
-      <link xlink:href="https://www.keycloak.org/docs/latest/server_development/#_themes">Themes
-      section of the Keycloak Server Development Guide</link> and the
-      description of the aforementioned NixOS option for more
-      information.
-    </para>
-  </section>
-  <section xml:id="module-services-keycloak-settings">
-    <title>Configuration file settings</title>
-    <para>
-      Keycloak server configuration parameters can be set in
-      <xref linkend="opt-services.keycloak.settings" />. These
-      correspond directly to options in
-      <filename>conf/keycloak.conf</filename>. Some of the most
-      important parameters are documented as suboptions, the rest can be
-      found in the
-      <link xlink:href="https://www.keycloak.org/server/all-config">All
-      configuration section of the Keycloak Server Installation and
-      Configuration Guide</link>.
-    </para>
-    <para>
-      Options containing secret data should be set to an attribute set
-      containing the attribute <literal>_secret</literal> - a string
-      pointing to a file containing the value the option should be set
-      to. See the description of
-      <xref linkend="opt-services.keycloak.settings" /> for an example.
-    </para>
-  </section>
-  <section xml:id="module-services-keycloak-example-config">
-    <title>Example configuration</title>
-    <para>
-      A basic configuration with some custom settings could look like
-      this:
-    </para>
-    <programlisting>
-services.keycloak = {
-  enable = true;
-  settings = {
-    hostname = &quot;keycloak.example.com&quot;;
-    hostname-strict-backchannel = true;
-  };
-  initialAdminPassword = &quot;e6Wcm0RrtegMEHl&quot;;  # change on first login
-  sslCertificate = &quot;/run/keys/ssl_cert&quot;;
-  sslCertificateKey = &quot;/run/keys/ssl_key&quot;;
-  database.passwordFile = &quot;/run/keys/db_password&quot;;
-};
-</programlisting>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/web-apps/lemmy.nix b/nixos/modules/services/web-apps/lemmy.nix
index f2eb6e726b903..af0fb38121a34 100644
--- a/nixos/modules/services/web-apps/lemmy.nix
+++ b/nixos/modules/services/web-apps/lemmy.nix
@@ -6,7 +6,7 @@ let
 in
 {
   meta.maintainers = with maintainers; [ happysalada ];
-  meta.doc = ./lemmy.xml;
+  meta.doc = ./lemmy.md;
 
   imports = [
     (mkRemovedOptionModule [ "services" "lemmy" "jwtSecretPath" ] "As of v0.13.0, Lemmy auto-generates the JWT secret.")
diff --git a/nixos/modules/services/web-apps/lemmy.xml b/nixos/modules/services/web-apps/lemmy.xml
deleted file mode 100644
index 114e11f3488ad..0000000000000
--- a/nixos/modules/services/web-apps/lemmy.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-lemmy">
-  <title>Lemmy</title>
-  <para>
-    Lemmy is a federated alternative to reddit in rust.
-  </para>
-  <section xml:id="module-services-lemmy-quickstart">
-    <title>Quickstart</title>
-    <para>
-      the minimum to start lemmy is
-    </para>
-    <programlisting language="nix">
-services.lemmy = {
-  enable = true;
-  settings = {
-    hostname = &quot;lemmy.union.rocks&quot;;
-    database.createLocally = true;
-  };
-  caddy.enable = true;
-}
-</programlisting>
-    <para>
-      this will start the backend on port 8536 and the frontend on port
-      1234. It will expose your instance with a caddy reverse proxy to
-      the hostname you’ve provided. Postgres will be initialized on that
-      same instance automatically.
-    </para>
-  </section>
-  <section xml:id="module-services-lemmy-usage">
-    <title>Usage</title>
-    <para>
-      On first connection you will be asked to define an admin user.
-    </para>
-  </section>
-  <section xml:id="module-services-lemmy-missing">
-    <title>Missing</title>
-    <itemizedlist spacing="compact">
-      <listitem>
-        <para>
-          Exposing with nginx is not implemented yet.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          This has been tested using a local database with a unix socket
-          connection. Using different database settings will likely
-          require modifications
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/web-apps/limesurvey.nix b/nixos/modules/services/web-apps/limesurvey.nix
index 7093d1de0dacf..dd51174c8b8e0 100644
--- a/nixos/modules/services/web-apps/limesurvey.nix
+++ b/nixos/modules/services/web-apps/limesurvey.nix
@@ -32,7 +32,7 @@ in
   # interface
 
   options.services.limesurvey = {
-    enable = mkEnableOption (lib.mdDoc "Limesurvey web application.");
+    enable = mkEnableOption (lib.mdDoc "Limesurvey web application");
 
     database = {
       type = mkOption {
diff --git a/nixos/modules/services/web-apps/matomo.nix b/nixos/modules/services/web-apps/matomo.nix
index 9845106599527..eadf8b62b9779 100644
--- a/nixos/modules/services/web-apps/matomo.nix
+++ b/nixos/modules/services/web-apps/matomo.nix
@@ -325,7 +325,7 @@ in {
   };
 
   meta = {
-    doc = ./matomo.xml;
+    doc = ./matomo.md;
     maintainers = with lib.maintainers; [ florianjacob ];
   };
 }
diff --git a/nixos/modules/services/web-apps/matomo.xml b/nixos/modules/services/web-apps/matomo.xml
deleted file mode 100644
index 30994cc9f1dad..0000000000000
--- a/nixos/modules/services/web-apps/matomo.xml
+++ /dev/null
@@ -1,107 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-matomo">
-  <title>Matomo</title>
-  <para>
-    Matomo is a real-time web analytics application. This module
-    configures php-fpm as backend for Matomo, optionally configuring an
-    nginx vhost as well.
-  </para>
-  <para>
-    An automatic setup is not suported by Matomo, so you need to
-    configure Matomo itself in the browser-based Matomo setup.
-  </para>
-  <section xml:id="module-services-matomo-database-setup">
-    <title>Database Setup</title>
-    <para>
-      You also need to configure a MariaDB or MySQL database and -user
-      for Matomo yourself, and enter those credentials in your browser.
-      You can use passwordless database authentication via the
-      UNIX_SOCKET authentication plugin with the following SQL commands:
-    </para>
-    <programlisting>
-# For MariaDB
-INSTALL PLUGIN unix_socket SONAME 'auth_socket';
-CREATE DATABASE matomo;
-CREATE USER 'matomo'@'localhost' IDENTIFIED WITH unix_socket;
-GRANT ALL PRIVILEGES ON matomo.* TO 'matomo'@'localhost';
-
-# For MySQL
-INSTALL PLUGIN auth_socket SONAME 'auth_socket.so';
-CREATE DATABASE matomo;
-CREATE USER 'matomo'@'localhost' IDENTIFIED WITH auth_socket;
-GRANT ALL PRIVILEGES ON matomo.* TO 'matomo'@'localhost';
-</programlisting>
-    <para>
-      Then fill in <literal>matomo</literal> as database user and
-      database name, and leave the password field blank. This
-      authentication works by allowing only the
-      <literal>matomo</literal> unix user to authenticate as the
-      <literal>matomo</literal> database user (without needing a
-      password), but no other users. For more information on
-      passwordless login, see
-      <link xlink:href="https://mariadb.com/kb/en/mariadb/unix_socket-authentication-plugin/">https://mariadb.com/kb/en/mariadb/unix_socket-authentication-plugin/</link>.
-    </para>
-    <para>
-      Of course, you can use password based authentication as well, e.g.
-      when the database is not on the same host.
-    </para>
-  </section>
-  <section xml:id="module-services-matomo-archive-processing">
-    <title>Archive Processing</title>
-    <para>
-      This module comes with the systemd service
-      <literal>matomo-archive-processing.service</literal> and a timer
-      that automatically triggers archive processing every hour. This
-      means that you can safely
-      <link xlink:href="https://matomo.org/docs/setup-auto-archiving/#disable-browser-triggers-for-matomo-archiving-and-limit-matomo-reports-to-updating-every-hour">disable
-      browser triggers for Matomo archiving</link> at
-      <literal>Administration &gt; System &gt; General Settings</literal>.
-    </para>
-    <para>
-      With automatic archive processing, you can now also enable to
-      <link xlink:href="https://matomo.org/docs/privacy/#step-2-delete-old-visitors-logs">delete
-      old visitor logs</link> at
-      <literal>Administration &gt; System &gt; Privacy</literal>, but
-      make sure that you run
-      <literal>systemctl start matomo-archive-processing.service</literal>
-      at least once without errors if you have already collected data
-      before, so that the reports get archived before the source data
-      gets deleted.
-    </para>
-  </section>
-  <section xml:id="module-services-matomo-backups">
-    <title>Backup</title>
-    <para>
-      You only need to take backups of your MySQL database and the
-      <filename>/var/lib/matomo/config/config.ini.php</filename> file.
-      Use a user in the <literal>matomo</literal> group or root to
-      access the file. For more information, see
-      <link xlink:href="https://matomo.org/faq/how-to-install/faq_138/">https://matomo.org/faq/how-to-install/faq_138/</link>.
-    </para>
-  </section>
-  <section xml:id="module-services-matomo-issues">
-    <title>Issues</title>
-    <itemizedlist spacing="compact">
-      <listitem>
-        <para>
-          Matomo will warn you that the JavaScript tracker is not
-          writable. This is because it’s located in the read-only nix
-          store. You can safely ignore this, unless you need a plugin
-          that needs JavaScript tracker access.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="module-services-matomo-other-web-servers">
-    <title>Using other Web Servers than nginx</title>
-    <para>
-      You can use other web servers by forwarding calls for
-      <filename>index.php</filename> and <filename>piwik.php</filename>
-      to the
-      <link linkend="opt-services.phpfpm.pools._name_.socket"><literal>services.phpfpm.pools.&lt;name&gt;.socket</literal></link>
-      fastcgi unix socket. You can use the nginx configuration in the
-      module code as a reference to what else should be configured.
-    </para>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/web-apps/nextcloud-notify_push.nix b/nixos/modules/services/web-apps/nextcloud-notify_push.nix
new file mode 100644
index 0000000000000..e36631b6093c5
--- /dev/null
+++ b/nixos/modules/services/web-apps/nextcloud-notify_push.nix
@@ -0,0 +1,96 @@
+{ config, options, lib, pkgs, ... }:
+
+let
+  cfg = config.services.nextcloud.notify_push;
+in
+{
+  options.services.nextcloud.notify_push = {
+    enable = lib.mkEnableOption (lib.mdDoc "Notify push");
+
+    package = lib.mkOption {
+      type = lib.types.package;
+      default = pkgs.nextcloud-notify_push;
+      defaultText = lib.literalMD "pkgs.nextcloud-notify_push";
+      description = lib.mdDoc "Which package to use for notify_push";
+    };
+
+    socketPath = lib.mkOption {
+      type = lib.types.str;
+      default = "/run/nextcloud-notify_push/sock";
+      description = lib.mdDoc "Socket path to use for notify_push";
+    };
+
+    logLevel = lib.mkOption {
+      type = lib.types.enum [ "error" "warn" "info" "debug" "trace" ];
+      default = "error";
+      description = lib.mdDoc "Log level";
+    };
+  } // (
+    lib.genAttrs [
+      "dbtype"
+      "dbname"
+      "dbuser"
+      "dbpassFile"
+      "dbhost"
+      "dbport"
+      "dbtableprefix"
+    ] (
+      opt: options.services.nextcloud.config.${opt} // {
+        default = config.services.nextcloud.config.${opt};
+        defaultText = "config.services.nextcloud.config.${opt}";
+      }
+    )
+  );
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.nextcloud-notify_push = let
+      nextcloudUrl = "http${lib.optionalString config.services.nextcloud.https "s"}://${config.services.nextcloud.hostName}";
+    in {
+      description = "Push daemon for Nextcloud clients";
+      documentation = [ "https://github.com/nextcloud/notify_push" ];
+      after = [ "phpfpm-nextcloud.service" ];
+      wantedBy = [ "multi-user.target" ];
+      environment = {
+        NEXTCLOUD_URL = nextcloudUrl;
+        SOCKET_PATH = cfg.socketPath;
+        DATABASE_PREFIX = cfg.dbtableprefix;
+        LOG = cfg.logLevel;
+      };
+      postStart = ''
+        ${config.services.nextcloud.occ}/bin/nextcloud-occ notify_push:setup ${nextcloudUrl}/push
+      '';
+      script = let
+        dbType = if cfg.dbtype == "pgsql" then "postgresql" else cfg.dbtype;
+        dbUser = lib.optionalString (cfg.dbuser != null) cfg.dbuser;
+        dbPass = lib.optionalString (cfg.dbpassFile != null) ":$DATABASE_PASSWORD";
+        isSocket = lib.hasPrefix "/" (toString cfg.dbhost);
+        dbHost = lib.optionalString (cfg.dbhost != null) (if
+          isSocket then
+            if dbType == "postgresql" then "?host=${cfg.dbhost}" else
+            if dbType == "mysql" then "?socket=${cfg.dbhost}" else throw "unsupported dbtype"
+          else
+            "@${cfg.dbhost}");
+        dbName = lib.optionalString (cfg.dbname != null) "/${cfg.dbname}";
+        dbUrl = "${dbType}://${dbUser}${dbPass}${lib.optionalString (!isSocket) dbHost}${dbName}${lib.optionalString isSocket dbHost}";
+      in lib.optionalString (dbPass != "") ''
+        export DATABASE_PASSWORD="$(<"${cfg.dbpassFile}")"
+      '' + ''
+        export DATABASE_URL="${dbUrl}"
+        ${cfg.package}/bin/notify_push --glob-config '${config.services.nextcloud.datadir}/config/config.php'
+      '';
+      serviceConfig = {
+        User = "nextcloud";
+        Group = "nextcloud";
+        RuntimeDirectory = [ "nextcloud-notify_push" ];
+        Restart = "on-failure";
+        RestartSec = "5s";
+      };
+    };
+
+    services.nginx.virtualHosts.${config.services.nextcloud.hostName}.locations."^~ /push/" = {
+      proxyPass = "http://unix:${cfg.socketPath}";
+      proxyWebsockets = true;
+      recommendedProxySettings = true;
+    };
+  };
+}
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
index 90801e9968175..c5e161c2516ad 100644
--- a/nixos/modules/services/web-apps/nextcloud.nix
+++ b/nixos/modules/services/web-apps/nextcloud.nix
@@ -79,7 +79,7 @@ in {
       (which can be opened e.g. by running `nixos-help`).
     '')
     (mkRemovedOptionModule [ "services" "nextcloud" "disableImagemagick" ] ''
-      Use services.nextcloud.nginx.enableImagemagick instead.
+      Use services.nextcloud.enableImagemagick instead.
     '')
   ];
 
@@ -1146,5 +1146,5 @@ in {
     }
   ]);
 
-  meta.doc = ./nextcloud.xml;
+  meta.doc = ./nextcloud.md;
 }
diff --git a/nixos/modules/services/web-apps/nextcloud.xml b/nixos/modules/services/web-apps/nextcloud.xml
deleted file mode 100644
index a5ac05723ef47..0000000000000
--- a/nixos/modules/services/web-apps/nextcloud.xml
+++ /dev/null
@@ -1,333 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-nextcloud">
-  <title>Nextcloud</title>
-  <para>
-    <link xlink:href="https://nextcloud.com/">Nextcloud</link> is an
-    open-source, self-hostable cloud platform. The server setup can be
-    automated using
-    <link linkend="opt-services.nextcloud.enable">services.nextcloud</link>.
-    A desktop client is packaged at
-    <literal>pkgs.nextcloud-client</literal>.
-  </para>
-  <para>
-    The current default by NixOS is <literal>nextcloud25</literal> which
-    is also the latest major version available.
-  </para>
-  <section xml:id="module-services-nextcloud-basic-usage">
-    <title>Basic usage</title>
-    <para>
-      Nextcloud is a PHP-based application which requires an HTTP server
-      (<link linkend="opt-services.nextcloud.enable"><literal>services.nextcloud</literal></link>
-      optionally supports
-      <link linkend="opt-services.nginx.enable"><literal>services.nginx</literal></link>)
-      and a database (it’s recommended to use
-      <link linkend="opt-services.postgresql.enable"><literal>services.postgresql</literal></link>).
-    </para>
-    <para>
-      A very basic configuration may look like this:
-    </para>
-    <programlisting>
-{ pkgs, ... }:
-{
-  services.nextcloud = {
-    enable = true;
-    hostName = &quot;nextcloud.tld&quot;;
-    config = {
-      dbtype = &quot;pgsql&quot;;
-      dbuser = &quot;nextcloud&quot;;
-      dbhost = &quot;/run/postgresql&quot;; # nextcloud will add /.s.PGSQL.5432 by itself
-      dbname = &quot;nextcloud&quot;;
-      adminpassFile = &quot;/path/to/admin-pass-file&quot;;
-      adminuser = &quot;root&quot;;
-    };
-  };
-
-  services.postgresql = {
-    enable = true;
-    ensureDatabases = [ &quot;nextcloud&quot; ];
-    ensureUsers = [
-     { name = &quot;nextcloud&quot;;
-       ensurePermissions.&quot;DATABASE nextcloud&quot; = &quot;ALL PRIVILEGES&quot;;
-     }
-    ];
-  };
-
-  # ensure that postgres is running *before* running the setup
-  systemd.services.&quot;nextcloud-setup&quot; = {
-    requires = [&quot;postgresql.service&quot;];
-    after = [&quot;postgresql.service&quot;];
-  };
-
-  networking.firewall.allowedTCPPorts = [ 80 443 ];
-}
-</programlisting>
-    <para>
-      The <literal>hostName</literal> option is used internally to
-      configure an HTTP server using
-      <link xlink:href="https://php-fpm.org/"><literal>PHP-FPM</literal></link>
-      and <literal>nginx</literal>. The <literal>config</literal>
-      attribute set is used by the imperative installer and all values
-      are written to an additional file to ensure that changes can be
-      applied by changing the module’s options.
-    </para>
-    <para>
-      In case the application serves multiple domains (those are checked
-      with
-      <link xlink:href="http://php.net/manual/en/reserved.variables.server.php"><literal>$_SERVER['HTTP_HOST']</literal></link>)
-      it’s needed to add them to
-      <link linkend="opt-services.nextcloud.config.extraTrustedDomains"><literal>services.nextcloud.config.extraTrustedDomains</literal></link>.
-    </para>
-    <para>
-      Auto updates for Nextcloud apps can be enabled using
-      <link linkend="opt-services.nextcloud.autoUpdateApps.enable"><literal>services.nextcloud.autoUpdateApps</literal></link>.
-    </para>
-  </section>
-  <section xml:id="module-services-nextcloud-pitfalls-during-upgrade">
-    <title>Common problems</title>
-    <itemizedlist>
-      <listitem>
-        <para>
-          <emphasis role="strong">General notes.</emphasis>
-          Unfortunately Nextcloud appears to be very stateful when it
-          comes to managing its own configuration. The config file lives
-          in the home directory of the <literal>nextcloud</literal> user
-          (by default
-          <literal>/var/lib/nextcloud/config/config.php</literal>) and
-          is also used to track several states of the application (e.g.,
-          whether installed or not).
-        </para>
-        <para>
-          All configuration parameters are also stored in
-          <filename>/var/lib/nextcloud/config/override.config.php</filename>
-          which is generated by the module and linked from the store to
-          ensure that all values from <filename>config.php</filename>
-          can be modified by the module. However
-          <filename>config.php</filename> manages the application’s
-          state and shouldn’t be touched manually because of that.
-        </para>
-        <warning>
-          <para>
-            Don’t delete <filename>config.php</filename>! This file
-            tracks the application’s state and a deletion can cause
-            unwanted side-effects!
-          </para>
-        </warning>
-        <warning>
-          <para>
-            Don’t rerun
-            <literal>nextcloud-occ maintenance:install</literal>! This
-            command tries to install the application and can cause
-            unwanted side-effects!
-          </para>
-        </warning>
-      </listitem>
-      <listitem>
-        <para>
-          <emphasis role="strong">Multiple version upgrades.</emphasis>
-          Nextcloud doesn’t allow to move more than one major-version
-          forward. E.g., if you’re on <literal>v16</literal>, you cannot
-          upgrade to <literal>v18</literal>, you need to upgrade to
-          <literal>v17</literal> first. This is ensured automatically as
-          long as the
-          <link linkend="opt-system.stateVersion">stateVersion</link> is
-          declared properly. In that case the oldest version available
-          (one major behind the one from the previous NixOS release)
-          will be selected by default and the module will generate a
-          warning that reminds the user to upgrade to latest Nextcloud
-          <emphasis>after</emphasis> that deploy.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <emphasis role="strong"><literal>Error: Command &quot;upgrade&quot; is not defined.</literal></emphasis>
-          This error usually occurs if the initial installation
-          (<command>nextcloud-occ maintenance:install</command>) has
-          failed. After that, the application is not installed, but the
-          upgrade is attempted to be executed. Further context can be
-          found in
-          <link xlink:href="https://github.com/NixOS/nixpkgs/issues/111175">NixOS/nixpkgs#111175</link>.
-        </para>
-        <para>
-          First of all, it makes sense to find out what went wrong by
-          looking at the logs of the installation via
-          <command>journalctl -u nextcloud-setup</command> and try to
-          fix the underlying issue.
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              If this occurs on an <emphasis>existing</emphasis> setup,
-              this is most likely because the maintenance mode is
-              active. It can be deactivated by running
-              <command>nextcloud-occ maintenance:mode --off</command>.
-              It’s advisable though to check the logs first on why the
-              maintenance mode was activated.
-            </para>
-          </listitem>
-          <listitem>
-            <warning>
-              <para>
-                Only perform the following measures on <emphasis>freshly
-                installed instances!</emphasis>
-              </para>
-            </warning>
-            <para>
-              A re-run of the installer can be forced by
-              <emphasis>deleting</emphasis>
-              <filename>/var/lib/nextcloud/config/config.php</filename>.
-              This is the only time advisable because the fresh install
-              doesn’t have any state that can be lost. In case that
-              doesn’t help, an entire re-creation can be forced via
-              <command>rm -rf ~nextcloud/</command>.
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          <emphasis role="strong">Server-side encryption.</emphasis>
-          Nextcloud supports
-          <link xlink:href="https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/encryption_configuration.html">server-side
-          encryption (SSE)</link>. This is not an end-to-end encryption,
-          but can be used to encrypt files that will be persisted to
-          external storage such as S3. Please note that this won’t work
-          anymore when using OpenSSL 3 for PHP’s openssl extension
-          because this is implemented using the legacy cipher RC4. If
-          <xref linkend="opt-system.stateVersion" /> is
-          <emphasis>above</emphasis> <literal>22.05</literal>, this is
-          disabled by default. To turn it on again and for further
-          information please refer to
-          <xref linkend="opt-services.nextcloud.enableBrokenCiphersForSSE" />.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="module-services-nextcloud-httpd">
-    <title>Using an alternative webserver as reverse-proxy (e.g.
-    <literal>httpd</literal>)</title>
-    <para>
-      By default, <literal>nginx</literal> is used as reverse-proxy for
-      <literal>nextcloud</literal>. However, it’s possible to use e.g.
-      <literal>httpd</literal> by explicitly disabling
-      <literal>nginx</literal> using
-      <xref linkend="opt-services.nginx.enable" /> and fixing the
-      settings <literal>listen.owner</literal> &amp;
-      <literal>listen.group</literal> in the
-      <link linkend="opt-services.phpfpm.pools">corresponding
-      <literal>phpfpm</literal> pool</link>.
-    </para>
-    <para>
-      An exemplary configuration may look like this:
-    </para>
-    <programlisting>
-{ config, lib, pkgs, ... }: {
-  services.nginx.enable = false;
-  services.nextcloud = {
-    enable = true;
-    hostName = &quot;localhost&quot;;
-
-    /* further, required options */
-  };
-  services.phpfpm.pools.nextcloud.settings = {
-    &quot;listen.owner&quot; = config.services.httpd.user;
-    &quot;listen.group&quot; = config.services.httpd.group;
-  };
-  services.httpd = {
-    enable = true;
-    adminAddr = &quot;webmaster@localhost&quot;;
-    extraModules = [ &quot;proxy_fcgi&quot; ];
-    virtualHosts.&quot;localhost&quot; = {
-      documentRoot = config.services.nextcloud.package;
-      extraConfig = ''
-        &lt;Directory &quot;${config.services.nextcloud.package}&quot;&gt;
-          &lt;FilesMatch &quot;\.php$&quot;&gt;
-            &lt;If &quot;-f %{REQUEST_FILENAME}&quot;&gt;
-              SetHandler &quot;proxy:unix:${config.services.phpfpm.pools.nextcloud.socket}|fcgi://localhost/&quot;
-            &lt;/If&gt;
-          &lt;/FilesMatch&gt;
-          &lt;IfModule mod_rewrite.c&gt;
-            RewriteEngine On
-            RewriteBase /
-            RewriteRule ^index\.php$ - [L]
-            RewriteCond %{REQUEST_FILENAME} !-f
-            RewriteCond %{REQUEST_FILENAME} !-d
-            RewriteRule . /index.php [L]
-          &lt;/IfModule&gt;
-          DirectoryIndex index.php
-          Require all granted
-          Options +FollowSymLinks
-        &lt;/Directory&gt;
-      '';
-    };
-  };
-}
-</programlisting>
-  </section>
-  <section xml:id="installing-apps-php-extensions-nextcloud">
-    <title>Installing Apps and PHP extensions</title>
-    <para>
-      Nextcloud apps are installed statefully through the web interface.
-      Some apps may require extra PHP extensions to be installed. This
-      can be configured with the
-      <xref linkend="opt-services.nextcloud.phpExtraExtensions" />
-      setting.
-    </para>
-    <para>
-      Alternatively, extra apps can also be declared with the
-      <xref linkend="opt-services.nextcloud.extraApps" /> setting. When
-      using this setting, apps can no longer be managed statefully
-      because this can lead to Nextcloud updating apps that are managed
-      by Nix. If you want automatic updates it is recommended that you
-      use web interface to install apps.
-    </para>
-  </section>
-  <section xml:id="module-services-nextcloud-maintainer-info">
-    <title>Maintainer information</title>
-    <para>
-      As stated in the previous paragraph, we must provide a clean
-      upgrade-path for Nextcloud since it cannot move more than one
-      major version forward on a single upgrade. This chapter adds some
-      notes how Nextcloud updates should be rolled out in the future.
-    </para>
-    <para>
-      While minor and patch-level updates are no problem and can be done
-      directly in the package-expression (and should be backported to
-      supported stable branches after that), major-releases should be
-      added in a new attribute (e.g. Nextcloud
-      <literal>v19.0.0</literal> should be available in
-      <literal>nixpkgs</literal> as
-      <literal>pkgs.nextcloud19</literal>). To provide simple upgrade
-      paths it’s generally useful to backport those as well to stable
-      branches. As long as the package-default isn’t altered, this won’t
-      break existing setups. After that, the versioning-warning in the
-      <literal>nextcloud</literal>-module should be updated to make sure
-      that the
-      <link linkend="opt-services.nextcloud.package">package</link>-option
-      selects the latest version on fresh setups.
-    </para>
-    <para>
-      If major-releases will be abandoned by upstream, we should check
-      first if those are needed in NixOS for a safe upgrade-path before
-      removing those. In that case we should keep those packages, but
-      mark them as insecure in an expression like this (in
-      <literal>&lt;nixpkgs/pkgs/servers/nextcloud/default.nix&gt;</literal>):
-    </para>
-    <programlisting>
-/* ... */
-{
-  nextcloud17 = generic {
-    version = &quot;17.0.x&quot;;
-    sha256 = &quot;0000000000000000000000000000000000000000000000000000&quot;;
-    eol = true;
-  };
-}
-</programlisting>
-    <para>
-      Ideally we should make sure that it’s possible to jump two NixOS
-      versions forward: i.e. the warnings and the logic in the module
-      should guard a user to upgrade from a Nextcloud on e.g. 19.09 to a
-      Nextcloud on 20.09.
-    </para>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/web-apps/onlyoffice.nix b/nixos/modules/services/web-apps/onlyoffice.nix
index 79ed3e43dd18a..3494f2fa21f09 100644
--- a/nixos/modules/services/web-apps/onlyoffice.nix
+++ b/nixos/modules/services/web-apps/onlyoffice.nix
@@ -229,6 +229,9 @@ in
             cp -r ${cfg.package}/etc/onlyoffice/documentserver/* /run/onlyoffice/config/
             chmod u+w /run/onlyoffice/config/default.json
 
+            # Allow members of the onlyoffice group to serve files under /var/lib/onlyoffice/documentserver/App_Data
+            chmod g+x /var/lib/onlyoffice/documentserver
+
             cp /run/onlyoffice/config/default.json{,.orig}
 
             # for a mapping of environment variables from the docker container to json options see
@@ -267,7 +270,7 @@ in
           wantedBy = [ "multi-user.target" ];
           serviceConfig = {
             ExecStart = "${cfg.package.fhs}/bin/onlyoffice-wrapper DocService/docservice /run/onlyoffice/config";
-            ExecStartPre = onlyoffice-prestart;
+            ExecStartPre = [ onlyoffice-prestart ];
             Group = "onlyoffice";
             Restart = "always";
             RuntimeDirectory = "onlyoffice";
@@ -284,6 +287,8 @@ in
         group = "onlyoffice";
         isSystemUser = true;
       };
+
+      nginx.extraGroups = [ "onlyoffice" ];
     };
 
     users.groups.onlyoffice = { };
diff --git a/nixos/modules/services/web-apps/pict-rs.nix b/nixos/modules/services/web-apps/pict-rs.nix
index ad07507ca37db..0f13b2ae6db13 100644
--- a/nixos/modules/services/web-apps/pict-rs.nix
+++ b/nixos/modules/services/web-apps/pict-rs.nix
@@ -5,7 +5,7 @@ let
 in
 {
   meta.maintainers = with maintainers; [ happysalada ];
-  meta.doc = ./pict-rs.xml;
+  meta.doc = ./pict-rs.md;
 
   options.services.pict-rs = {
     enable = mkEnableOption (lib.mdDoc "pict-rs server");
diff --git a/nixos/modules/services/web-apps/pict-rs.xml b/nixos/modules/services/web-apps/pict-rs.xml
deleted file mode 100644
index 3f5900c55f151..0000000000000
--- a/nixos/modules/services/web-apps/pict-rs.xml
+++ /dev/null
@@ -1,185 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-pict-rs">
-  <title>Pict-rs</title>
-  <para>
-    pict-rs is a a simple image hosting service.
-  </para>
-  <section xml:id="module-services-pict-rs-quickstart">
-    <title>Quickstart</title>
-    <para>
-      the minimum to start pict-rs is
-    </para>
-    <programlisting language="nix">
-services.pict-rs.enable = true;
-</programlisting>
-    <para>
-      this will start the http server on port 8080 by default.
-    </para>
-  </section>
-  <section xml:id="module-services-pict-rs-usage">
-    <title>Usage</title>
-    <para>
-      pict-rs offers the following endpoints:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          <literal>POST /image</literal> for uploading an image.
-          Uploaded content must be valid multipart/form-data with an
-          image array located within the <literal>images[]</literal> key
-        </para>
-        <para>
-          This endpoint returns the following JSON structure on success
-          with a 201 Created status
-        </para>
-        <programlisting language="json">
-{
-    &quot;files&quot;: [
-        {
-            &quot;delete_token&quot;: &quot;JFvFhqJA98&quot;,
-            &quot;file&quot;: &quot;lkWZDRvugm.jpg&quot;
-        },
-        {
-            &quot;delete_token&quot;: &quot;kAYy9nk2WK&quot;,
-            &quot;file&quot;: &quot;8qFS0QooAn.jpg&quot;
-        },
-        {
-            &quot;delete_token&quot;: &quot;OxRpM3sf0Y&quot;,
-            &quot;file&quot;: &quot;1hJaYfGE01.jpg&quot;
-        }
-    ],
-    &quot;msg&quot;: &quot;ok&quot;
-}
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>GET /image/download?url=...</literal> Download an
-          image from a remote server, returning the same JSON payload as
-          the <literal>POST</literal> endpoint
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>GET /image/original/{file}</literal> for getting a
-          full-resolution image. <literal>file</literal> here is the
-          <literal>file</literal> key from the <literal>/image</literal>
-          endpoint’s JSON
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>GET /image/details/original/{file}</literal> for
-          getting the details of a full-resolution image. The returned
-          JSON is structured like so:
-        </para>
-        <programlisting language="json">
-{
-    &quot;width&quot;: 800,
-    &quot;height&quot;: 537,
-    &quot;content_type&quot;: &quot;image/webp&quot;,
-    &quot;created_at&quot;: [
-        2020,
-        345,
-        67376,
-        394363487
-    ]
-}
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>GET /image/process.{ext}?src={file}&amp;...</literal>
-          get a file with transformations applied. existing
-          transformations include
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              <literal>identity=true</literal>: apply no changes
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>blur={float}</literal>: apply a gaussian blur to
-              the file
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>thumbnail={int}</literal>: produce a thumbnail of
-              the image fitting inside an <literal>{int}</literal> by
-              <literal>{int}</literal> square using raw pixel sampling
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>resize={int}</literal>: produce a thumbnail of
-              the image fitting inside an <literal>{int}</literal> by
-              <literal>{int}</literal> square using a Lanczos2 filter.
-              This is slower than sampling but looks a bit better in
-              some cases
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>crop={int-w}x{int-h}</literal>: produce a cropped
-              version of the image with an <literal>{int-w}</literal> by
-              <literal>{int-h}</literal> aspect ratio. The resulting
-              crop will be centered on the image. Either the width or
-              height of the image will remain full-size, depending on
-              the image’s aspect ratio and the requested aspect ratio.
-              For example, a 1600x900 image cropped with a 1x1 aspect
-              ratio will become 900x900. A 1600x1100 image cropped with
-              a 16x9 aspect ratio will become 1600x900.
-            </para>
-          </listitem>
-        </itemizedlist>
-        <para>
-          Supported <literal>ext</literal> file extensions include
-          <literal>png</literal>, <literal>jpg</literal>, and
-          <literal>webp</literal>
-        </para>
-        <para>
-          An example of usage could be
-        </para>
-        <programlisting>
-GET /image/process.jpg?src=asdf.png&amp;thumbnail=256&amp;blur=3.0
-</programlisting>
-        <para>
-          which would create a 256x256px JPEG thumbnail and blur it
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>GET /image/details/process.{ext}?src={file}&amp;...</literal>
-          for getting the details of a processed image. The returned
-          JSON is the same format as listed for the full-resolution
-          details endpoint.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>DELETE /image/delete/{delete_token}/{file}</literal>
-          or <literal>GET /image/delete/{delete_token}/{file}</literal>
-          to delete a file, where <literal>delete_token</literal> and
-          <literal>file</literal> are from the <literal>/image</literal>
-          endpoint’s JSON
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="module-services-pict-rs-missing">
-    <title>Missing</title>
-    <itemizedlist spacing="compact">
-      <listitem>
-        <para>
-          Configuring the secure-api-key is not included yet. The
-          envisioned basic use case is consumption on localhost by other
-          services without exposing the service to the internet.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/web-apps/plausible.nix b/nixos/modules/services/web-apps/plausible.nix
index e5dc1b1036017..f64254d62524e 100644
--- a/nixos/modules/services/web-apps/plausible.nix
+++ b/nixos/modules/services/web-apps/plausible.nix
@@ -292,5 +292,5 @@ in {
   };
 
   meta.maintainers = with maintainers; [ ma27 ];
-  meta.doc = ./plausible.xml;
+  meta.doc = ./plausible.md;
 }
diff --git a/nixos/modules/services/web-apps/plausible.xml b/nixos/modules/services/web-apps/plausible.xml
deleted file mode 100644
index 39ff004ffd95f..0000000000000
--- a/nixos/modules/services/web-apps/plausible.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-plausible">
-  <title>Plausible</title>
-  <para>
-    <link xlink:href="https://plausible.io/">Plausible</link> is a
-    privacy-friendly alternative to Google analytics.
-  </para>
-  <section xml:id="module-services-plausible-basic-usage">
-    <title>Basic Usage</title>
-    <para>
-      At first, a secret key is needed to be generated. This can be done
-      with e.g.
-    </para>
-    <programlisting>
-$ openssl rand -base64 64
-</programlisting>
-    <para>
-      After that, <literal>plausible</literal> can be deployed like
-      this:
-    </para>
-    <programlisting>
-{
-  services.plausible = {
-    enable = true;
-    adminUser = {
-      # activate is used to skip the email verification of the admin-user that's
-      # automatically created by plausible. This is only supported if
-      # postgresql is configured by the module. This is done by default, but
-      # can be turned off with services.plausible.database.postgres.setup.
-      activate = true;
-      email = &quot;admin@localhost&quot;;
-      passwordFile = &quot;/run/secrets/plausible-admin-pwd&quot;;
-    };
-    server = {
-      baseUrl = &quot;http://analytics.example.org&quot;;
-      # secretKeybaseFile is a path to the file which contains the secret generated
-      # with openssl as described above.
-      secretKeybaseFile = &quot;/run/secrets/plausible-secret-key-base&quot;;
-    };
-  };
-}
-</programlisting>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/web-apps/wordpress.nix b/nixos/modules/services/web-apps/wordpress.nix
index 416ad8556bdda..d4c987da1144c 100644
--- a/nixos/modules/services/web-apps/wordpress.nix
+++ b/nixos/modules/services/web-apps/wordpress.nix
@@ -32,8 +32,8 @@ let
       # Since hard linking directories is not allowed, copying is the next best thing.
 
       # copy additional plugin(s), theme(s) and language(s)
-      ${concatMapStringsSep "\n" (theme: "cp -r ${theme} $out/share/wordpress/wp-content/themes/${theme.name}") cfg.themes}
-      ${concatMapStringsSep "\n" (plugin: "cp -r ${plugin} $out/share/wordpress/wp-content/plugins/${plugin.name}") cfg.plugins}
+      ${concatStringsSep "\n" (mapAttrsToList (name: theme: "cp -r ${theme} $out/share/wordpress/wp-content/themes/${name}") cfg.themes)}
+      ${concatStringsSep "\n" (mapAttrsToList (name: plugin: "cp -r ${plugin} $out/share/wordpress/wp-content/plugins/${name}") cfg.plugins)}
       ${concatMapStringsSep "\n" (language: "cp -r ${language} $out/share/wordpress/wp-content/languages/") cfg.languages}
     '';
   };
@@ -130,62 +130,45 @@ let
         };
 
         plugins = mkOption {
-          type = types.listOf types.path;
-          default = [];
+          type = with types; coercedTo
+            (listOf path)
+            (l: warn "setting this option with a list is deprecated"
+              listToAttrs (map (p: nameValuePair (p.name or (throw "${p} does not have a name")) p) l))
+            (attrsOf path);
+          default = {};
           description = lib.mdDoc ''
-            List of path(s) to respective plugin(s) which are copied from the 'plugins' directory.
+            Path(s) to respective plugin(s) which are copied from the 'plugins' directory.
 
             ::: {.note}
             These plugins need to be packaged before use, see example.
             :::
           '';
           example = literalExpression ''
-            let
-              # Wordpress plugin 'embed-pdf-viewer' installation example
-              embedPdfViewerPlugin = pkgs.stdenv.mkDerivation {
-                name = "embed-pdf-viewer-plugin";
-                # Download the theme from the wordpress site
-                src = pkgs.fetchurl {
-                  url = "https://downloads.wordpress.org/plugin/embed-pdf-viewer.2.0.3.zip";
-                  sha256 = "1rhba5h5fjlhy8p05zf0p14c9iagfh96y91r36ni0rmk6y891lyd";
-                };
-                # We need unzip to build this package
-                nativeBuildInputs = [ pkgs.unzip ];
-                # Installing simply means copying all files to the output directory
-                installPhase = "mkdir -p $out; cp -R * $out/";
-              };
-            # And then pass this theme to the themes list like this:
-            in [ embedPdfViewerPlugin ]
+            {
+              inherit (pkgs.wordpressPackages.plugins) embed-pdf-viewer-plugin;
+            }
           '';
         };
 
         themes = mkOption {
-          type = types.listOf types.path;
-          default = [];
+          type = with types; coercedTo
+            (listOf path)
+            (l: warn "setting this option with a list is deprecated"
+              listToAttrs (map (p: nameValuePair (p.name or (throw "${p} does not have a name")) p) l))
+            (attrsOf path);
+          default = { inherit (pkgs.wordpressPackages.themes) twentytwentythree; };
+          defaultText = literalExpression "{ inherit (pkgs.wordpressPackages.themes) twentytwentythree; }";
           description = lib.mdDoc ''
-            List of path(s) to respective theme(s) which are copied from the 'theme' directory.
+            Path(s) to respective theme(s) which are copied from the 'theme' directory.
 
             ::: {.note}
             These themes need to be packaged before use, see example.
             :::
           '';
           example = literalExpression ''
-            let
-              # Let's package the responsive theme
-              responsiveTheme = pkgs.stdenv.mkDerivation {
-                name = "responsive-theme";
-                # Download the theme from the wordpress site
-                src = pkgs.fetchurl {
-                  url = "https://downloads.wordpress.org/theme/responsive.3.14.zip";
-                  sha256 = "0rjwm811f4aa4q43r77zxlpklyb85q08f9c8ns2akcarrvj5ydx3";
-                };
-                # We need unzip to build this package
-                nativeBuildInputs = [ pkgs.unzip ];
-                # Installing simply means copying all files to the output directory
-                installPhase = "mkdir -p $out; cp -R * $out/";
-              };
-            # And then pass this theme to the themes list like this:
-            in [ responsiveTheme ]
+            {
+              inherit (pkgs.wordpressPackages.themes) responsive-theme;
+            }
           '';
         };
 
diff --git a/nixos/modules/services/web-servers/caddy/default.nix b/nixos/modules/services/web-servers/caddy/default.nix
index 50213ec252ff2..f5a9cfac5d77f 100644
--- a/nixos/modules/services/web-servers/caddy/default.nix
+++ b/nixos/modules/services/web-servers/caddy/default.nix
@@ -35,7 +35,8 @@ let
 
       Caddyfile-formatted = pkgs.runCommand "Caddyfile-formatted" { nativeBuildInputs = [ cfg.package ]; } ''
         mkdir -p $out
-        ${cfg.package}/bin/caddy fmt ${Caddyfile}/Caddyfile > $out/Caddyfile
+        cp --no-preserve=mode ${Caddyfile}/Caddyfile $out/Caddyfile
+        caddy fmt --overwrite $out/Caddyfile
       '';
     in
       "${if pkgs.stdenv.buildPlatform == pkgs.stdenv.hostPlatform then Caddyfile-formatted else Caddyfile}/Caddyfile";
diff --git a/nixos/modules/services/web-servers/garage.nix b/nixos/modules/services/web-servers/garage.nix
index 1c25d865f980c..2491c788d6c51 100644
--- a/nixos/modules/services/web-servers/garage.nix
+++ b/nixos/modules/services/web-servers/garage.nix
@@ -9,7 +9,7 @@ let
 in
 {
   meta = {
-    doc = ./garage.xml;
+    doc = ./garage.md;
     maintainers = with pkgs.lib.maintainers; [ raitobezarius ];
   };
 
diff --git a/nixos/modules/services/web-servers/garage.xml b/nixos/modules/services/web-servers/garage.xml
deleted file mode 100644
index 6a16b1693dafd..0000000000000
--- a/nixos/modules/services/web-servers/garage.xml
+++ /dev/null
@@ -1,206 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-garage">
-  <title>Garage</title>
-  <para>
-    <link xlink:href="https://garagehq.deuxfleurs.fr/">Garage</link> is
-    an open-source, self-hostable S3 store, simpler than MinIO, for
-    geodistributed stores. The server setup can be automated using
-    <link linkend="opt-services.garage.enable">services.garage</link>. A
-    client configured to your local Garage instance is available in the
-    global environment as <literal>garage-manage</literal>.
-  </para>
-  <para>
-    The current default by NixOS is <literal>garage_0_8</literal> which
-    is also the latest major version available.
-  </para>
-  <section xml:id="module-services-garage-upgrade-scenarios">
-    <title>General considerations on upgrades</title>
-    <para>
-      Garage provides a cookbook documentation on how to upgrade:
-      <link xlink:href="https://garagehq.deuxfleurs.fr/documentation/cookbook/upgrading/">https://garagehq.deuxfleurs.fr/documentation/cookbook/upgrading/</link>
-    </para>
-    <warning>
-      <para>
-        Garage has two types of upgrades: patch-level upgrades and
-        minor/major version upgrades.
-      </para>
-      <para>
-        In all cases, you should read the changelog and ideally test the
-        upgrade on a staging cluster.
-      </para>
-      <para>
-        Checking the health of your cluster can be achieved using
-        <literal>garage-manage repair</literal>.
-      </para>
-    </warning>
-    <warning>
-      <para>
-        Until 1.0 is released, patch-level upgrades are considered as
-        minor version upgrades. Minor version upgrades are considered as
-        major version upgrades. i.e. 0.6 to 0.7 is a major version
-        upgrade.
-      </para>
-    </warning>
-    <itemizedlist spacing="compact">
-      <listitem>
-        <para>
-          <emphasis role="strong">Straightforward upgrades (patch-level
-          upgrades).</emphasis> Upgrades must be performed one by one,
-          i.e. for each node, stop it, upgrade it : change
-          <link linkend="opt-system.stateVersion">stateVersion</link> or
-          <link linkend="opt-services.garage.package">services.garage.package</link>,
-          restart it if it was not already by switching.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <emphasis role="strong">Multiple version upgrades.</emphasis>
-          Garage do not provide any guarantee on moving more than one
-          major-version forward. E.g., if you’re on
-          <literal>0.7</literal>, you cannot upgrade to
-          <literal>0.9</literal>. You need to upgrade to
-          <literal>0.8</literal> first. As long as
-          <link linkend="opt-system.stateVersion">stateVersion</link> is
-          declared properly, this is enforced automatically. The module
-          will issue a warning to remind the user to upgrade to latest
-          Garage <emphasis>after</emphasis> that deploy.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="module-services-garage-advanced-upgrades">
-    <title>Advanced upgrades (minor/major version upgrades)</title>
-    <para>
-      Here are some baseline instructions to handle advanced upgrades in
-      Garage, when in doubt, please refer to upstream instructions.
-    </para>
-    <itemizedlist spacing="compact">
-      <listitem>
-        <para>
-          Disable API and web access to Garage.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Perform
-          <literal>garage-manage repair --all-nodes --yes tables</literal>
-          and
-          <literal>garage-manage repair --all-nodes --yes blocks</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Verify the resulting logs and check that data is synced
-          properly between all nodes. If you have time, do additional
-          checks (<literal>scrub</literal>,
-          <literal>block_refs</literal>, etc.).
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Check if queues are empty by
-          <literal>garage-manage stats</literal> or through monitoring
-          tools.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Run <literal>systemctl stop garage</literal> to stop the
-          actual Garage version.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Backup the metadata folder of ALL your nodes, e.g. for a
-          metadata directory (the default one) in
-          <literal>/var/lib/garage/meta</literal>, you can run
-          <literal>pushd /var/lib/garage; tar -acf meta-v0.7.tar.zst meta/; popd</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Run the offline migration:
-          <literal>nix-shell -p garage_0_8 --run &quot;garage offline-repair --yes&quot;</literal>,
-          this can take some time depending on how many objects are
-          stored in your cluster.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Bump Garage version in your NixOS configuration, either by
-          changing
-          <link linkend="opt-system.stateVersion">stateVersion</link> or
-          bumping
-          <link linkend="opt-services.garage.package">services.garage.package</link>,
-          this should restart Garage automatically.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Perform
-          <literal>garage-manage repair --all-nodes --yes tables</literal>
-          and
-          <literal>garage-manage repair --all-nodes --yes blocks</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Wait for a full table sync to run.
-        </para>
-      </listitem>
-    </itemizedlist>
-    <para>
-      Your upgraded cluster should be in a working state, re-enable API
-      and web access.
-    </para>
-  </section>
-  <section xml:id="module-services-garage-maintainer-info">
-    <title>Maintainer information</title>
-    <para>
-      As stated in the previous paragraph, we must provide a clean
-      upgrade-path for Garage since it cannot move more than one major
-      version forward on a single upgrade. This chapter adds some notes
-      how Garage updates should be rolled out in the future. This is
-      inspired from how Nextcloud does it.
-    </para>
-    <para>
-      While patch-level updates are no problem and can be done directly
-      in the package-expression (and should be backported to supported
-      stable branches after that), major-releases should be added in a
-      new attribute (e.g. Garage <literal>v0.8.0</literal> should be
-      available in <literal>nixpkgs</literal> as
-      <literal>pkgs.garage_0_8_0</literal>). To provide simple upgrade
-      paths it’s generally useful to backport those as well to stable
-      branches. As long as the package-default isn’t altered, this won’t
-      break existing setups. After that, the versioning-warning in the
-      <literal>garage</literal>-module should be updated to make sure
-      that the
-      <link linkend="opt-services.garage.package">package</link>-option
-      selects the latest version on fresh setups.
-    </para>
-    <para>
-      If major-releases will be abandoned by upstream, we should check
-      first if those are needed in NixOS for a safe upgrade-path before
-      removing those. In that case we shold keep those packages, but
-      mark them as insecure in an expression like this (in
-      <literal>&lt;nixpkgs/pkgs/tools/filesystem/garage/default.nix&gt;</literal>):
-    </para>
-    <programlisting>
-/* ... */
-{
-  garage_0_7_3 = generic {
-    version = &quot;0.7.3&quot;;
-    sha256 = &quot;0000000000000000000000000000000000000000000000000000&quot;;
-    eol = true;
-  };
-}
-</programlisting>
-    <para>
-      Ideally we should make sure that it’s possible to jump two NixOS
-      versions forward: i.e. the warnings and the logic in the module
-      should guard a user to upgrade from a Garage on e.g. 22.11 to a
-      Garage on 23.11.
-    </para>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix
index c723b962c8474..bc5b4be510985 100644
--- a/nixos/modules/services/web-servers/nginx/default.nix
+++ b/nixos/modules/services/web-servers/nginx/default.nix
@@ -117,7 +117,7 @@ let
       # used by most other Linux distributions by default.
       include ${pkgs.mailcap}/etc/nginx/mime.types;
       # When recommendedOptimisation is disabled nginx fails to start because the mailmap mime.types database
-      # contains 1026 enries and the default is only 1024. Setting to a higher number to remove the need to
+      # contains 1026 entries and the default is only 1024. Setting to a higher number to remove the need to
       # overwrite it because nginx does not allow duplicated settings.
       types_hash_max_size 4096;
 
@@ -184,25 +184,17 @@ let
         brotli_window 512k;
         brotli_min_length 256;
         brotli_types ${lib.concatStringsSep " " compressMimeTypes};
-        brotli_buffers 32 8k;
       ''}
 
+      # https://docs.nginx.com/nginx/admin-guide/web-server/compression/
       ${optionalString cfg.recommendedGzipSettings ''
         gzip on;
-        gzip_proxied any;
-        gzip_comp_level 5;
-        gzip_types
-          application/atom+xml
-          application/javascript
-          application/json
-          application/xml
-          application/xml+rss
-          image/svg+xml
-          text/css
-          text/javascript
-          text/plain
-          text/xml;
+        gzip_static on;
         gzip_vary on;
+        gzip_comp_level 5;
+        gzip_min_length 256;
+        gzip_proxied expired no-cache no-store private auth;
+        gzip_types ${lib.concatStringsSep " " compressMimeTypes};
       ''}
 
       ${optionalString cfg.recommendedProxySettings ''
@@ -211,6 +203,9 @@ let
         proxy_send_timeout      ${cfg.proxyTimeout};
         proxy_read_timeout      ${cfg.proxyTimeout};
         proxy_http_version      1.1;
+        # don't let clients close the keep-alive connection to upstream. See the nginx blog for details:
+        # https://www.nginx.com/blog/avoiding-top-10-nginx-configuration-mistakes/#no-keepalives
+        proxy_set_header        "Connection" "";
         include ${recommendedProxyConfig};
       ''}
 
diff --git a/nixos/modules/services/x11/desktop-managers/cinnamon.nix b/nixos/modules/services/x11/desktop-managers/cinnamon.nix
index a693f3e2379a1..2d8addb0f10e3 100644
--- a/nixos/modules/services/x11/desktop-managers/cinnamon.nix
+++ b/nixos/modules/services/x11/desktop-managers/cinnamon.nix
@@ -109,6 +109,7 @@ in
         xapp
       ];
       services.cinnamon.apps.enable = mkDefault true;
+      services.gnome.evolution-data-server.enable = true;
       services.gnome.glib-networking.enable = true;
       services.gnome.gnome-keyring.enable = true;
       services.gvfs.enable = true;
@@ -214,7 +215,6 @@ in
       programs.geary.enable = mkDefault true;
       programs.gnome-disks.enable = mkDefault true;
       programs.gnome-terminal.enable = mkDefault true;
-      programs.evince.enable = mkDefault true;
       programs.file-roller.enable = mkDefault true;
 
       environment.systemPackages = with pkgs // pkgs.gnome // pkgs.cinnamon; utils.removePackagesByName [
@@ -232,6 +232,7 @@ in
         # external apps shipped with linux-mint
         hexchat
         gnome-calculator
+        gnome-calendar
         gnome-screenshot
       ] config.environment.cinnamon.excludePackages;
     })
diff --git a/nixos/modules/services/x11/desktop-managers/gnome.nix b/nixos/modules/services/x11/desktop-managers/gnome.nix
index dadfb421d3a87..79b2e7c6ead78 100644
--- a/nixos/modules/services/x11/desktop-managers/gnome.nix
+++ b/nixos/modules/services/x11/desktop-managers/gnome.nix
@@ -66,7 +66,7 @@ in
 {
 
   meta = {
-    doc = ./gnome.xml;
+    doc = ./gnome.md;
     maintainers = teams.gnome.members;
   };
 
diff --git a/nixos/modules/services/x11/desktop-managers/gnome.xml b/nixos/modules/services/x11/desktop-managers/gnome.xml
deleted file mode 100644
index 6613f49eec7af..0000000000000
--- a/nixos/modules/services/x11/desktop-managers/gnome.xml
+++ /dev/null
@@ -1,261 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="chap-gnome">
-  <title>GNOME Desktop</title>
-  <para>
-    GNOME provides a simple, yet full-featured desktop environment with
-    a focus on productivity. Its Mutter compositor supports both Wayland
-    and X server, and the GNOME Shell user interface is fully
-    customizable by extensions.
-  </para>
-  <section xml:id="sec-gnome-enable">
-    <title>Enabling GNOME</title>
-    <para>
-      All of the core apps, optional apps, games, and core developer
-      tools from GNOME are available.
-    </para>
-    <para>
-      To enable the GNOME desktop use:
-    </para>
-    <programlisting>
-services.xserver.desktopManager.gnome.enable = true;
-services.xserver.displayManager.gdm.enable = true;
-</programlisting>
-    <note>
-      <para>
-        While it is not strictly necessary to use GDM as the display
-        manager with GNOME, it is recommended, as some features such as
-        screen lock
-        <link linkend="sec-gnome-faq-can-i-use-lightdm-with-gnome">might
-        not work</link> without it.
-      </para>
-    </note>
-    <para>
-      The default applications used in NixOS are very minimal, inspired
-      by the defaults used in
-      <link xlink:href="https://gitlab.gnome.org/GNOME/gnome-build-meta/blob/40.0/elements/core/meta-gnome-core-utilities.bst">gnome-build-meta</link>.
-    </para>
-    <section xml:id="sec-gnome-without-the-apps">
-      <title>GNOME without the apps</title>
-      <para>
-        If you’d like to only use the GNOME desktop and not the apps,
-        you can disable them with:
-      </para>
-      <programlisting>
-services.gnome.core-utilities.enable = false;
-</programlisting>
-      <para>
-        and none of them will be installed.
-      </para>
-      <para>
-        If you’d only like to omit a subset of the core utilities, you
-        can use
-        <xref linkend="opt-environment.gnome.excludePackages" />. Note
-        that this mechanism can only exclude core utilities, games and
-        core developer tools.
-      </para>
-    </section>
-    <section xml:id="sec-gnome-disabling-services">
-      <title>Disabling GNOME services</title>
-      <para>
-        It is also possible to disable many of the
-        <link xlink:href="https://github.com/NixOS/nixpkgs/blob/b8ec4fd2a4edc4e30d02ba7b1a2cc1358f3db1d5/nixos/modules/services/x11/desktop-managers/gnome.nix#L329-L348">core
-        services</link>. For example, if you do not need indexing files,
-        you can disable Tracker with:
-      </para>
-      <programlisting>
-services.gnome.tracker-miners.enable = false;
-services.gnome.tracker.enable = false;
-</programlisting>
-      <para>
-        Note, however, that doing so is not supported and might break
-        some applications. Notably, GNOME Music cannot work without
-        Tracker.
-      </para>
-    </section>
-    <section xml:id="sec-gnome-games">
-      <title>GNOME games</title>
-      <para>
-        You can install all of the GNOME games with:
-      </para>
-      <programlisting>
-services.gnome.games.enable = true;
-</programlisting>
-    </section>
-    <section xml:id="sec-gnome-core-developer-tools">
-      <title>GNOME core developer tools</title>
-      <para>
-        You can install GNOME core developer tools with:
-      </para>
-      <programlisting>
-services.gnome.core-developer-tools.enable = true;
-</programlisting>
-    </section>
-  </section>
-  <section xml:id="sec-gnome-enable-flashback">
-    <title>Enabling GNOME Flashback</title>
-    <para>
-      GNOME Flashback provides a desktop environment based on the
-      classic GNOME 2 architecture. You can enable the default GNOME
-      Flashback session, which uses the Metacity window manager, with:
-    </para>
-    <programlisting>
-services.xserver.desktopManager.gnome.flashback.enableMetacity = true;
-</programlisting>
-    <para>
-      It is also possible to create custom sessions that replace
-      Metacity with a different window manager using
-      <xref linkend="opt-services.xserver.desktopManager.gnome.flashback.customSessions" />.
-    </para>
-    <para>
-      The following example uses <literal>xmonad</literal> window
-      manager:
-    </para>
-    <programlisting>
-services.xserver.desktopManager.gnome.flashback.customSessions = [
-  {
-    wmName = &quot;xmonad&quot;;
-    wmLabel = &quot;XMonad&quot;;
-    wmCommand = &quot;${pkgs.haskellPackages.xmonad}/bin/xmonad&quot;;
-    enableGnomePanel = false;
-  }
-];
-</programlisting>
-  </section>
-  <section xml:id="sec-gnome-icons-and-gtk-themes">
-    <title>Icons and GTK Themes</title>
-    <para>
-      Icon themes and GTK themes don’t require any special option to
-      install in NixOS.
-    </para>
-    <para>
-      You can add them to
-      <xref linkend="opt-environment.systemPackages" /> and switch to
-      them with GNOME Tweaks. If you’d like to do this manually in
-      dconf, change the values of the following keys:
-    </para>
-    <programlisting>
-/org/gnome/desktop/interface/gtk-theme
-/org/gnome/desktop/interface/icon-theme
-</programlisting>
-    <para>
-      in <literal>dconf-editor</literal>
-    </para>
-  </section>
-  <section xml:id="sec-gnome-shell-extensions">
-    <title>Shell Extensions</title>
-    <para>
-      Most Shell extensions are packaged under the
-      <literal>gnomeExtensions</literal> attribute. Some packages that
-      include Shell extensions, like <literal>gnome.gpaste</literal>,
-      don’t have their extension decoupled under this attribute.
-    </para>
-    <para>
-      You can install them like any other package:
-    </para>
-    <programlisting>
-environment.systemPackages = [
-  gnomeExtensions.dash-to-dock
-  gnomeExtensions.gsconnect
-  gnomeExtensions.mpris-indicator-button
-];
-</programlisting>
-    <para>
-      Unfortunately, we lack a way for these to be managed in a
-      completely declarative way. So you have to enable them manually
-      with an Extensions application. It is possible to use a
-      <link linkend="sec-gnome-gsettings-overrides">GSettings
-      override</link> for this on
-      <literal>org.gnome.shell.enabled-extensions</literal>, but that
-      will only influence the default value.
-    </para>
-  </section>
-  <section xml:id="sec-gnome-gsettings-overrides">
-    <title>GSettings Overrides</title>
-    <para>
-      Majority of software building on the GNOME platform use GLib’s
-      <link xlink:href="https://developer.gnome.org/gio/unstable/GSettings.html">GSettings</link>
-      system to manage runtime configuration. For our purposes, the
-      system consists of XML schemas describing the individual
-      configuration options, stored in the package, and a settings
-      backend, where the values of the settings are stored. On NixOS,
-      like on most Linux distributions, dconf database is used as the
-      backend.
-    </para>
-    <para>
-      <link xlink:href="https://developer.gnome.org/gio/unstable/GSettings.html#id-1.4.19.2.9.25">GSettings
-      vendor overrides</link> can be used to adjust the default values
-      for settings of the GNOME desktop and apps by replacing the
-      default values specified in the XML schemas. Using overrides will
-      allow you to pre-seed user settings before you even start the
-      session.
-    </para>
-    <warning>
-      <para>
-        Overrides really only change the default values for GSettings
-        keys so if you or an application changes the setting value, the
-        value set by the override will be ignored. Until
-        <link xlink:href="https://github.com/NixOS/nixpkgs/issues/54150">NixOS’s
-        dconf module implements changing values</link>, you will either
-        need to keep that in mind and clear the setting from the backend
-        using <literal>dconf reset</literal> command when that happens,
-        or use the
-        <link xlink:href="https://nix-community.github.io/home-manager/options.html#opt-dconf.settings">module
-        from home-manager</link>.
-      </para>
-    </warning>
-    <para>
-      You can override the default GSettings values using the
-      <xref linkend="opt-services.xserver.desktopManager.gnome.extraGSettingsOverrides" />
-      option.
-    </para>
-    <para>
-      Take note that whatever packages you want to override GSettings
-      for, you need to add them to
-      <xref linkend="opt-services.xserver.desktopManager.gnome.extraGSettingsOverridePackages" />.
-    </para>
-    <para>
-      You can use <literal>dconf-editor</literal> tool to explore which
-      GSettings you can set.
-    </para>
-    <section xml:id="sec-gnome-gsettings-overrides-example">
-      <title>Example</title>
-      <programlisting>
-services.xserver.desktopManager.gnome = {
-  extraGSettingsOverrides = ''
-    # Change default background
-    [org.gnome.desktop.background]
-    picture-uri='file://${pkgs.nixos-artwork.wallpapers.mosaic-blue.gnomeFilePath}'
-
-    # Favorite apps in gnome-shell
-    [org.gnome.shell]
-    favorite-apps=['org.gnome.Photos.desktop', 'org.gnome.Nautilus.desktop']
-  '';
-
-  extraGSettingsOverridePackages = [
-    pkgs.gsettings-desktop-schemas # for org.gnome.desktop
-    pkgs.gnome.gnome-shell # for org.gnome.shell
-  ];
-};
-</programlisting>
-    </section>
-  </section>
-  <section xml:id="sec-gnome-faq">
-    <title>Frequently Asked Questions</title>
-    <section xml:id="sec-gnome-faq-can-i-use-lightdm-with-gnome">
-      <title>Can I use LightDM with GNOME?</title>
-      <para>
-        Yes you can, and any other display-manager in NixOS.
-      </para>
-      <para>
-        However, it doesn’t work correctly for the Wayland session of
-        GNOME Shell yet, and won’t be able to lock your screen.
-      </para>
-      <para>
-        See
-        <link xlink:href="https://github.com/NixOS/nixpkgs/issues/56342">this
-        issue.</link>
-      </para>
-    </section>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/x11/desktop-managers/pantheon.nix b/nixos/modules/services/x11/desktop-managers/pantheon.nix
index f5cc2d8187da5..7791a98965d1a 100644
--- a/nixos/modules/services/x11/desktop-managers/pantheon.nix
+++ b/nixos/modules/services/x11/desktop-managers/pantheon.nix
@@ -17,7 +17,7 @@ in
 {
 
   meta = {
-    doc = ./pantheon.xml;
+    doc = ./pantheon.md;
     maintainers = teams.pantheon.members;
   };
 
diff --git a/nixos/modules/services/x11/desktop-managers/pantheon.xml b/nixos/modules/services/x11/desktop-managers/pantheon.xml
deleted file mode 100644
index 0e98b08fb6580..0000000000000
--- a/nixos/modules/services/x11/desktop-managers/pantheon.xml
+++ /dev/null
@@ -1,171 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="chap-pantheon">
-  <title>Pantheon Desktop</title>
-  <para>
-    Pantheon is the desktop environment created for the elementary OS
-    distribution. It is written from scratch in Vala, utilizing GNOME
-    technologies with GTK and Granite.
-  </para>
-  <section xml:id="sec-pantheon-enable">
-    <title>Enabling Pantheon</title>
-    <para>
-      All of Pantheon is working in NixOS and the applications should be
-      available, aside from a few
-      <link xlink:href="https://github.com/NixOS/nixpkgs/issues/58161">exceptions</link>.
-      To enable Pantheon, set
-    </para>
-    <programlisting>
-services.xserver.desktopManager.pantheon.enable = true;
-</programlisting>
-    <para>
-      This automatically enables LightDM and Pantheon’s LightDM greeter.
-      If you’d like to disable this, set
-    </para>
-    <programlisting>
-services.xserver.displayManager.lightdm.greeters.pantheon.enable = false;
-services.xserver.displayManager.lightdm.enable = false;
-</programlisting>
-    <para>
-      but please be aware using Pantheon without LightDM as a display
-      manager will break screenlocking from the UI. The NixOS module for
-      Pantheon installs all of Pantheon’s default applications. If you’d
-      like to not install Pantheon’s apps, set
-    </para>
-    <programlisting>
-services.pantheon.apps.enable = false;
-</programlisting>
-    <para>
-      You can also use
-      <xref linkend="opt-environment.pantheon.excludePackages" /> to
-      remove any other app (like <literal>elementary-mail</literal>).
-    </para>
-  </section>
-  <section xml:id="sec-pantheon-wingpanel-switchboard">
-    <title>Wingpanel and Switchboard plugins</title>
-    <para>
-      Wingpanel and Switchboard work differently than they do in other
-      distributions, as far as using plugins. You cannot install a
-      plugin globally (like with
-      <option>environment.systemPackages</option>) to start using it.
-      You should instead be using the following options:
-    </para>
-    <itemizedlist spacing="compact">
-      <listitem>
-        <para>
-          <xref linkend="opt-services.xserver.desktopManager.pantheon.extraWingpanelIndicators" />
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <xref linkend="opt-services.xserver.desktopManager.pantheon.extraSwitchboardPlugs" />
-        </para>
-      </listitem>
-    </itemizedlist>
-    <para>
-      to configure the programs with plugs or indicators.
-    </para>
-    <para>
-      The difference in NixOS is both these programs are patched to load
-      plugins from a directory that is the value of an environment
-      variable. All of which is controlled in Nix. If you need to
-      configure the particular packages manually you can override the
-      packages like:
-    </para>
-    <programlisting>
-wingpanel-with-indicators.override {
-  indicators = [
-    pkgs.some-special-indicator
-  ];
-};
-
-switchboard-with-plugs.override {
-  plugs = [
-    pkgs.some-special-plug
-  ];
-};
-</programlisting>
-    <para>
-      please note that, like how the NixOS options describe these as
-      extra plugins, this would only add to the default plugins included
-      with the programs. If for some reason you’d like to configure
-      which plugins to use exactly, both packages have an argument for
-      this:
-    </para>
-    <programlisting>
-wingpanel-with-indicators.override {
-  useDefaultIndicators = false;
-  indicators = specialListOfIndicators;
-};
-
-switchboard-with-plugs.override {
-  useDefaultPlugs = false;
-  plugs = specialListOfPlugs;
-};
-</programlisting>
-    <para>
-      this could be most useful for testing a particular plug-in in
-      isolation.
-    </para>
-  </section>
-  <section xml:id="sec-pantheon-faq">
-    <title>FAQ</title>
-    <variablelist spacing="compact">
-      <varlistentry>
-        <term>
-          <anchor xml:id="sec-pantheon-faq-messed-up-theme" />I have
-          switched from a different desktop and Pantheon’s theming looks
-          messed up.
-        </term>
-        <listitem>
-          <para>
-            Open Switchboard and go to: Administration → About → Restore
-            Default Settings → Restore Settings. This will reset any
-            dconf settings to their Pantheon defaults. Note this could
-            reset certain GNOME specific preferences if that desktop was
-            used prior.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <anchor xml:id="sec-pantheon-faq-gnome-and-pantheon" />I
-          cannot enable both GNOME and Pantheon.
-        </term>
-        <listitem>
-          <para>
-            This is a known
-            <link xlink:href="https://github.com/NixOS/nixpkgs/issues/64611">issue</link>
-            and there is no known workaround.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <anchor xml:id="sec-pantheon-faq-appcenter" />Does AppCenter
-          work, or is it available?
-        </term>
-        <listitem>
-          <para>
-            AppCenter has been available since 20.03. Starting from
-            21.11, the Flatpak backend should work so you can install
-            some Flatpak applications using it. However, due to missing
-            appstream metadata, the Packagekit backend does not function
-            currently. See this
-            <link xlink:href="https://github.com/NixOS/nixpkgs/issues/15932">issue</link>.
-          </para>
-          <para>
-            If you are using Pantheon, AppCenter should be installed by
-            default if you have
-            <link linkend="module-services-flatpak">Flatpak
-            support</link> enabled. If you also wish to add the
-            <literal>appcenter</literal> Flatpak remote:
-          </para>
-          <programlisting>
-$ flatpak remote-add --if-not-exists appcenter https://flatpak.elementary.io/repo.flatpakrepo
-</programlisting>
-        </listitem>
-      </varlistentry>
-    </variablelist>
-  </section>
-</chapter>
diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix
index b295b9a109b6d..73322696aeac6 100644
--- a/nixos/modules/services/x11/desktop-managers/plasma5.nix
+++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix
@@ -28,52 +28,11 @@ let
 
   libsForQt5 = pkgs.plasma5Packages;
   inherit (libsForQt5) kdeGear kdeFrameworks plasma5;
-  inherit (pkgs) writeText;
   inherit (lib)
     getBin optionalString literalExpression
     mkRemovedOptionModule mkRenamedOptionModule
     mkDefault mkIf mkMerge mkOption mkPackageOptionMD types;
 
-  ini = pkgs.formats.ini { };
-
-  gtkrc2 = writeText "gtkrc-2.0" ''
-    # Default GTK+ 2 config for NixOS Plasma 5
-    include "/run/current-system/sw/share/themes/Breeze/gtk-2.0/gtkrc"
-    style "user-font"
-    {
-      font_name="Sans Serif Regular"
-    }
-    widget_class "*" style "user-font"
-    gtk-font-name="Sans Serif Regular 10"
-    gtk-theme-name="Breeze"
-    gtk-icon-theme-name="breeze"
-    gtk-fallback-icon-theme="hicolor"
-    gtk-cursor-theme-name="breeze_cursors"
-    gtk-toolbar-style=GTK_TOOLBAR_ICONS
-    gtk-menu-images=1
-    gtk-button-images=1
-  '';
-
-  gtk3_settings = ini.generate "settings.ini" {
-    Settings = {
-      gtk-font-name = "Sans Serif Regular 10";
-      gtk-theme-name = "Breeze";
-      gtk-icon-theme-name = "breeze";
-      gtk-fallback-icon-theme = "hicolor";
-      gtk-cursor-theme-name = "breeze_cursors";
-      gtk-toolbar-style = "GTK_TOOLBAR_ICONS";
-      gtk-menu-images = 1;
-      gtk-button-images = 1;
-    };
-  };
-
-  kcminputrc = ini.generate "kcminputrc" {
-    Mouse = {
-      cursorTheme = "breeze_cursors";
-      cursorSize = 0;
-    };
-  };
-
   activationScript = ''
     ${set_XDG_CONFIG_HOME}
 
@@ -119,37 +78,6 @@ let
     XDG_CONFIG_HOME=''${XDG_CONFIG_HOME:-$HOME/.config}
   '';
 
-  startplasma = ''
-    ${set_XDG_CONFIG_HOME}
-    mkdir -p "''${XDG_CONFIG_HOME}"
-  '' + optionalString config.hardware.pulseaudio.enable ''
-    # Load PulseAudio module for routing support.
-    # See also: http://colin.guthr.ie/2009/10/so-how-does-the-kde-pulseaudio-support-work-anyway/
-      ${getBin config.hardware.pulseaudio.package}/bin/pactl load-module module-device-manager "do_routing=1"
-  '' + ''
-    ${activationScript}
-
-    # Create default configurations if Plasma has never been started.
-    kdeglobals="''${XDG_CONFIG_HOME}/kdeglobals"
-    if ! [ -f "$kdeglobals" ]; then
-      kcminputrc="''${XDG_CONFIG_HOME}/kcminputrc"
-      if ! [ -f "$kcminputrc" ]; then
-          cat ${kcminputrc} >"$kcminputrc"
-      fi
-
-      gtkrc2="$HOME/.gtkrc-2.0"
-      if ! [ -f "$gtkrc2" ]; then
-          cat ${gtkrc2} >"$gtkrc2"
-      fi
-
-      gtk3_settings="''${XDG_CONFIG_HOME}/gtk-3.0/settings.ini"
-      if ! [ -f "$gtk3_settings" ]; then
-          mkdir -p "$(dirname "$gtk3_settings")"
-          cat ${gtk3_settings} >"$gtk3_settings"
-      fi
-    fi
-  '';
-
 in
 
 {
@@ -162,8 +90,8 @@ in
 
     phononBackend = mkOption {
       type = types.enum [ "gstreamer" "vlc" ];
-      default = "gstreamer";
-      example = "vlc";
+      default = "vlc";
+      example = "gstreamer";
       description = lib.mdDoc "Phonon audio backend to install.";
     };
 
@@ -365,6 +293,7 @@ in
             pkgs.xdg-user-dirs # Update user dirs as described in https://freedesktop.org/wiki/Software/xdg-user-dirs/
           ];
           optionalPackages = [
+            pkgs.aha # needed by kinfocenter for fwupd support
             plasma-browser-integration
             konsole
             oxygen
@@ -387,7 +316,8 @@ in
         ++ lib.optional config.services.colord.enable pkgs.colord-kde
         ++ lib.optional config.services.hardware.bolt.enable pkgs.plasma5Packages.plasma-thunderbolt
         ++ lib.optionals config.services.samba.enable [ kdenetwork-filesharing pkgs.samba ]
-        ++ lib.optional config.services.xserver.wacom.enable pkgs.wacomtablet;
+        ++ lib.optional config.services.xserver.wacom.enable pkgs.wacomtablet
+        ++ lib.optional config.services.flatpak.enable flatpak-kcm;
 
       # Extra services for D-Bus activation
       services.dbus.packages = [
@@ -449,12 +379,7 @@ in
 
       security.pam.services.kde = { allowNullPassword = true; };
 
-      # Doing these one by one seems silly, but we currently lack a better
-      # construct for handling common pam configs.
-      security.pam.services.gdm.enableKwallet = true;
-      security.pam.services.kdm.enableKwallet = true;
-      security.pam.services.lightdm.enableKwallet = true;
-      security.pam.services.sddm.enableKwallet = true;
+      security.pam.services.login.enableKwallet = true;
 
       systemd.user.services = {
         plasma-early-setup = mkIf cfg.runUsingSystemd {
@@ -473,7 +398,6 @@ in
 
       # Update the start menu for each user that is currently logged in
       system.userActivationScripts.plasmaSetup = activationScript;
-      services.xserver.displayManager.setupCommands = startplasma;
 
       nixpkgs.config.firefox.enablePlasmaBrowserIntegration = true;
     })
@@ -520,6 +444,7 @@ in
             dolphin-plugins
             ffmpegthumbs
             kdegraphics-thumbnailers
+            pkgs.kio-admin
             kio-extras
           ];
           optionalPackages = [
diff --git a/nixos/modules/services/x11/display-managers/lightdm.nix b/nixos/modules/services/x11/display-managers/lightdm.nix
index f74e8efb8f640..65f414705fc51 100644
--- a/nixos/modules/services/x11/display-managers/lightdm.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm.nix
@@ -32,7 +32,7 @@ let
   usersConf = writeText "users.conf"
     ''
       [UserList]
-      minimum-uid=500
+      minimum-uid=1000
       hidden-users=${concatStringsSep " " dmcfg.hiddenUsers}
       hidden-shells=/run/current-system/sw/bin/nologin
     '';
diff --git a/nixos/modules/services/x11/display-managers/sddm.nix b/nixos/modules/services/x11/display-managers/sddm.nix
index a3f03d7a19a6b..0ddeac0f10984 100644
--- a/nixos/modules/services/x11/display-managers/sddm.nix
+++ b/nixos/modules/services/x11/display-managers/sddm.nix
@@ -215,10 +215,12 @@ in
     };
 
     security.pam.services = {
-      sddm = {
-        allowNullPassword = true;
-        startSession = true;
-      };
+      sddm.text = ''
+        auth      substack      login
+        account   include       login
+        password  substack      login
+        session   include       login
+      '';
 
       sddm-greeter.text = ''
         auth     required       pam_succeed_if.so audit quiet_success user = sddm
diff --git a/nixos/modules/services/x11/extra-layouts.nix b/nixos/modules/services/x11/extra-layouts.nix
index 574657a50c82d..9c88d12ca6f29 100644
--- a/nixos/modules/services/x11/extra-layouts.nix
+++ b/nixos/modules/services/x11/extra-layouts.nix
@@ -106,9 +106,9 @@ in
       description = lib.mdDoc ''
         Extra custom layouts that will be included in the xkb configuration.
         Information on how to create a new layout can be found here:
-        [](https://www.x.org/releases/current/doc/xorg-docs/input/XKB-Enhancing.html#Defining_New_Layouts).
+        <https://www.x.org/releases/current/doc/xorg-docs/input/XKB-Enhancing.html#Defining_New_Layouts>.
         For more examples see
-        [](https://wiki.archlinux.org/index.php/X_KeyBoard_extension#Basic_examples)
+        <https://wiki.archlinux.org/index.php/X_KeyBoard_extension#Basic_examples>
       '';
     };
 
diff --git a/nixos/modules/system/boot/binfmt.nix b/nixos/modules/system/boot/binfmt.nix
index 7f817e5d350da..98db67e7c00e0 100644
--- a/nixos/modules/system/boot/binfmt.nix
+++ b/nixos/modules/system/boot/binfmt.nix
@@ -316,11 +316,13 @@ in {
       mkdir -p -m 0755 /run/binfmt
       ${lib.concatStringsSep "\n" (lib.mapAttrsToList activationSnippet config.boot.binfmt.registrations)}
     '';
-    systemd.additionalUpstreamSystemUnits = lib.mkIf (config.boot.binfmt.registrations != {}) [
-      "proc-sys-fs-binfmt_misc.automount"
-      "proc-sys-fs-binfmt_misc.mount"
-      "systemd-binfmt.service"
-    ];
-    systemd.services.systemd-binfmt.restartTriggers = [ (builtins.toJSON config.boot.binfmt.registrations) ];
+    systemd = lib.mkIf (config.boot.binfmt.registrations != {}) {
+      additionalUpstreamSystemUnits = [
+        "proc-sys-fs-binfmt_misc.automount"
+        "proc-sys-fs-binfmt_misc.mount"
+        "systemd-binfmt.service"
+      ];
+      services.systemd-binfmt.restartTriggers = [ (builtins.toJSON config.boot.binfmt.registrations) ];
+    };
   };
 }
diff --git a/nixos/modules/system/boot/initrd-ssh.nix b/nixos/modules/system/boot/initrd-ssh.nix
index 701d242abc154..125f75d667069 100644
--- a/nixos/modules/system/boot/initrd-ssh.nix
+++ b/nixos/modules/system/boot/initrd-ssh.nix
@@ -128,13 +128,13 @@ in
         HostKey ${initrdKeyPath path}
       '')}
 
-      KexAlgorithms ${concatStringsSep "," sshdCfg.kexAlgorithms}
-      Ciphers ${concatStringsSep "," sshdCfg.ciphers}
-      MACs ${concatStringsSep "," sshdCfg.macs}
+      KexAlgorithms ${concatStringsSep "," sshdCfg.settings.KexAlgorithms}
+      Ciphers ${concatStringsSep "," sshdCfg.settings.Ciphers}
+      MACs ${concatStringsSep "," sshdCfg.settings.Macs}
 
-      LogLevel ${sshdCfg.logLevel}
+      LogLevel ${sshdCfg.settings.LogLevel}
 
-      ${if sshdCfg.useDns then ''
+      ${if sshdCfg.settings.UseDns then ''
         UseDNS yes
       '' else ''
         UseDNS no
diff --git a/nixos/modules/system/boot/kernel.nix b/nixos/modules/system/boot/kernel.nix
index 8f203b2c8b328..0298e28f32897 100644
--- a/nixos/modules/system/boot/kernel.nix
+++ b/nixos/modules/system/boot/kernel.nix
@@ -20,7 +20,7 @@ in
   ###### interface
 
   options = {
-    boot.kernel.enable = mkEnableOption (lib.mdDoc "the Linux kernel. This is useful for systemd-like containers which do not require a kernel.") // {
+    boot.kernel.enable = mkEnableOption (lib.mdDoc "the Linux kernel. This is useful for systemd-like containers which do not require a kernel") // {
       default = true;
     };
 
diff --git a/nixos/modules/system/boot/loader/external/external.nix b/nixos/modules/system/boot/loader/external/external.nix
index 7c5455bb47aa2..926cbd2b4b3f3 100644
--- a/nixos/modules/system/boot/loader/external/external.nix
+++ b/nixos/modules/system/boot/loader/external/external.nix
@@ -8,7 +8,7 @@ in
 {
   meta = {
     maintainers = with maintainers; [ cole-h grahamc raitobezarius ];
-    doc = ./external.xml;
+    doc = ./external.md;
   };
 
   options.boot.loader.external = {
diff --git a/nixos/modules/system/boot/loader/external/external.xml b/nixos/modules/system/boot/loader/external/external.xml
deleted file mode 100644
index 9a392c27441d9..0000000000000
--- a/nixos/modules/system/boot/loader/external/external.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<!-- Do not edit this file directly, edit its companion .md instead
-     and regenerate this file using nixos/doc/manual/md-to-db.sh -->
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-bootloader-external">
-  <title>External Bootloader Backends</title>
-  <para>
-    NixOS has support for several bootloader backends by default:
-    systemd-boot, grub, uboot, etc. The built-in bootloader backend
-    support is generic and supports most use cases. Some users may
-    prefer to create advanced workflows around managing the bootloader
-    and bootable entries.
-  </para>
-  <para>
-    You can replace the built-in bootloader support with your own
-    tooling using the <quote>external</quote> bootloader option.
-  </para>
-  <para>
-    Imagine you have created a new package called FooBoot. FooBoot
-    provides a program at
-    <literal>${pkgs.fooboot}/bin/fooboot-install</literal> which takes
-    the system closure’s path as its only argument and configures the
-    system’s bootloader.
-  </para>
-  <para>
-    You can enable FooBoot like this:
-  </para>
-  <programlisting language="nix">
-{ pkgs, ... }: {
-  boot.loader.external = {
-    enable = true;
-    installHook = &quot;${pkgs.fooboot}/bin/fooboot-install&quot;;
-  };
-}
-</programlisting>
-  <section xml:id="sec-bootloader-external-developing">
-    <title>Developing Custom Bootloader Backends</title>
-    <para>
-      Bootloaders should use
-      <link xlink:href="https://github.com/NixOS/rfcs/pull/125">RFC-0125</link>’s
-      Bootspec format and synthesis tools to identify the key properties
-      for bootable system generations.
-    </para>
-  </section>
-</chapter>
diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix
index 03d03cb348e82..cdb5d8bf3c26f 100644
--- a/nixos/modules/system/boot/luksroot.nix
+++ b/nixos/modules/system/boot/luksroot.nix
@@ -929,7 +929,14 @@ in
       ++ (if builtins.elem "xts" luks.cryptoModules then ["ecb"] else []);
 
     # copy the cryptsetup binary and it's dependencies
-    boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable) ''
+    boot.initrd.extraUtilsCommands = let
+      pbkdf2-sha512 = pkgs.runCommandCC "pbkdf2-sha512" { buildInputs = [ pkgs.openssl ]; } ''
+        mkdir -p "$out/bin"
+        cc -O3 -lcrypto ${./pbkdf2-sha512.c} -o "$out/bin/pbkdf2-sha512"
+        strip -s "$out/bin/pbkdf2-sha512"
+      '';
+    in
+    mkIf (!config.boot.initrd.systemd.enable) ''
       copy_bin_and_libs ${pkgs.cryptsetup}/bin/cryptsetup
       copy_bin_and_libs ${askPass}/bin/cryptsetup-askpass
       sed -i s,/bin/sh,$out/bin/sh, $out/bin/cryptsetup-askpass
@@ -939,9 +946,7 @@ in
         copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykinfo
         copy_bin_and_libs ${pkgs.openssl.bin}/bin/openssl
 
-        cc -O3 -I${pkgs.openssl.dev}/include -L${lib.getLib pkgs.openssl}/lib ${./pbkdf2-sha512.c} -o pbkdf2-sha512 -lcrypto
-        strip -s pbkdf2-sha512
-        copy_bin_and_libs pbkdf2-sha512
+        copy_bin_and_libs ${pbkdf2-sha512}/bin/pbkdf2-sha512
 
         mkdir -p $out/etc/ssl
         cp -pdv ${pkgs.openssl.out}/etc/ssl/openssl.cnf $out/etc/ssl
diff --git a/nixos/modules/system/boot/plymouth.nix b/nixos/modules/system/boot/plymouth.nix
index 9b6472fea4293..a1ab709385756 100644
--- a/nixos/modules/system/boot/plymouth.nix
+++ b/nixos/modules/system/boot/plymouth.nix
@@ -146,6 +146,9 @@ in
     systemd.services.systemd-ask-password-plymouth.wantedBy = [ "multi-user.target" ];
     systemd.paths.systemd-ask-password-plymouth.wantedBy = [ "multi-user.target" ];
 
+    # Prevent Plymouth taking over the screen during system updates.
+    systemd.services.plymouth-start.restartIfChanged = false;
+
     boot.initrd.systemd = {
       extraBin.plymouth = "${plymouth}/bin/plymouth"; # for the recovery shell
       storePaths = [
diff --git a/nixos/modules/system/boot/stage-1.nix b/nixos/modules/system/boot/stage-1.nix
index 4fcaeebffc3b2..d26ea7597c450 100644
--- a/nixos/modules/system/boot/stage-1.nix
+++ b/nixos/modules/system/boot/stage-1.nix
@@ -90,7 +90,7 @@ let
   # copy what we need.  Instead of using statically linked binaries,
   # we just copy what we need from Glibc and use patchelf to make it
   # work.
-  extraUtils = pkgs.runCommandCC "extra-utils"
+  extraUtils = pkgs.runCommand "extra-utils"
     { nativeBuildInputs = [pkgs.buildPackages.nukeReferences];
       allowedReferences = [ "out" ]; # prevent accidents like glibc being included in the initrd
     }
diff --git a/nixos/modules/system/boot/systemd/coredump.nix b/nixos/modules/system/boot/systemd/coredump.nix
index c2ca973d38074..03ef00e5683c1 100644
--- a/nixos/modules/system/boot/systemd/coredump.nix
+++ b/nixos/modules/system/boot/systemd/coredump.nix
@@ -44,7 +44,21 @@ in {
         '';
 
         # install provided sysctl snippets
-        "sysctl.d/50-coredump.conf".source = "${systemd}/example/sysctl.d/50-coredump.conf";
+        "sysctl.d/50-coredump.conf".source =
+          # Fix systemd-coredump error caused by truncation of `kernel.core_pattern`
+          # when the `systemd` derivation name is too long. This works by substituting
+          # the path to `systemd` with a symlink that has a constant-length path.
+          #
+          # See: https://github.com/NixOS/nixpkgs/issues/213408
+          pkgs.substitute {
+            src = "${systemd}/example/sysctl.d/50-coredump.conf";
+            replacements = [
+              "--replace"
+              "${systemd}"
+              "${pkgs.symlinkJoin { name = "systemd"; paths = [ systemd ]; }}"
+            ];
+          };
+
         "sysctl.d/50-default.conf".source = "${systemd}/example/sysctl.d/50-default.conf";
       };
 
diff --git a/nixos/modules/system/boot/systemd/initrd.nix b/nixos/modules/system/boot/systemd/initrd.nix
index 620d76aef20d8..cf76704577fd0 100644
--- a/nixos/modules/system/boot/systemd/initrd.nix
+++ b/nixos/modules/system/boot/systemd/initrd.nix
@@ -118,7 +118,7 @@ let
     name = "initrd-bin-env";
     paths = map getBin cfg.initrdBin;
     pathsToLink = ["/bin" "/sbin"];
-    postBuild = concatStringsSep "\n" (mapAttrsToList (n: v: "ln -s '${v}' $out/bin/'${n}'") cfg.extraBin);
+    postBuild = concatStringsSep "\n" (mapAttrsToList (n: v: "ln -sf '${v}' $out/bin/'${n}'") cfg.extraBin);
   };
 
   initialRamdisk = pkgs.makeInitrdNG {
@@ -427,9 +427,6 @@ in {
         # fido2 support
         "${cfg.package}/lib/cryptsetup/libcryptsetup-token-systemd-fido2.so"
         "${pkgs.libfido2}/lib/libfido2.so.1"
-
-        # the unwrapped systemd-cryptsetup executable
-        "${cfg.package}/lib/systemd/.systemd-cryptsetup-wrapped"
       ] ++ jobScripts;
 
       targets.initrd.aliases = ["default.target"];
@@ -495,7 +492,7 @@ in {
 
           # If we are not booting a NixOS closure (e.g. init=/bin/sh),
           # we don't know what root to prepare so we don't do anything
-          if ! [ -x "/sysroot$closure/prepare-root" ]; then
+          if ! [ -x "/sysroot$(readlink "/sysroot$closure/prepare-root" || echo "$closure/prepare-root")" ]; then
             echo "NEW_INIT=''${initParam[1]}" > /etc/switch-root.conf
             echo "$closure does not look like a NixOS installation - not activating"
             exit 0
diff --git a/nixos/modules/system/boot/systemd/repart.nix b/nixos/modules/system/boot/systemd/repart.nix
new file mode 100644
index 0000000000000..8f3a700237700
--- /dev/null
+++ b/nixos/modules/system/boot/systemd/repart.nix
@@ -0,0 +1,123 @@
+{ config, pkgs, lib, ... }:
+
+let
+  cfg = config.systemd.repart;
+  initrdCfg = config.boot.initrd.systemd.repart;
+
+  writeDefinition = name: partitionConfig: pkgs.writeText
+    "${name}.conf"
+    (lib.generators.toINI { } { Partition = partitionConfig; });
+
+  listOfDefinitions = lib.mapAttrsToList
+    writeDefinition
+    (lib.filterAttrs (k: _: !(lib.hasPrefix "_" k)) cfg.partitions);
+
+  # Create a directory in the store that contains a copy of all definition
+  # files. This is then passed to systemd-repart in the initrd so it can access
+  # the definition files after the sysroot has been mounted but before
+  # activation. This needs a hard copy of the files and not just symlinks
+  # because otherwise the files do not show up in the sysroot.
+  definitionsDirectory = pkgs.runCommand "systemd-repart-definitions" { } ''
+    mkdir -p $out
+    ${(lib.concatStringsSep "\n"
+      (map (pkg: "cp ${pkg} $out/${pkg.name}") listOfDefinitions)
+    )}
+  '';
+in
+{
+  options = {
+    boot.initrd.systemd.repart.enable = lib.mkEnableOption (lib.mdDoc "systemd-repart") // {
+      description = lib.mdDoc ''
+        Grow and add partitions to a partition table at boot time in the initrd.
+        systemd-repart only works with GPT partition tables.
+
+        To run systemd-repart after the initrd, see
+        `options.systemd.repart.enable`.
+      '';
+    };
+
+    systemd.repart = {
+      enable = lib.mkEnableOption (lib.mdDoc "systemd-repart") // {
+        description = lib.mdDoc ''
+          Grow and add partitions to a partition table.
+          systemd-repart only works with GPT partition tables.
+
+          To run systemd-repart while in the initrd, see
+          `options.boot.initrd.systemd.repart.enable`.
+        '';
+      };
+
+      partitions = lib.mkOption {
+        type = with lib.types; attrsOf (attrsOf (oneOf [ str int bool ]));
+        default = { };
+        example = {
+          "10-root" = {
+            Type = "root";
+          };
+          "20-home" = {
+            Type = "home";
+            SizeMinBytes = "512M";
+            SizeMaxBytes = "2G";
+          };
+        };
+        description = lib.mdDoc ''
+          Specify partitions as a set of the names of the definition files as the
+          key and the partition configuration as its value. The partition
+          configuration can use all upstream options. See <link
+          xlink:href="https://www.freedesktop.org/software/systemd/man/repart.d.html"/>
+          for all available options.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf (cfg.enable || initrdCfg.enable) {
+    # Always link the definitions into /etc so that they are also included in
+    # the /nix/store of the sysroot during early userspace (i.e. while in the
+    # initrd).
+    environment.etc."repart.d".source = definitionsDirectory;
+
+    boot.initrd.systemd = lib.mkIf initrdCfg.enable {
+      additionalUpstreamUnits = [
+        "systemd-repart.service"
+      ];
+
+      storePaths = [
+        "${config.boot.initrd.systemd.package}/bin/systemd-repart"
+      ];
+
+      # Override defaults in upstream unit.
+      services.systemd-repart = {
+        # Unset the conditions as they cannot be met before activation because
+        # the definition files are not stored in the expected locations.
+        unitConfig.ConditionDirectoryNotEmpty = [
+          " " # required to unset the previous value.
+        ];
+        serviceConfig = {
+          # systemd-repart runs before the activation script. Thus we cannot
+          # rely on them being linked in /etc already. Instead we have to
+          # explicitly pass their location in the sysroot to the binary.
+          ExecStart = [
+            " " # required to unset the previous value.
+            ''${config.boot.initrd.systemd.package}/bin/systemd-repart \
+                  --definitions=/sysroot${definitionsDirectory} \
+                  --dry-run=no
+            ''
+          ];
+        };
+        # Because the initrd does not have the `initrd-usr-fs.target` the
+        # upestream unit runs too early in the boot process, before the sysroot
+        # is available. However, systemd-repart needs access to the sysroot to
+        # find the definition files.
+        after = [ "sysroot.mount" ];
+      };
+    };
+
+    systemd = lib.mkIf cfg.enable {
+      additionalUpstreamSystemUnits = [
+        "systemd-repart.service"
+      ];
+    };
+  };
+
+}
diff --git a/nixos/modules/tasks/filesystems/envfs.nix b/nixos/modules/tasks/filesystems/envfs.nix
index 450b805f0f580..76344f5f87eaf 100644
--- a/nixos/modules/tasks/filesystems/envfs.nix
+++ b/nixos/modules/tasks/filesystems/envfs.nix
@@ -7,11 +7,11 @@ let
       device = "none";
       fsType = "envfs";
       options = [
-        "fallback-path=${pkgs.runCommand "fallback-path" {} ''
+        "fallback-path=${pkgs.runCommand "fallback-path" {} (''
           mkdir -p $out
-          ln -s ${pkgs.coreutils}/bin/env $out/env
-          ln -s ${config.system.build.binsh}/bin/sh $out/sh
-        ''}"
+          ln -s ${config.environment.usrbinenv} $out/env
+          ln -s ${config.environment.binsh} $out/sh
+        '' + cfg.extraFallbackPathCommands)}"
       ];
     };
     "/bin" = {
@@ -31,11 +31,19 @@ in {
           etc.
         '';
       };
+
       package = lib.mkOption {
         type = lib.types.package;
-        description = lib.mdDoc "Which package to use for the envfs.";
         default = pkgs.envfs;
         defaultText = lib.literalExpression "pkgs.envfs";
+        description = lib.mdDoc "Which package to use for the envfs.";
+      };
+
+      extraFallbackPathCommands = lib.mkOption {
+        type = lib.types.lines;
+        default = "";
+        example = "ln -s $''{pkgs.bash}/bin/bash $out/bash";
+        description = lib.mdDoc "Extra commands to run in the package that contains fallback executables in case not other executable is found";
       };
     };
   };
diff --git a/nixos/modules/virtualisation/amazon-image.nix b/nixos/modules/virtualisation/amazon-image.nix
index e6c2c72339fd7..aa44f26426970 100644
--- a/nixos/modules/virtualisation/amazon-image.nix
+++ b/nixos/modules/virtualisation/amazon-image.nix
@@ -25,11 +25,7 @@ in
 
   config = {
 
-    assertions = [
-      { assertion = versionOlder config.boot.kernelPackages.kernel.version "5.17";
-        message = "ENA driver fails to build with kernel >= 5.17";
-      }
-    ];
+    assertions = [ ];
 
     boot.growPartition = true;
 
diff --git a/nixos/modules/virtualisation/amazon-options.nix b/nixos/modules/virtualisation/amazon-options.nix
index 926fe43b0ffe6..3ea4a6cf78183 100644
--- a/nixos/modules/virtualisation/amazon-options.nix
+++ b/nixos/modules/virtualisation/amazon-options.nix
@@ -28,13 +28,13 @@ in {
             options = {
               mount = lib.mkOption {
                 description = lib.mdDoc "Where to mount this dataset.";
-                type = types.nullOr types.string;
+                type = types.nullOr types.str;
                 default = null;
               };
 
               properties = lib.mkOption {
                 description = lib.mdDoc "Properties to set on this dataset.";
-                type = types.attrsOf types.string;
+                type = types.attrsOf types.str;
                 default = {};
               };
             };
diff --git a/nixos/modules/virtualisation/docker.nix b/nixos/modules/virtualisation/docker.nix
index d9bd10ba1fc8f..046b8e2f79010 100644
--- a/nixos/modules/virtualisation/docker.nix
+++ b/nixos/modules/virtualisation/docker.nix
@@ -100,7 +100,7 @@ in
 
     logDriver =
       mkOption {
-        type = types.enum ["none" "json-file" "syslog" "journald" "gelf" "fluentd" "awslogs" "splunk" "etwlogs" "gcplogs"];
+        type = types.enum ["none" "json-file" "syslog" "journald" "gelf" "fluentd" "awslogs" "splunk" "etwlogs" "gcplogs" "local"];
         default = "journald";
         description =
           lib.mdDoc ''
@@ -163,7 +163,7 @@ in
   ###### implementation
 
   config = mkIf cfg.enable (mkMerge [{
-      boot.kernelModules = [ "bridge" "veth" ];
+      boot.kernelModules = [ "bridge" "veth" "br_netfilter" "xt_nat" ];
       boot.kernel.sysctl = {
         "net.ipv4.conf.all.forwarding" = mkOverride 98 true;
         "net.ipv4.conf.default.forwarding" = mkOverride 98 true;
diff --git a/nixos/modules/virtualisation/linode-config.nix b/nixos/modules/virtualisation/linode-config.nix
index d664e8269f411..bbf81bda9c024 100644
--- a/nixos/modules/virtualisation/linode-config.nix
+++ b/nixos/modules/virtualisation/linode-config.nix
@@ -6,8 +6,8 @@ with lib;
   services.openssh = {
     enable = true;
 
-    permitRootLogin = "prohibit-password";
-    passwordAuthentication = mkDefault false;
+    settings.PermitRootLogin = "prohibit-password";
+    settings.PasswordAuthentication = mkDefault false;
   };
 
   networking = {
diff --git a/nixos/modules/virtualisation/multipass.nix b/nixos/modules/virtualisation/multipass.nix
new file mode 100644
index 0000000000000..6ef7de4b2bf50
--- /dev/null
+++ b/nixos/modules/virtualisation/multipass.nix
@@ -0,0 +1,61 @@
+{ config
+, lib
+, pkgs
+, ...
+}:
+
+let
+  cfg = config.virtualisation.multipass;
+in
+{
+  options = {
+    virtualisation.multipass = {
+      enable = lib.mkEnableOption (lib.mdDoc ''
+        Multipass, a simple manager for virtualised Ubuntu instances.
+      '');
+
+      logLevel = lib.mkOption {
+        type = lib.types.enum [ "error" "warning" "info" "debug" "trace" ];
+        default = "debug";
+        description = lib.mdDoc ''
+          The logging verbosity of the multipassd binary.
+        '';
+      };
+
+      package = lib.mkPackageOptionMD pkgs "multipass" { };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+
+    systemd.services.multipass = {
+      description = "Multipass orchestrates virtual Ubuntu instances.";
+
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "network.target" ];
+      after = [ "network.target" ];
+
+      environment = {
+        "XDG_DATA_HOME" = "/var/lib/multipass/data";
+        "XDG_CACHE_HOME" = "/var/lib/multipass/cache";
+        "XDG_CONFIG_HOME" = "/var/lib/multipass/config";
+      };
+
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/multipassd --logger platform --verbosity ${cfg.logLevel}";
+        SyslogIdentifier = "multipassd";
+        Restart = "on-failure";
+        TimeoutStopSec = 300;
+        Type = "simple";
+
+        WorkingDirectory = "/var/lib/multipass";
+
+        StateDirectory = "multipass";
+        StateDirectoryMode = "0750";
+        CacheDirectory = "multipass";
+        CacheDirectoryMode = "0750";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/virtualisation/nixos-containers.nix b/nixos/modules/virtualisation/nixos-containers.nix
index e1e640c447425..669981da59740 100644
--- a/nixos/modules/virtualisation/nixos-containers.nix
+++ b/nixos/modules/virtualisation/nixos-containers.nix
@@ -514,6 +514,7 @@ in
                       };
                     in [ extraConfig ] ++ (map (x: x.value) defs);
                   prefix = [ "containers" name ];
+                  inherit (config) specialArgs;
                 }).config;
               };
             };
@@ -555,6 +556,16 @@ in
               '';
             };
 
+            specialArgs = mkOption {
+              type = types.attrsOf types.unspecified;
+              default = {};
+              description = lib.mdDoc ''
+                A set of special arguments to be passed to NixOS modules.
+                This will be merged into the `specialArgs` used to evaluate
+                the NixOS configurations.
+              '';
+            };
+
             ephemeral = mkOption {
               type = types.bool;
               default = false;
diff --git a/nixos/modules/virtualisation/openstack-options.nix b/nixos/modules/virtualisation/openstack-options.nix
index c71b581b02ca7..52f45de92ecbb 100644
--- a/nixos/modules/virtualisation/openstack-options.nix
+++ b/nixos/modules/virtualisation/openstack-options.nix
@@ -29,13 +29,13 @@ in
             options = {
               mount = lib.mkOption {
                 description = lib.mdDoc "Where to mount this dataset.";
-                type = types.nullOr types.string;
+                type = types.nullOr types.str;
                 default = null;
               };
 
               properties = lib.mkOption {
                 description = lib.mdDoc "Properties to set on this dataset.";
-                type = types.attrsOf types.string;
+                type = types.attrsOf types.str;
                 default = { };
               };
             };
diff --git a/nixos/modules/virtualisation/podman/default.nix b/nixos/modules/virtualisation/podman/default.nix
index 6c00fabaa1858..83ddba3ce06ef 100644
--- a/nixos/modules/virtualisation/podman/default.nix
+++ b/nixos/modules/virtualisation/podman/default.nix
@@ -7,6 +7,8 @@ let
 
   podmanPackage = (pkgs.podman.override {
     extraPackages = cfg.extraPackages
+      # setuid shadow
+      ++ [ "/run/wrappers" ]
       ++ lib.optional (builtins.elem "zfs" config.boot.supportedFilesystems) config.boot.zfs.package;
   });
 
@@ -181,10 +183,6 @@ in
 
       systemd.packages = [ cfg.package ];
 
-      systemd.services.podman.serviceConfig = {
-        ExecStart = [ "" "${cfg.package}/bin/podman $LOGGING system service" ];
-      };
-
       systemd.services.podman-prune = {
         description = "Prune podman resources";
 
@@ -205,10 +203,6 @@ in
       systemd.sockets.podman.wantedBy = [ "sockets.target" ];
       systemd.sockets.podman.socketConfig.SocketGroup = "podman";
 
-      systemd.user.services.podman.serviceConfig = {
-        ExecStart = [ "" "${cfg.package}/bin/podman $LOGGING system service" ];
-      };
-
       systemd.user.sockets.podman.wantedBy = [ "sockets.target" ];
 
       systemd.tmpfiles.packages = [
diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix
index 933a9c539e48b..06210529eb8c4 100644
--- a/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixos/modules/virtualisation/qemu-vm.nix
@@ -545,8 +545,7 @@ in
     virtualisation.vlans =
       mkOption {
         type = types.listOf types.ints.unsigned;
-        default = if config.virtualisation.interfaces == {} then [ 1 ] else [ ];
-        defaultText = lib.literalExpression ''if config.virtualisation.interfaces == {} then [ 1 ] else [ ]'';
+        default = [ 1 ];
         example = [ 1 2 ];
         description =
           lib.mdDoc ''
@@ -561,35 +560,6 @@ in
           '';
       };
 
-    virtualisation.interfaces = mkOption {
-      default = {};
-      example = {
-        enp1s0.vlan = 1;
-      };
-      description = lib.mdDoc ''
-        Network interfaces to add to the VM.
-      '';
-      type = with types; attrsOf (submodule {
-        options = {
-          vlan = mkOption {
-            type = types.ints.unsigned;
-            description = lib.mdDoc ''
-              VLAN to which the network interface is connected.
-            '';
-          };
-
-          assignIP = mkOption {
-            type = types.bool;
-            default = false;
-            description = lib.mdDoc ''
-              Automatically assign an IP address to the network interface using the same scheme as
-              virtualisation.vlans.
-            '';
-          };
-        };
-      });
-    };
-
     virtualisation.writableStore =
       mkOption {
         type = types.bool;
diff --git a/nixos/modules/virtualisation/virtualbox-image.nix b/nixos/modules/virtualisation/virtualbox-image.nix
index 2ea23d958cf77..bb42e6de069bd 100644
--- a/nixos/modules/virtualisation/virtualbox-image.nix
+++ b/nixos/modules/virtualisation/virtualbox-image.nix
@@ -107,6 +107,46 @@ in {
           };
         });
       };
+      postExportCommands = mkOption {
+        type = types.lines;
+        default = "";
+        example = ''
+          ${pkgs.cot}/bin/cot edit-hardware "$fn" \
+            -v vmx-14 \
+            --nics 2 \
+            --nic-types VMXNET3 \
+            --nic-names 'Nic name' \
+            --nic-networks 'Nic match' \
+            --network-descriptions 'Nic description' \
+            --scsi-subtypes VirtualSCSI
+        '';
+        description = lib.mdDoc ''
+          Extra commands to run after exporting the OVA to `$fn`.
+        '';
+      };
+      storageController = mkOption {
+        type = with types; attrsOf (oneOf [ str int bool (listOf str) ]);
+        example = {
+          name = "SCSI";
+          add = "scsi";
+          portcount = 16;
+          bootable = "on";
+          hostiocache = "on";
+        };
+        default = {
+          name = "SATA";
+          add = "sata";
+          portcount = 4;
+          bootable = "on";
+          hostiocache = "on";
+        };
+        description = lib.mdDoc ''
+          Parameters passed to the VirtualBox appliance. Must have at least
+          `name`.
+
+          Run `VBoxManage storagectl --help` to see more options.
+        '';
+      };
     };
   };
 
@@ -167,11 +207,11 @@ in {
           VBoxManage modifyvm "$vmName" \
             --memory ${toString cfg.memorySize} \
             ${lib.cli.toGNUCommandLineShell { } cfg.params}
-          VBoxManage storagectl "$vmName" --name SATA --add sata --portcount 4 --bootable on --hostiocache on
-          VBoxManage storageattach "$vmName" --storagectl SATA --port 0 --device 0 --type hdd \
+          VBoxManage storagectl "$vmName" ${lib.cli.toGNUCommandLineShell { } cfg.storageController}
+          VBoxManage storageattach "$vmName" --storagectl ${cfg.storageController.name} --port 0 --device 0 --type hdd \
             --medium disk.vmdk
           ${optionalString (cfg.extraDisk != null) ''
-            VBoxManage storageattach "$vmName" --storagectl SATA --port 1 --device 0 --type hdd \
+            VBoxManage storageattach "$vmName" --storagectl ${cfg.storageController.name} --port 1 --device 0 --type hdd \
             --medium data-disk.vmdk
           ''}
 
@@ -179,6 +219,7 @@ in {
           mkdir -p $out
           fn="$out/${cfg.vmFileName}"
           VBoxManage export "$vmName" --output "$fn" --options manifest ${escapeShellArgs cfg.exportParams}
+          ${cfg.postExportCommands}
 
           rm -v $diskImage