about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/README.md34
-rw-r--r--nixos/doc/manual/configuration/declarative-packages.section.md2
-rw-r--r--nixos/doc/manual/configuration/linux-kernel.chapter.md66
-rw-r--r--nixos/doc/manual/configuration/modularity.section.md4
-rw-r--r--nixos/doc/manual/configuration/subversion.chapter.md4
-rw-r--r--nixos/doc/manual/configuration/xfce.chapter.md2
-rw-r--r--nixos/doc/manual/development/running-nixos-tests-interactively.section.md21
-rw-r--r--nixos/doc/manual/development/writing-documentation.chapter.md10
-rw-r--r--nixos/doc/manual/installation/changing-config.chapter.md7
-rw-r--r--nixos/doc/manual/installation/installing-pxe.section.md2
-rw-r--r--nixos/doc/manual/release-notes/rl-1609.section.md2
-rw-r--r--nixos/doc/manual/release-notes/rl-1909.section.md2
-rw-r--r--nixos/doc/manual/release-notes/rl-2003.section.md2
-rw-r--r--nixos/doc/manual/release-notes/rl-2009.section.md2
-rw-r--r--nixos/doc/manual/release-notes/rl-2211.section.md2
-rw-r--r--nixos/doc/manual/release-notes/rl-2305.section.md4
-rw-r--r--nixos/doc/manual/release-notes/rl-2311.section.md184
-rw-r--r--nixos/doc/manual/release-notes/rl-2405.section.md29
-rw-r--r--nixos/lib/systemd-lib.nix35
-rw-r--r--nixos/lib/systemd-network-units.nix6
-rw-r--r--nixos/lib/test-driver/test_driver/machine.py16
-rw-r--r--nixos/maintainers/scripts/lxd/lxd-container-image-inner.nix2
-rw-r--r--nixos/modules/config/stevenblack.nix2
-rw-r--r--nixos/modules/config/terminfo.nix5
-rw-r--r--nixos/modules/config/users-groups.nix2
-rw-r--r--nixos/modules/config/xdg/portal.nix67
-rw-r--r--nixos/modules/hardware/video/nvidia.nix11
-rw-r--r--nixos/modules/installer/tools/nix-fallback-paths.nix10
-rw-r--r--nixos/modules/installer/tools/nixos-generate-config.pl16
-rw-r--r--nixos/modules/installer/tools/nixos-option/default.nix1
-rw-r--r--nixos/modules/misc/ids.nix4
-rw-r--r--nixos/modules/module-list.nix7
-rw-r--r--nixos/modules/programs/ausweisapp.nix6
-rw-r--r--nixos/modules/programs/git.nix9
-rw-r--r--nixos/modules/programs/hyprland.nix1
-rw-r--r--nixos/modules/programs/npm.nix2
-rw-r--r--nixos/modules/programs/singularity.nix22
-rw-r--r--nixos/modules/programs/wayland/sway.nix2
-rw-r--r--nixos/modules/programs/wayland/wayfire.nix2
-rw-r--r--nixos/modules/programs/zsh/oh-my-zsh.md2
-rw-r--r--nixos/modules/rename.nix1
-rw-r--r--nixos/modules/security/acme/default.nix4
-rw-r--r--nixos/modules/security/google_oslogin.nix4
-rw-r--r--nixos/modules/security/lock-kernel-modules.nix4
-rw-r--r--nixos/modules/security/pam.nix6
-rw-r--r--nixos/modules/security/pam_mount.nix6
-rw-r--r--nixos/modules/security/polkit.nix3
-rw-r--r--nixos/modules/security/sudo.nix151
-rw-r--r--nixos/modules/services/audio/jack.nix2
-rw-r--r--nixos/modules/services/audio/navidrome.nix7
-rw-r--r--nixos/modules/services/audio/slimserver.nix2
-rw-r--r--nixos/modules/services/backup/restic.nix8
-rw-r--r--nixos/modules/services/backup/syncoid.nix8
-rw-r--r--nixos/modules/services/computing/boinc/client.nix2
-rw-r--r--nixos/modules/services/computing/slurm/slurm.nix2
-rw-r--r--nixos/modules/services/databases/couchdb.nix2
-rw-r--r--nixos/modules/services/databases/firebird.nix2
-rw-r--r--nixos/modules/services/databases/pgmanage.nix2
-rw-r--r--nixos/modules/services/databases/postgresql.md121
-rw-r--r--nixos/modules/services/databases/postgresql.nix60
-rw-r--r--nixos/modules/services/databases/redis.nix2
-rw-r--r--nixos/modules/services/desktops/flatpak.md1
-rw-r--r--nixos/modules/services/desktops/gnome/at-spi2-core.nix2
-rw-r--r--nixos/modules/services/desktops/gnome/gnome-initial-setup.nix3
-rw-r--r--nixos/modules/services/desktops/pipewire/pipewire.nix17
-rw-r--r--nixos/modules/services/desktops/seatd.nix51
-rw-r--r--nixos/modules/services/development/zammad.nix4
-rw-r--r--nixos/modules/services/finance/odoo.nix2
-rw-r--r--nixos/modules/services/games/factorio.nix46
-rw-r--r--nixos/modules/services/hardware/fwupd.nix9
-rw-r--r--nixos/modules/services/hardware/udev.nix2
-rw-r--r--nixos/modules/services/hardware/usbmuxd.nix2
-rw-r--r--nixos/modules/services/home-automation/home-assistant.nix84
-rw-r--r--nixos/modules/services/logging/logstash.nix2
-rw-r--r--nixos/modules/services/mail/listmonk.nix2
-rw-r--r--nixos/modules/services/mail/mailman.nix5
-rw-r--r--nixos/modules/services/mail/roundcube.nix14
-rw-r--r--nixos/modules/services/mail/sympa.nix10
-rw-r--r--nixos/modules/services/matrix/matrix-sliding-sync.nix4
-rw-r--r--nixos/modules/services/matrix/mautrix-facebook.nix4
-rw-r--r--nixos/modules/services/misc/amazon-ssm-agent.nix9
-rw-r--r--nixos/modules/services/misc/apache-kafka.nix190
-rw-r--r--nixos/modules/services/misc/atuin.nix4
-rw-r--r--nixos/modules/services/misc/autofs.nix2
-rw-r--r--nixos/modules/services/misc/forgejo.md79
-rw-r--r--nixos/modules/services/misc/forgejo.nix22
-rw-r--r--nixos/modules/services/misc/gitea.nix10
-rw-r--r--nixos/modules/services/misc/kafka.md63
-rw-r--r--nixos/modules/services/misc/mediatomb.nix2
-rw-r--r--nixos/modules/services/misc/redmine.nix4
-rw-r--r--nixos/modules/services/misc/soft-serve.nix4
-rw-r--r--nixos/modules/services/misc/sourcehut/default.nix158
-rw-r--r--nixos/modules/services/misc/sourcehut/service.nix40
-rw-r--r--nixos/modules/services/monitoring/bosun.nix2
-rw-r--r--nixos/modules/services/monitoring/grafana.nix5
-rw-r--r--nixos/modules/services/monitoring/graphite.nix2
-rw-r--r--nixos/modules/services/monitoring/munin.nix14
-rw-r--r--nixos/modules/services/monitoring/nagios.nix2
-rw-r--r--nixos/modules/services/monitoring/ocsinventory-agent.md33
-rw-r--r--nixos/modules/services/monitoring/ocsinventory-agent.nix134
-rw-r--r--nixos/modules/services/monitoring/parsedmarc.nix1
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters.nix47
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/exportarr.nix55
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/mongodb.nix68
-rw-r--r--nixos/modules/services/monitoring/thanos.nix145
-rw-r--r--nixos/modules/services/monitoring/zabbix-proxy.nix4
-rw-r--r--nixos/modules/services/monitoring/zabbix-server.nix4
-rw-r--r--nixos/modules/services/network-filesystems/xtreemfs.nix10
-rw-r--r--nixos/modules/services/networking/asterisk.nix2
-rw-r--r--nixos/modules/services/networking/centrifugo.nix123
-rw-r--r--nixos/modules/services/networking/firewall-iptables.nix1
-rw-r--r--nixos/modules/services/networking/harmonia.nix2
-rw-r--r--nixos/modules/services/networking/i2pd.nix2
-rw-r--r--nixos/modules/services/networking/ircd-hybrid/builder.sh2
-rw-r--r--nixos/modules/services/networking/mullvad-vpn.nix4
-rw-r--r--nixos/modules/services/networking/nar-serve.nix2
-rw-r--r--nixos/modules/services/networking/nix-serve.nix4
-rw-r--r--nixos/modules/services/networking/ntp/chrony.nix66
-rw-r--r--nixos/modules/services/networking/ntp/ntpd.nix2
-rw-r--r--nixos/modules/services/networking/pixiecore.nix6
-rw-r--r--nixos/modules/services/networking/pleroma.nix2
-rw-r--r--nixos/modules/services/networking/prayer.nix90
-rw-r--r--nixos/modules/services/networking/squid.nix2
-rw-r--r--nixos/modules/services/networking/syncthing.nix4
-rw-r--r--nixos/modules/services/networking/tox-bootstrapd.nix2
-rw-r--r--nixos/modules/services/networking/unbound.nix28
-rw-r--r--nixos/modules/services/networking/unifi.nix82
-rw-r--r--nixos/modules/services/printing/cupsd.nix7
-rw-r--r--nixos/modules/services/search/kibana.nix213
-rw-r--r--nixos/modules/services/search/opensearch.nix19
-rw-r--r--nixos/modules/services/security/clamav.nix107
-rw-r--r--nixos/modules/services/security/hockeypuck.nix2
-rw-r--r--nixos/modules/services/torrent/flexget.nix1
-rw-r--r--nixos/modules/services/torrent/transmission.nix2
-rw-r--r--nixos/modules/services/video/unifi-video.nix4
-rw-r--r--nixos/modules/services/web-apps/akkoma.nix2
-rw-r--r--nixos/modules/services/web-apps/code-server.nix259
-rw-r--r--nixos/modules/services/web-apps/coder.nix10
-rw-r--r--nixos/modules/services/web-apps/discourse.nix4
-rw-r--r--nixos/modules/services/web-apps/gotosocial.nix4
-rw-r--r--nixos/modules/services/web-apps/invidious.nix15
-rw-r--r--nixos/modules/services/web-apps/invoiceplane.nix48
-rw-r--r--nixos/modules/services/web-apps/jitsi-meet.nix178
-rw-r--r--nixos/modules/services/web-apps/lemmy.nix2
-rw-r--r--nixos/modules/services/web-apps/mastodon.nix138
-rw-r--r--nixos/modules/services/web-apps/mediawiki.nix4
-rw-r--r--nixos/modules/services/web-apps/miniflux.nix17
-rw-r--r--nixos/modules/services/web-apps/mobilizon.nix13
-rw-r--r--nixos/modules/services/web-apps/moodle.nix4
-rw-r--r--nixos/modules/services/web-apps/netbox.nix4
-rw-r--r--nixos/modules/services/web-apps/nextcloud.md2
-rw-r--r--nixos/modules/services/web-apps/nextcloud.nix2
-rw-r--r--nixos/modules/services/web-apps/onlyoffice.nix2
-rw-r--r--nixos/modules/services/web-apps/outline.nix2
-rw-r--r--nixos/modules/services/web-apps/peering-manager.nix4
-rw-r--r--nixos/modules/services/web-apps/peertube.nix8
-rw-r--r--nixos/modules/services/web-apps/pixelfed.nix1
-rw-r--r--nixos/modules/services/web-apps/plantuml-server.nix118
-rw-r--r--nixos/modules/services/web-apps/plausible.nix52
-rw-r--r--nixos/modules/services/web-apps/tt-rss.nix15
-rw-r--r--nixos/modules/services/web-servers/caddy/default.nix4
-rw-r--r--nixos/modules/services/web-servers/hydron.nix2
-rw-r--r--nixos/modules/services/web-servers/jboss/builder.sh2
-rw-r--r--nixos/modules/services/web-servers/lighttpd/default.nix6
-rw-r--r--nixos/modules/services/web-servers/mighttpd2.nix4
-rw-r--r--nixos/modules/services/web-servers/nginx/default.nix2
-rw-r--r--nixos/modules/services/web-servers/tomcat.nix165
-rw-r--r--nixos/modules/services/x11/desktop-managers/budgie.nix1
-rw-r--r--nixos/modules/services/x11/desktop-managers/cinnamon.nix3
-rw-r--r--nixos/modules/services/x11/desktop-managers/deepin.nix3
-rw-r--r--nixos/modules/services/x11/desktop-managers/enlightenment.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/gnome.md2
-rw-r--r--nixos/modules/services/x11/desktop-managers/gnome.nix12
-rw-r--r--nixos/modules/services/x11/desktop-managers/lxqt.nix3
-rw-r--r--nixos/modules/services/x11/desktop-managers/mate.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/pantheon.nix7
-rw-r--r--nixos/modules/services/x11/desktop-managers/plasma5.nix1
-rw-r--r--nixos/modules/services/x11/desktop-managers/xfce.nix2
-rw-r--r--nixos/modules/services/x11/display-managers/default.nix2
-rw-r--r--nixos/modules/services/x11/display-managers/xpra.nix6
-rw-r--r--nixos/modules/services/x11/imwheel.nix2
-rw-r--r--nixos/modules/services/x11/xscreensaver.nix40
-rw-r--r--nixos/modules/system/boot/kernel.nix9
-rw-r--r--nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix2
-rw-r--r--nixos/modules/system/boot/loader/grub/grub.nix2
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py112
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix2
-rw-r--r--nixos/modules/system/boot/luksroot.nix2
-rw-r--r--nixos/modules/system/boot/networkd.nix38
-rw-r--r--nixos/modules/system/boot/stage-1-init.sh2
-rw-r--r--nixos/modules/system/boot/stage-1.nix14
-rw-r--r--nixos/modules/system/boot/systemd.nix190
-rw-r--r--nixos/modules/system/boot/systemd/initrd.nix3
-rw-r--r--nixos/modules/tasks/filesystems/bcachefs.nix93
-rw-r--r--nixos/modules/tasks/filesystems/zfs.nix31
-rw-r--r--nixos/modules/tasks/network-interfaces.nix2
-rw-r--r--nixos/modules/testing/test-instrumentation.nix151
-rw-r--r--nixos/modules/virtualisation/azure-agent.nix18
-rw-r--r--nixos/modules/virtualisation/azure-image.nix37
-rw-r--r--nixos/modules/virtualisation/google-compute-config.nix4
-rw-r--r--nixos/modules/virtualisation/google-compute-image.nix5
-rw-r--r--nixos/tests/all-tests.nix10
-rw-r--r--nixos/tests/amazon-ssm-agent.nix17
-rw-r--r--nixos/tests/archi.nix31
-rw-r--r--nixos/tests/centrifugo.nix80
-rw-r--r--nixos/tests/code-server.nix22
-rw-r--r--nixos/tests/containers-ip.nix5
-rw-r--r--nixos/tests/convos.nix1
-rw-r--r--nixos/tests/dex-oidc.nix2
-rw-r--r--nixos/tests/dnscrypt-wrapper/default.nix144
-rw-r--r--nixos/tests/elk.nix14
-rw-r--r--nixos/tests/ferretdb.nix2
-rw-r--r--nixos/tests/freshrss-pgsql.nix4
-rw-r--r--nixos/tests/gnome-extensions.nix151
-rw-r--r--nixos/tests/gnome-xorg.nix16
-rw-r--r--nixos/tests/gnome.nix14
-rw-r--r--nixos/tests/grafana/basic.nix2
-rw-r--r--nixos/tests/hockeypuck.nix2
-rw-r--r--nixos/tests/home-assistant.nix45
-rw-r--r--nixos/tests/installer.nix62
-rw-r--r--nixos/tests/invidious.nix3
-rw-r--r--nixos/tests/kafka.nix85
-rw-r--r--nixos/tests/lxd/ui.nix33
-rw-r--r--nixos/tests/ocsinventory-agent.nix33
-rw-r--r--nixos/tests/paperless.nix2
-rw-r--r--nixos/tests/pgadmin4.nix8
-rw-r--r--nixos/tests/pgbouncer.nix10
-rw-r--r--nixos/tests/plantuml-server.nix20
-rw-r--r--nixos/tests/plausible.nix7
-rw-r--r--nixos/tests/pleroma.nix9
-rw-r--r--nixos/tests/powerdns-admin.nix4
-rw-r--r--nixos/tests/prometheus-exporters.nix19
-rw-r--r--nixos/tests/prometheus.nix14
-rw-r--r--nixos/tests/restic.nix1
-rw-r--r--nixos/tests/seatd.nix51
-rw-r--r--nixos/tests/sftpgo.nix2
-rw-r--r--nixos/tests/slimserver.nix47
-rw-r--r--nixos/tests/sourcehut.nix6
-rw-r--r--nixos/tests/sudo.nix3
-rw-r--r--nixos/tests/systemd-boot.nix45
-rw-r--r--nixos/tests/systemd-initrd-modprobe.nix7
-rw-r--r--nixos/tests/systemd-initrd-networkd-ssh.nix52
-rw-r--r--nixos/tests/systemd-initrd-networkd.nix130
-rw-r--r--nixos/tests/systemd-initrd-simple.nix12
-rw-r--r--nixos/tests/systemd-networkd.nix2
-rw-r--r--nixos/tests/tandoor-recipes.nix4
-rw-r--r--nixos/tests/terminal-emulators.nix1
-rw-r--r--nixos/tests/tomcat.nix17
-rw-r--r--nixos/tests/udisks2.nix3
-rw-r--r--nixos/tests/vikunja.nix2
-rw-r--r--nixos/tests/web-apps/mastodon/remote-postgresql.nix22
-rw-r--r--nixos/tests/web-apps/mastodon/script.nix3
-rw-r--r--nixos/tests/web-apps/mastodon/standard.nix4
-rw-r--r--nixos/tests/wiki-js.nix5
-rw-r--r--nixos/tests/wordpress.nix2
-rw-r--r--nixos/tests/xmpp/ejabberd.nix2
-rw-r--r--nixos/tests/xscreensaver.nix64
-rw-r--r--nixos/tests/zfs.nix10
258 files changed, 4351 insertions, 2081 deletions
diff --git a/nixos/README.md b/nixos/README.md
index b3cd9d234fa61..07e82bf0ad938 100644
--- a/nixos/README.md
+++ b/nixos/README.md
@@ -8,6 +8,27 @@ https://nixos.org/nixos and in the manual in doc/manual.
 
 You can add new module to your NixOS configuration file (usually it’s `/etc/nixos/configuration.nix`). And do `sudo nixos-rebuild test -I nixpkgs=<path to your local nixpkgs folder> --fast`.
 
+## Commit conventions
+
+- Make sure you read about the [commit conventions](../CONTRIBUTING.md#commit-conventions) common to Nixpkgs as a whole.
+
+- Format the commit messages in the following way:
+
+  ```
+  nixos/(module): (init module | add setting | refactor | etc)
+
+  (Motivation for change. Link to release notes. Additional information.)
+  ```
+
+  Examples:
+
+  * nixos/hydra: add bazBaz option
+
+    Dual baz behavior is needed to do foo.
+  * nixos/nginx: refactor config generation
+
+    The old config generation system used impure shell scripts and could break in specific circumstances (see #1234).
+
 ## Reviewing contributions
 
 When changing the bootloader installation process, extra care must be taken. Grub installations cannot be rolled back, hence changes may break people’s installations forever. For any non-trivial change to the bootloader please file a PR asking for review, especially from \@edolstra.
@@ -21,12 +42,14 @@ Reviewing process:
 - Ensure that the module maintainers are notified.
   - [CODEOWNERS](https://help.github.com/articles/about-codeowners/) will make GitHub notify users based on the submitted changes, but it can happen that it misses some of the package maintainers.
 - Ensure that the module tests, if any, are succeeding.
+  - You may invoke OfBorg with `@ofborg test <module>` to build `nixosTests.<module>`
 - Ensure that the introduced options are correct.
   - Type should be appropriate (string related types differs in their merging capabilities, `loaOf` and `string` types are deprecated).
   - Description, default and example should be provided.
 - Ensure that option changes are backward compatible.
-  - `mkRenamedOptionModuleWith` provides a way to make option changes backward compatible.
-- Ensure that removed options are declared with `mkRemovedOptionModule`
+  - `mkRenamedOptionModuleWith` provides a way to make renamed option backward compatible.
+  - Use `lib.versionAtLeast config.system.stateVersion "23.11"` on backward incompatible changes which may corrupt, change or update the state stored on existing setups.
+- Ensure that removed options are declared with `mkRemovedOptionModule`.
 - Ensure that changes that are not backward compatible are mentioned in release notes.
 - Ensure that documentations affected by the change is updated.
 
@@ -55,6 +78,7 @@ New modules submissions introduce a new module to NixOS.
 
 Reviewing process:
 
+- Ensure that all file paths [fit the guidelines](../CONTRIBUTING.md#file-naming-and-organisation).
 - Ensure that the module tests, if any, are succeeding.
 - Ensure that the introduced options are correct.
   - Type should be appropriate (string related types differs in their merging capabilities, `loaOf` and `string` types are deprecated).
@@ -76,9 +100,9 @@ Sample template for a new module review is provided below.
 - [ ] options have default
 - [ ] options have example
 - [ ] options have descriptions
-- [ ] No unneeded package is added to environment.systemPackages
-- [ ] meta.maintainers is set
-- [ ] module documentation is declared in meta.doc
+- [ ] No unneeded package is added to `environment.systemPackages`
+- [ ] `meta.maintainers` is set
+- [ ] module documentation is declared in `meta.doc`
 
 ##### Possible improvements
 
diff --git a/nixos/doc/manual/configuration/declarative-packages.section.md b/nixos/doc/manual/configuration/declarative-packages.section.md
index 02eaa56192e46..480e250da8c73 100644
--- a/nixos/doc/manual/configuration/declarative-packages.section.md
+++ b/nixos/doc/manual/configuration/declarative-packages.section.md
@@ -37,7 +37,7 @@ Note: the `nixos` prefix tells us that we want to get the package from
 the `nixos` channel and works only in CLI tools. In declarative
 configuration use `pkgs` prefix (variable).
 
-To "uninstall" a package, simply remove it from
+To "uninstall" a package, remove it from
 [](#opt-environment.systemPackages) and run `nixos-rebuild switch`.
 
 ```{=include=} sections
diff --git a/nixos/doc/manual/configuration/linux-kernel.chapter.md b/nixos/doc/manual/configuration/linux-kernel.chapter.md
index f5bce99dd1bbe..9d1b2bc2f9b8f 100644
--- a/nixos/doc/manual/configuration/linux-kernel.chapter.md
+++ b/nixos/doc/manual/configuration/linux-kernel.chapter.md
@@ -84,26 +84,7 @@ available parameters, run `sysctl -a`.
 
 ## Building a custom kernel {#sec-linux-config-customizing}
 
-You can customize the default kernel configuration by overriding the arguments for your kernel package:
-
-```nix
-pkgs.linux_latest.override {
-  ignoreConfigErrors = true;
-  autoModules = false;
-  kernelPreferBuiltin = true;
-  extraStructuredConfig = with lib.kernel; {
-    DEBUG_KERNEL = yes;
-    FRAME_POINTER = yes;
-    KGDB = yes;
-    KGDB_SERIAL_CONSOLE = yes;
-    DEBUG_INFO = yes;
-  };
-}
-```
-
-See `pkgs/os-specific/linux/kernel/generic.nix` for details on how these arguments
-affect the generated configuration. You can also build a custom version of Linux by calling
-`pkgs.buildLinux` directly, which requires the `src` and `version` arguments to be specified.
+Please refer to the Nixpkgs manual for the various ways of [building a custom kernel](https://nixos.org/nixpkgs/manual#sec-linux-kernel).
 
 To use your custom kernel package in your NixOS configuration, set
 
@@ -111,50 +92,9 @@ To use your custom kernel package in your NixOS configuration, set
 boot.kernelPackages = pkgs.linuxPackagesFor yourCustomKernel;
 ```
 
-Note that this method will use the common configuration defined in `pkgs/os-specific/linux/kernel/common-config.nix`,
-which is suitable for a NixOS system.
-
-If you already have a generated configuration file, you can build a kernel that uses it with `pkgs.linuxManualConfig`:
-
-```nix
-let
-  baseKernel = pkgs.linux_latest;
-in pkgs.linuxManualConfig {
-  inherit (baseKernel) src modDirVersion;
-  version = "${baseKernel.version}-custom";
-  configfile = ./my_kernel_config;
-  allowImportFromDerivation = true;
-}
-```
-
-::: {.note}
-The build will fail if `modDirVersion` does not match the source's `kernel.release` file,
-so `modDirVersion` should remain tied to `src`.
-:::
-
-To edit the `.config` file for Linux X.Y, proceed as follows:
-
-```ShellSession
-$ nix-shell '<nixpkgs>' -A linuxKernel.kernels.linux_X_Y.configEnv
-$ unpackPhase
-$ cd linux-*
-$ make nconfig
-```
-
 ## Developing kernel modules {#sec-linux-config-developing-modules}
 
-When developing kernel modules it's often convenient to run
-edit-compile-run loop as quickly as possible. See below snippet as an
-example of developing `mellanox` drivers.
-
-```ShellSession
-$ nix-build '<nixpkgs>' -A linuxPackages.kernel.dev
-$ nix-shell '<nixpkgs>' -A linuxPackages.kernel
-$ unpackPhase
-$ cd linux-*
-$ make -C $dev/lib/modules/*/build M=$(pwd)/drivers/net/ethernet/mellanox modules
-# insmod ./drivers/net/ethernet/mellanox/mlx5/core/mlx5_core.ko
-```
+This section was moved to the [Nixpkgs manual](https://nixos.org/nixpkgs/manual#sec-linux-kernel-developing-modules).
 
 ## ZFS {#sec-linux-zfs}
 
@@ -163,7 +103,7 @@ available Linux kernel. It is recommended to use the latest available LTS that's
 with ZFS. Usually this is the default kernel provided by nixpkgs (i.e. `pkgs.linuxPackages`).
 
 Alternatively, it's possible to pin the system to the latest available kernel
-version *that is supported by ZFS* like this:
+version _that is supported by ZFS_ like this:
 
 ```nix
 {
diff --git a/nixos/doc/manual/configuration/modularity.section.md b/nixos/doc/manual/configuration/modularity.section.md
index 2eff15387987f..f4a566d669735 100644
--- a/nixos/doc/manual/configuration/modularity.section.md
+++ b/nixos/doc/manual/configuration/modularity.section.md
@@ -36,8 +36,8 @@ Here, we include two modules from the same directory, `vpn.nix` and
 Note that both `configuration.nix` and `kde.nix` define the option
 [](#opt-environment.systemPackages). When multiple modules define an
 option, NixOS will try to *merge* the definitions. In the case of
-[](#opt-environment.systemPackages), that's easy: the lists of
-packages can simply be concatenated. The value in `configuration.nix` is
+[](#opt-environment.systemPackages) the lists of packages will be
+concatenated. The value in `configuration.nix` is
 merged last, so for list-type options, it will appear at the end of the
 merged list. If you want it to appear first, you can use `mkBefore`:
 
diff --git a/nixos/doc/manual/configuration/subversion.chapter.md b/nixos/doc/manual/configuration/subversion.chapter.md
index 84f9c2703378a..ff870f5c40b97 100644
--- a/nixos/doc/manual/configuration/subversion.chapter.md
+++ b/nixos/doc/manual/configuration/subversion.chapter.md
@@ -2,7 +2,7 @@
 
 [Subversion](https://subversion.apache.org/) is a centralized
 version-control system. It can use a [variety of
-protocols](http://svnbook.red-bean.com/en/1.7/svn-book.html#svn.serverconfig.choosing)
+protocols](https://svnbook.red-bean.com/en/1.7/svn-book.html#svn.serverconfig.choosing)
 for communication between client and server.
 
 ## Subversion inside Apache HTTP {#module-services-subversion-apache-httpd}
@@ -14,7 +14,7 @@ for communication.
 
 For more information on the general setup, please refer to the [the
 appropriate section of the Subversion
-book](http://svnbook.red-bean.com/en/1.7/svn-book.html#svn.serverconfig.httpd).
+book](https://svnbook.red-bean.com/en/1.7/svn-book.html#svn.serverconfig.httpd).
 
 To configure, include in `/etc/nixos/configuration.nix` code to activate
 Apache HTTP, setting [](#opt-services.httpd.adminAddr)
diff --git a/nixos/doc/manual/configuration/xfce.chapter.md b/nixos/doc/manual/configuration/xfce.chapter.md
index a80be2b523e24..9ec4a51d6e35e 100644
--- a/nixos/doc/manual/configuration/xfce.chapter.md
+++ b/nixos/doc/manual/configuration/xfce.chapter.md
@@ -28,7 +28,7 @@ manually (system wide), put them into your
 
 Thunar (the Xfce file manager) is automatically enabled when Xfce is
 enabled. To enable Thunar without enabling Xfce, use the configuration
-option [](#opt-programs.thunar.enable) instead of simply adding
+option [](#opt-programs.thunar.enable) instead of adding
 `pkgs.xfce.thunar` to [](#opt-environment.systemPackages).
 
 If you'd like to add extra plugins to Thunar, add them to
diff --git a/nixos/doc/manual/development/running-nixos-tests-interactively.section.md b/nixos/doc/manual/development/running-nixos-tests-interactively.section.md
index 54002941d6349..4b8385d7e0d94 100644
--- a/nixos/doc/manual/development/running-nixos-tests-interactively.section.md
+++ b/nixos/doc/manual/development/running-nixos-tests-interactively.section.md
@@ -57,6 +57,27 @@ using:
 Once the connection is established, you can enter commands in the socat terminal
 where socat is running.
 
+## Port forwarding to NixOS test VMs {#sec-nixos-test-port-forwarding}
+
+If your test has only a single VM, you may use e.g.
+
+```ShellSession
+$ QEMU_NET_OPTS="hostfwd=tcp:127.0.0.1:2222-:22" ./result/bin/nixos-test-driver
+```
+
+to port-forward a port in the VM (here `22`) to the host machine (here port `2222`).
+
+This naturally does not work when multiple machines are involved,
+since a single port on the host cannot forward to multiple VMs.
+
+If the test defines multiple machines, you may opt to _temporarily_ set
+`virtualisation.forwardPorts` in the test definition for debugging.
+
+Such port forwardings connect via the VM's virtual network interface.
+Thus they cannot connect to ports that are only bound to the VM's
+loopback interface (`127.0.0.1`), and the VM's NixOS firewall
+must be configured to allow these connections.
+
 ## Reuse VM state {#sec-nixos-test-reuse-vm-state}
 
 You can re-use the VM states coming from a previous run by setting the
diff --git a/nixos/doc/manual/development/writing-documentation.chapter.md b/nixos/doc/manual/development/writing-documentation.chapter.md
index 8cb6823d09851..3d9bd318cf339 100644
--- a/nixos/doc/manual/development/writing-documentation.chapter.md
+++ b/nixos/doc/manual/development/writing-documentation.chapter.md
@@ -33,13 +33,13 @@ symlink at `./result/share/doc/nixos/index.html`.
 ## Editing DocBook XML {#sec-writing-docs-editing-docbook-xml}
 
 For general information on how to write in DocBook, see [DocBook 5: The
-Definitive Guide](http://www.docbook.org/tdg5/en/html/docbook.html).
+Definitive Guide](https://tdg.docbook.org/tdg/5.1/).
 
 Emacs nXML Mode is very helpful for editing DocBook XML because it
 validates the document as you write, and precisely locates errors. To
 use it, see [](#sec-emacs-docbook-xml).
 
-[Pandoc](http://pandoc.org) can generate DocBook XML from a multitude of
+[Pandoc](https://pandoc.org/) can generate DocBook XML from a multitude of
 formats, which makes a good starting point. Here is an example of Pandoc
 invocation to convert GitHub-Flavoured MarkDown to DocBook 5 XML:
 
@@ -50,7 +50,7 @@ pandoc -f markdown_github -t docbook5 docs.md -o my-section.md
 Pandoc can also quickly convert a single `section.xml` to HTML, which is
 helpful when drafting.
 
-Sometimes writing valid DocBook is simply too difficult. In this case,
+Sometimes writing valid DocBook is too difficult. In this case,
 submit your documentation updates in a [GitHub
 Issue](https://github.com/NixOS/nixpkgs/issues/new) and someone will
 handle the conversion to XML for you.
@@ -62,9 +62,9 @@ topic from scratch.
 
 Keep the following guidelines in mind when you create and add a topic:
 
--   The NixOS [`book`](http://www.docbook.org/tdg5/en/html/book.html)
+-   The NixOS [`book`](https://tdg.docbook.org/tdg/5.0/book.html)
     element is in `nixos/doc/manual/manual.xml`. It includes several
-    [`parts`](http://www.docbook.org/tdg5/en/html/book.html) which are in
+    [`parts`](https://tdg.docbook.org/tdg/5.0/book.html) which are in
     subdirectories.
 
 -   Store the topic file in the same directory as the `part` to which it
diff --git a/nixos/doc/manual/installation/changing-config.chapter.md b/nixos/doc/manual/installation/changing-config.chapter.md
index 11b49ccb1f671..12abf90b718fd 100644
--- a/nixos/doc/manual/installation/changing-config.chapter.md
+++ b/nixos/doc/manual/installation/changing-config.chapter.md
@@ -89,7 +89,7 @@ guest. For instance, the following will forward host port 2222 to guest
 port 22 (SSH):
 
 ```ShellSession
-$ QEMU_NET_OPTS="hostfwd=tcp::2222-:22" ./result/bin/run-*-vm
+$ QEMU_NET_OPTS="hostfwd=tcp:127.0.0.1:2222-:22" ./result/bin/run-*-vm
 ```
 
 allowing you to log in via SSH (assuming you have set the appropriate
@@ -98,3 +98,8 @@ passwords or SSH authorized keys):
 ```ShellSession
 $ ssh -p 2222 localhost
 ```
+
+Such port forwardings connect via the VM's virtual network interface.
+Thus they cannot connect to ports that are only bound to the VM's
+loopback interface (`127.0.0.1`), and the VM's NixOS firewall
+must be configured to allow these connections.
diff --git a/nixos/doc/manual/installation/installing-pxe.section.md b/nixos/doc/manual/installation/installing-pxe.section.md
index 4fbd6525f8c3b..c1cad99d39f34 100644
--- a/nixos/doc/manual/installation/installing-pxe.section.md
+++ b/nixos/doc/manual/installation/installing-pxe.section.md
@@ -4,7 +4,7 @@ Advanced users may wish to install NixOS using an existing PXE or iPXE
 setup.
 
 These instructions assume that you have an existing PXE or iPXE
-infrastructure and simply want to add the NixOS installer as another
+infrastructure and want to add the NixOS installer as another
 option. To build the necessary files from your current version of nixpkgs,
 you can run:
 
diff --git a/nixos/doc/manual/release-notes/rl-1609.section.md b/nixos/doc/manual/release-notes/rl-1609.section.md
index ad3478d0ca173..0cbabf58ca039 100644
--- a/nixos/doc/manual/release-notes/rl-1609.section.md
+++ b/nixos/doc/manual/release-notes/rl-1609.section.md
@@ -46,7 +46,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 Other notable improvements:
 
-- Revamped grsecurity/PaX support. There is now only a single general-purpose distribution kernel and the configuration interface has been streamlined. Desktop users should be able to simply set
+- Revamped grsecurity/PaX support. There is now only a single general-purpose distribution kernel and the configuration interface has been streamlined. Desktop users should be able to set
 
   ```nix
   {
diff --git a/nixos/doc/manual/release-notes/rl-1909.section.md b/nixos/doc/manual/release-notes/rl-1909.section.md
index 22cef05d4fa75..2bd04f8dd40a3 100644
--- a/nixos/doc/manual/release-notes/rl-1909.section.md
+++ b/nixos/doc/manual/release-notes/rl-1909.section.md
@@ -198,7 +198,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
   For nginx, the dependencies are still automatically managed when `services.nginx.virtualhosts.<name>.enableACME` is enabled just like before. What changed is that nginx now directly depends on the specific certificates that it needs, instead of depending on the catch-all `acme-certificates.target`. This target unit was also removed from the codebase. This will mean nginx will no longer depend on certificates it isn't explicitly managing and fixes a bug with certificate renewal ordering racing with nginx restarting which could lead to nginx getting in a broken state as described at [NixOS/nixpkgs\#60180](https://github.com/NixOS/nixpkgs/issues/60180).
 
-- The old deprecated `emacs` package sets have been dropped. What used to be called `emacsPackagesNg` is now simply called `emacsPackages`.
+- The old deprecated `emacs` package sets have been dropped. What used to be called `emacsPackagesNg` is now called `emacsPackages`.
 
 - `services.xserver.desktopManager.xterm` is now disabled by default if `stateVersion` is 19.09 or higher. Previously the xterm desktopManager was enabled when xserver was enabled, but it isn't useful for all people so it didn't make sense to have any desktopManager enabled default.
 
diff --git a/nixos/doc/manual/release-notes/rl-2003.section.md b/nixos/doc/manual/release-notes/rl-2003.section.md
index 76cee8858e80a..695f8a2c95cab 100644
--- a/nixos/doc/manual/release-notes/rl-2003.section.md
+++ b/nixos/doc/manual/release-notes/rl-2003.section.md
@@ -482,7 +482,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
   - If you use `postgresql` on a different server, you don't need to change anything as well since this module was never designed to configure remote databases.
 
-  - If you use `postgresql` and configured your synapse initially on `19.09` or older, you simply need to enable postgresql-support explicitly:
+  - If you use `postgresql` and configured your synapse initially on `19.09` or older, you need to enable postgresql-support explicitly:
 
     ```nix
     { ... }: {
diff --git a/nixos/doc/manual/release-notes/rl-2009.section.md b/nixos/doc/manual/release-notes/rl-2009.section.md
index 6bb75a04b3e8a..eac02a8ff445b 100644
--- a/nixos/doc/manual/release-notes/rl-2009.section.md
+++ b/nixos/doc/manual/release-notes/rl-2009.section.md
@@ -422,7 +422,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - The `systemd-networkd` option `systemd.network.networks._name_.dhcpConfig` has been renamed to [systemd.network.networks._name_.dhcpV4Config](options.html#opt-systemd.network.networks._name_.dhcpV4Config) following upstream systemd's documentation change. See systemd.network 5 for details.
 
-- In the `picom` module, several options that accepted floating point numbers encoded as strings (for example [services.picom.activeOpacity](options.html#opt-services.picom.activeOpacity)) have been changed to the (relatively) new native `float` type. To migrate your configuration simply remove the quotes around the numbers.
+- In the `picom` module, several options that accepted floating point numbers encoded as strings (for example [services.picom.activeOpacity](options.html#opt-services.picom.activeOpacity)) have been changed to the (relatively) new native `float` type. To migrate your configuration remove the quotes around the numbers.
 
 - When using `buildBazelPackage` from Nixpkgs, `flat` hash mode is now used for dependencies instead of `recursive`. This is to better allow using hashed mirrors where needed. As a result, these hashes will have changed.
 
diff --git a/nixos/doc/manual/release-notes/rl-2211.section.md b/nixos/doc/manual/release-notes/rl-2211.section.md
index 37079c20967bd..1c73d0c9790d5 100644
--- a/nixos/doc/manual/release-notes/rl-2211.section.md
+++ b/nixos/doc/manual/release-notes/rl-2211.section.md
@@ -14,7 +14,7 @@ In addition to numerous new and upgraded packages, this release includes the fol
   - Support for algorithms that `libxcrypt` [does not consider strong](https://github.com/besser82/libxcrypt/blob/v4.4.28/lib/hashes.conf#L41) are **deprecated** as of this release, and will be removed in NixOS 23.05.
   - This includes system login passwords. Given this, we **strongly encourage** all users to update their system passwords, as you will be unable to login if password hashes are not migrated by the time their support is removed.
     - When using `users.users.<name>.hashedPassword` to configure user passwords, run `mkpasswd`, and use the yescrypt hash that is provided as the new value.
-    - On the other hand, for interactively configured user passwords, simply re-set the passwords for all users with `passwd`.
+    - On the other hand, for interactively configured user passwords, re-set the passwords for all users with `passwd`.
     - This release introduces warnings for the use of deprecated hash algorithms for both methods of configuring passwords. To make sure you migrated correctly, run `nixos-rebuild switch`.
 
 - The NixOS documentation is now generated from markdown. While docbook is still part of the documentation build process, it's a big step towards the full migration.
diff --git a/nixos/doc/manual/release-notes/rl-2305.section.md b/nixos/doc/manual/release-notes/rl-2305.section.md
index 3d27d3fef8faa..21c798b3b4a46 100644
--- a/nixos/doc/manual/release-notes/rl-2305.section.md
+++ b/nixos/doc/manual/release-notes/rl-2305.section.md
@@ -611,7 +611,7 @@ If you are:
 - adding new rules with `*.rules`
 - running custom PulseAudio commands with `pulse.cmd`
 
-Simply move the definitions into the drop-in.
+Move the definitions into the drop-in.
 
 Note that the use of `context.exec` is not recommended and other methods of running your thing are likely a better option.
 
@@ -660,5 +660,5 @@ If reloading the module is not an option, proceed to [Nuclear option](#sec-relea
 
 #### Nuclear option {#sec-release-23.05-migration-pipewire-nuclear}
 If all else fails, you can still manually copy the contents of the default configuration file
-from `${pkgs.pipewire.lib}/share/pipewire` to `/etc/pipewire` and edit it to fully override the default.
+from `${pkgs.pipewire}/share/pipewire` to `/etc/pipewire` and edit it to fully override the default.
 However, this should be done only as a last resort. Please talk to the Pipewire maintainers if you ever need to do this.
diff --git a/nixos/doc/manual/release-notes/rl-2311.section.md b/nixos/doc/manual/release-notes/rl-2311.section.md
index 2b45bbac8766d..a6a3a946c7ba7 100644
--- a/nixos/doc/manual/release-notes/rl-2311.section.md
+++ b/nixos/doc/manual/release-notes/rl-2311.section.md
@@ -6,6 +6,8 @@
 
 - PostgreSQL now defaults to major version 15.
 
+- GNOME has been updated to version 45, see the [release notes](https://release.gnome.org/45/) for details. Notably, Loupe has replaced Eye of GNOME as the default image viewer, Snapshot has replaced Cheese as the default camera application, and Photos will no longer be installed.
+
 - Support for WiFi6 (IEEE 802.11ax) and WPA3-SAE-PK was enabled in the `hostapd` package, along with a significant rework of the hostapd module.
 
 - LXD now supports virtual machine instances to complement the existing container support
@@ -30,9 +32,14 @@
 
 [`sudo-rs`]: https://github.com/memorysafety/sudo-rs/
 
+- `linuxPackages_testing_bcachefs` is now soft-deprecated by `linuxPackages_testing`.
+  - Please consider changing your NixOS configuration's `boot.kernelPackages` to `linuxPackages_testing` until a stable kernel with bcachefs support is released.
+
 - All [ROCm](https://rocm.docs.amd.com/en/latest/) packages have been updated to 5.7.0.
   - [ROCm](https://rocm.docs.amd.com/en/latest/) package attribute sets are versioned: `rocmPackages` -> `rocmPackages_5`.
 
+- `yarn-berry` has been updated to 4.0.1. This means that NodeJS versions less than `18.12` are no longer supported by it. More details at the [upstream changelog](https://github.com/yarnpkg/berry/blob/master/CHANGELOG.md).
+
 - If the user has a custom shell enabled via `users.users.${USERNAME}.shell = ${CUSTOMSHELL}`, the
   assertion will require them to also set `programs.${CUSTOMSHELL}.enable =
   true`. This is generally safe behavior, but for anyone needing to opt out from
@@ -58,6 +65,8 @@
 
 - [hddfancontrol](https://github.com/desbma/hddfancontrol), a service to regulate fan speeds based on hard drive temperature. Available as [services.hddfancontrol](#opt-services.hddfancontrol.enable).
 
+- [seatd](https://sr.ht/~kennylevinsen/seatd/), A minimal seat management daemon. Available as [services.seatd](#opt-services.seatd.enable).
+
 - [GoToSocial](https://gotosocial.org/), an ActivityPub social network server, written in Golang. Available as [services.gotosocial](#opt-services.gotosocial.enable).
 
 - [Castopod](https://castopod.org/), an open-source hosting platform made for podcasters who want to engage and interact with their audience. Available as [services.castopod](#opt-services.castopod.enable).
@@ -102,6 +111,8 @@
 
 - [eris-server](https://codeberg.org/eris/eris-go). [ERIS](https://eris.codeberg.page/) is an encoding for immutable storage and this server provides block exchange as well as content decoding over HTTP and through a FUSE file-system. Available as [services.eris-server](#opt-services.eris-server.enable).
 
+- [forgejo](https://forgejo.org/), a git forge. Previously deployed as a drop-in replacement package in the [gitea module](#opt-services.gitea.package). Available as [services.forgejo](#opt-services.forgejo.enable). See migration instructions in the [NixOS manual](#module-forgejo) on how to migrate your forgejo instance using [`services.gitea.package = pkgs.forgejo`](#opt-services.gitea.package) to [`services.forgejo`](#opt-services.forgejo.enable).
+
 - hardware/infiniband.nix adds infiniband subnet manager support using an [opensm](https://github.com/linux-rdma/opensm) systemd-template service, instantiated on card guids. The module also adds kernel modules and cli tooling to help administrators debug and measure performance. Available as [hardware.infiniband.enable](#opt-hardware.infiniband.enable).
 
 - [zwave-js](https://github.com/zwave-js/zwave-js-server), a small server wrapper around Z-Wave JS to access it via a WebSocket. Available as [services.zwave-js](#opt-services.zwave-js.enable).
@@ -125,6 +136,8 @@
 
 - [ZITADEL](https://zitadel.com), a turnkey identity and access management platform. Available as [services.zitadel](#opt-services.zitadel.enable).
 
+- [exportarr](https://github.com/onedr0p/exportarr), Prometheus Exporters for Bazarr, Lidarr, Prowlarr, Radarr, Readarr, and Sonarr. Available as [services.prometheus.exporters.exportarr-bazarr](#opt-services.prometheus.exporters.exportarr-bazarr.enable)/[services.prometheus.exporters.exportarr-lidarr](#opt-services.prometheus.exporters.exportarr-lidarr.enable)/[services.prometheus.exporters.exportarr-prowlarr](#opt-services.prometheus.exporters.exportarr-prowlarr.enable)/[services.prometheus.exporters.exportarr-radarr](#opt-services.prometheus.exporters.exportarr-radarr.enable)/[services.prometheus.exporters.exportarr-readarr](#opt-services.prometheus.exporters.exportarr-readarr.enable)/[services.prometheus.exporters.exportarr-sonarr](#opt-services.prometheus.exporters.exportarr-sonarr.enable).
+
 - [netclient](https://github.com/gravitl/netclient), an automated WireGuard® Management Client. Available as [services.netclient](#opt-services.netclient.enable).
 
 - [trunk-ng](https://github.com/ctron/trunk), A fork of `trunk`: Build, bundle & ship your Rust WASM application to the web
@@ -139,6 +152,9 @@
 
 ## Backward Incompatibilities {#sec-release-23.11-incompatibilities}
 
+- `services.postgresql.ensurePermissions` has been deprecated in favor of `services.postgresql.ensureUsers.*.ensureDBOwnership` which simplifies the setup of database owned by a certain system user
+  in local database contexts (which make use of peer authentication via UNIX sockets), migration guidelines were provided in the NixOS manual, please refer to them if you are affected by a PostgreSQL 15 changing the way `GRANT ALL PRIVILEGES` is working. `services.postgresql.ensurePermissions` will be removed in 24.05. All NixOS modules were migrated using one of the strategy, e.g. `ensureDBOwnership` or `postStart`. More about this situation can be learnt in https://github.com/NixOS/nixpkgs/pull/266270.
+
 - `network-online.target` has been fixed to no longer time out for systems with `networking.useDHCP = true` and `networking.useNetworkd = true`.
   Workarounds for this can be removed.
 
@@ -150,6 +166,8 @@
 
 - The latest version of `clonehero` now stores custom content in `~/.clonehero`. See the [migration instructions](https://clonehero.net/2022/11/29/v23-to-v1-migration-instructions.html). Typically, these content files would exist along side the binary, but the previous build used a wrapper script that would store them in `~/.config/unity3d/srylain Inc_/Clone Hero`.
 
+- `services.mastodon` doesn't support providing a TCP port to its `streaming` component anymore, as upstream implemented parallelization by running multiple instances instead of running multiple processes in one instance. Please create a PR if you are interested in this feature.
+
 - The `services.hostapd` module was rewritten to support `passwordFile` like options, WPA3-SAE, and management of multiple interfaces. This breaks compatibility with older configurations.
   - `hostapd` is now started with additional systemd sandbox/hardening options for better security.
   - `services.hostapd.interface` was replaced with a per-radio and per-bss configuration scheme using [services.hostapd.radios](#opt-services.hostapd.radios).
@@ -158,10 +176,16 @@
 
 - `python3.pkgs.fetchPypi` (and `python3Packages.fetchPypi`) has been deprecated in favor of top-level `fetchPypi`.
 
+- xdg-desktop-portal has been updated to 1.18, which reworked how portal implementations are selected. If you roll your own desktop environment, you should either set `xdg.portal.config` or `xdg.portal.configPackages`, which allow fine-grained control over which portal backend to use for specific interfaces, as described in {manpage}`portals.conf(5)`.
+
+  If you don't provide configurations, a portal backend will only be considered when the desktop you use matches its deprecated `UseIn` key. While some NixOS desktop modules should already ship one for you, it is suggested to test portal availability by trying [Door Knocker](https://flathub.org/apps/xyz.tytanium.DoorKnocker) and [ASHPD Demo](https://flathub.org/apps/com.belmoussaoui.ashpd.demo). If things regressed, you may run `G_MESSAGES_DEBUG=all /path/to/xdg-desktop-portal/libexec/xdg-desktop-portal` for ideas on which config file and which portals are chosen.
+
 - `pass` now does not contain `password-store.el`.  Users should get `password-store.el` from Emacs lisp package set `emacs.pkgs.password-store`.
 
 - `services.knot` now supports `.settings` from RFC42.  The previous `.extraConfig` still works the same, but it displays a warning now.
 
+- `services.invoiceplane` now supports .settings from RFC42. The previous .extraConfig still works the same, but it displays a warning now.
+
 - `mu` now does not install `mu4e` files by default.  Users should get `mu4e` from Emacs lisp package set `emacs.pkgs.mu4e`.
 
 - `mariadb` now defaults to `mariadb_1011` instead of `mariadb_106`, meaning the default version was upgraded from 10.6.x to 10.11.x. See the [upgrade notes](https://mariadb.com/kb/en/upgrading-from-mariadb-10-6-to-mariadb-10-11/) for potential issues.
@@ -185,6 +209,8 @@
 
 - JACK tools (`jack_*` except `jack_control`) have moved from the `jack2` package to `jack-example-tools`
 
+- The `waagent` service does provisioning now
+
 - The `matrix-synapse` package & module have undergone some significant internal changes, for most setups no intervention is needed, though:
   - The option [`services.matrix-synapse.package`](#opt-services.matrix-synapse.package) is now read-only. For modifying the package, use an overlay which modifies `matrix-synapse-unwrapped` instead. More on that below.
   - The `enableSystemd` & `enableRedis` arguments have been removed and `matrix-synapse` has been renamed to `matrix-synapse-unwrapped`. Also, several optional dependencies (such as `psycopg2` or `authlib`) have been removed.
@@ -262,6 +288,18 @@
 
 - `fileSystems.<name>.autoResize` now uses `systemd-growfs` to resize the file system online in stage 2. This means that `f2fs` and `ext2` can no longer be auto resized, while `xfs` and `btrfs` now can be.
 
+- `fuse3` has been updated from 3.11.0 to 3.16.2; see [ChangeLog.rst](https://github.com/libfuse/libfuse/blob/fuse-3.16.2/ChangeLog.rst#libfuse-3162-2023-10-10) for an overview of the changes.
+
+  Unsupported mount options are no longer silently accepted [(since 3.15.0)](https://github.com/libfuse/libfuse/blob/fuse-3.16.2/ChangeLog.rst#libfuse-3150-2023-06-09). The [affected mount options](https://github.com/libfuse/libfuse/commit/dba6b3983af34f30de01cf532dff0b66f0ed6045) are: `atime`, `diratime`, `lazytime`, `nolazytime`, `relatime`, `norelatime`, `strictatime`.
+
+  For example,
+
+  ```bash
+  $ sshfs 127.0.0.1:/home/test/testdir /home/test/sshfs_mnt -o atime`
+  ```
+
+  would previously terminate successfully with the mount point established, now it outputs the error message ``fuse: unknown option(s): `-o atime'`` and terminates with exit status 1.
+
 - `nixos-rebuild {switch,boot,test,dry-activate}` now runs the system activation inside `systemd-run`, creating an ephemeral systemd service and protecting the system switch against issues like network disconnections during remote (e.g. SSH) sessions. This has the side effect of running the switch in an isolated environment, that could possible break post-switch scripts that depends on things like environment variables being set. If you want to opt-out from this behavior for now, you may set the `NIXOS_SWITCH_USE_DIRTY_ENV` environment variable before running `nixos-rebuild`. However, keep in mind that this option will be removed in the future.
 
 - The `services.vaultwarden.config` option default value was changed to make Vaultwarden only listen on localhost, following the [secure defaults for most NixOS services](https://github.com/NixOS/nixpkgs/issues/100192).
@@ -282,12 +320,21 @@
 
 - The binary of the package `cloud-sql-proxy` has changed from `cloud_sql_proxy` to `cloud-sql-proxy`.
 
+- The module `services.apache-kafka` was largely rewritten and has certain breaking changes. To be precise, this means that the following things have changed:
+
+  - Most settings have been migrated to [services.apache-kafka.settings](#opt-services.apache-kafka.settings).
+    - Care must be taken when adapting an existing cluster to these changes, see [](#module-services-apache-kafka-migrating-to-settings).
+  - By virtue of being less opinionated, it is now possible to use the module to run Apache Kafka in KRaft mode instead of Zookeeper mode.
+    - [A few options](#module-services-apache-kafka-kraft) have been added to assist in this mode.
+
 - Garage has been upgraded to 0.9.x. `services.garage.package` now needs to be explicitly set, so version upgrades can be done in a controlled fashion. For this, we expose `garage_x_y` attributes which can be set here.
 
 - `voms` and `xrootd` now moves the `$out/etc` content to the `$etc` output instead of `$out/etc.orig`, when input argument `externalEtc` is not `null`.
 
 - The `woodpecker-*` CI packages have been updated to 1.0.0. This release is wildly incompatible with the 0.15.X versions that were previously packaged. Please read [upstream's documentation](https://woodpecker-ci.org/docs/next/migrations#100) to learn how to update your CI configurations.
 
+- Meilisearch was updated from 1.3.1 to 1.5.0. The update has breaking changes about backslashes and filtering. See the [release announcement](https://blog.meilisearch.com/v1-4-release/) for more information.
+
 - The Caddy module gained a new option named `services.caddy.enableReload` which is enabled by default. It allows reloading the service instead of restarting it, if only a config file has changed. This option must be disabled if you have turned off the [Caddy admin API](https://caddyserver.com/docs/caddyfile/options#admin). If you keep this option enabled, you should consider setting [`grace_period`](https://caddyserver.com/docs/caddyfile/options#grace-period) to a non-infinite value to prevent Caddy from delaying the reload indefinitely.
 
 - mdraid support is now optional. This reduces initramfs size and prevents the potentially undesired automatic detection and activation of software RAID pools. It is disabled by default in new configurations (determined by `stateVersion`), but the appropriate settings will be generated by `nixos-generate-config` when installing to a software RAID device, so the standard installation procedure should be unaffected. If you have custom configs relying on mdraid, ensure that you use `stateVersion` correctly or set `boot.swraid.enable` manually. On systems with an updated `stateVersion` we now also emit warnings if `mdadm.conf` does not contain the minimum required configuration necessary to run the dynamically enabled monitoring daemons.
@@ -298,13 +345,13 @@
 
 - The default `kops` version is now 1.28.0 and support for 1.25 and older has been dropped.
 
-- `pharo` has been updated to latest stable (PharoVM 10.0.5), which is compatible with the latest stable and oldstable images (Pharo 10 and 11). The VM in question is the 64bit Spur. The 32bit version has been dropped due to lack of maintenance. The Cog VM has been deleted because it is severily outdated. Finally, the `pharo-launcher` package has been deleted because it was not compatible with the newer VM, and due to lack of maintenance.
+- `pharo` has been updated to latest stable (PharoVM 10.0.8), which is compatible with the latest stable and oldstable images (Pharo 10 and 11). The VM in question is the 64bit Spur. The 32bit version has been dropped due to lack of maintenance. The Cog VM has been deleted because it is severily outdated. Finally, the `pharo-launcher` package has been deleted because it was not compatible with the newer VM, and due to lack of maintenance.
 
 - Emacs mainline version 29 was introduced. This new version includes many major additions, most notably `tree-sitter` support (enabled by default) and the pgtk variant (useful for Wayland users), which is available under the attribute `emacs29-pgtk`.
 
 - Emacs macport version 29 was introduced.
 
-- The option `services.networking.networkmanager.enableFccUnlock` was removed in favor of `networking.networkmanager.fccUnlockScripts`, which allows specifying unlock scripts explicitly. The previous option simply did enable all unlock scripts bundled with ModemManager, which is risky, and didn't allow using vendor-provided unlock scripts at all.
+- The option `services.networking.networkmanager.enableFccUnlock` was removed in favor of `networking.networkmanager.fccUnlockScripts`, which allows specifying unlock scripts explicitly. The previous option enabled all unlock scripts bundled with ModemManager, which is risky, and didn't allow using vendor-provided unlock scripts at all.
 
 - The `html-proofer` package has been updated from major version 3 to major version 5, which includes [breaking changes](https://github.com/gjtorikian/html-proofer/blob/v5.0.8/UPGRADING.md).
 
@@ -319,6 +366,8 @@
 
 - Package `pash` was removed due to being archived upstream. Use `powershell` as an alternative.
 
+- The option `services.plausible.releaseCookiePath` has been removed: Plausible does not use any distributed Erlang features, and does not plan to (see [discussion](https://github.com/NixOS/nixpkgs/pull/130297#issuecomment-1805851333)), so NixOS now disables them, and the Erlang cookie becomes unnecessary. You may delete the file that `releaseCookiePath` was set to.
+
 - `security.sudo.extraRules` now includes `root`'s default rule, with ordering
   priority 400. This is functionally identical for users not specifying rule
   order, or relying on `mkBefore` and `mkAfter`, but may impact users calling
@@ -328,11 +377,6 @@
 
 - `networking.networkmanager.firewallBackend` was removed as NixOS is now using iptables-nftables-compat even when using iptables, therefore Networkmanager now uses the nftables backend unconditionally.
 
-- [`lib.lists.foldl'`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.lists.foldl-prime) now always evaluates the initial accumulator argument first.
-  If you depend on the lazier behavior, consider using [`lib.lists.foldl`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.lists.foldl) or [`builtins.foldl'`](https://nixos.org/manual/nix/stable/language/builtins.html#builtins-foldl') instead.
-
-- [`lib.attrsets.foldlAttrs`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.attrsets.foldlAttrs) now always evaluates the initial accumulator argument first.
-
 - `rome` was removed because it is no longer maintained and is succeeded by `biome`.
 
 - The `prometheus-knot-exporter` was migrated to a version maintained by CZ.NIC. Various metric names have changed, so checking existing rules is recommended.
@@ -361,6 +405,12 @@
 
 - The `junicode` font package has been updated to [major version 2](https://github.com/psb1558/Junicode-font/releases/tag/v2.001), which is now a font family. In particular, plain `Junicode.ttf` no longer exists. In addition, TrueType font files are now placed in `font/truetype` instead of `font/junicode-ttf`; this change does not affect use via `fonts.packages` NixOS option.
 
+- The `prayer` package as well as `services.prayer` have been removed because it's been unmaintained for several years and the author's website has vanished.
+
+- The `chrony` NixOS module now tracks the Real-Time Clock drift from the System Clock with `rtcfile` and automatically adjusts it with `rtcautotrim` when it exceeds the maximum error specified in `services.chrony.autotrimThreshold` (default 30 seconds). If you enabled `rtcsync` in `extraConfig`, you should remove RTC related options from `extraConfig`. If you do not want chrony configured to keep the RTC in check, you can set `services.chrony.enableRTCTrimming = false;`
+
+- `trilium-desktop` and `trilium-server` have been updated to [v0.61](https://github.com/zadam/trilium/releases/tag/v0.61.12). For existing installations, upgrading to this version is supported only after running v0.60.X at least once. If you are still on an older version, make sure to update to v0.60 (available in NixOS 23.05) first and only then to v0.61 (available in NixOS 23.11).
+
 ## Other Notable Changes {#sec-release-23.11-notable-changes}
 
 - A new option `system.switch.enable` was added. By default, this is option is
@@ -394,11 +444,17 @@
 
 - `zfs` was updated from 2.1.x to 2.2.0, [enabling newer kernel support and adding new features](https://github.com/openzfs/zfs/releases/tag/zfs-2.2.0).
 
+- The iptables firewall module now installs the `nixos-firewall-tool` which allows the user to easily temporarily open ports through the firewall.
+
 - Elixir now defaults to version
   [v1.15](https://elixir-lang.org/blog/2023/06/19/elixir-v1-15-0-released/).
 
 - A new option was added to the virtualisation module that enables specifying explicitly named network interfaces in QEMU VMs. The existing `virtualisation.vlans` is still supported for cases where the name of the network interface is irrelevant.
 
+- Apptainer/Singularity now defaults to using `"$out/var/lib"` for the `LOCALSTATEDIR` configuration option instead of the top-level `"/var/lib"`. This change impacts the `SESSIONDIR` (container-run-time mount point) configuration, which is set to `$LOCALSTATEDIR/<apptainer or singularity>/mnt/session`. This detaches the packages from the top-level directory, rendering the NixOS module optional.
+
+  The default behavior of the NixOS module `programs.singularity` stays unchanged. We add a new option `programs.singularity.enableExternalSysConfDir` (default to `true`) to specify whether to set the top-level `"/var/lib"` as `LOCALSTATEDIR` or not.
+
 - DocBook option documentation is no longer supported, all module documentation now uses markdown.
 
 - `services.outline` can now be configured to use local filesystem storage instead of S3 storage using [services.outline.storage.storageType](#opt-services.outline.storage.storageType).
@@ -513,8 +569,20 @@ The module update takes care of the new config syntax and the data itself (user
 
 - `services.bitcoind` now properly respects the `enable` option.
 
+- The Home Assistant module now offers support for installing custom components and lovelace modules. Available at [`services.home-assistant.customComponents`](#opt-services.home-assistant.customComponents) and [`services.home-assistant.customLovelaceModules`](#opt-services.home-assistant.customLovelaceModules).
+
+- The argument `vendorSha256` of `buildGoModule` is deprecated. Use `vendorHash` instead. ([\#259999](https://github.com/NixOS/nixpkgs/pull/259999))
+
+- TeX Live environments can now be built with the new `texlive.withPackages`. The procedure for creating custom TeX packages has been changed, see the [Nixpkgs manual](https://nixos.org/manual/nixpkgs/stable/#sec-language-texlive-custom-packages) for more details.
+
+- In `wxGTK32`, the webkit module `wxWebView` has been enabled on all builds; prior releases only enabled this on Darwin.
+
 ## Nixpkgs internals {#sec-release-23.11-nixpkgs-internals}
 
+- Node.js v14, v16 has been removed as they were end of life. Any dependent packages that contributors were not able to reasonably upgrade were dropped after a month of notice to their maintainers, were **removed**.
+  - This includes VSCode Server.
+  - This includes Kibana 7 as the ELK stack is unmaintained in nixpkgs and is marked for slow removal.
+
 - The use of `sourceRoot = "source";`, `sourceRoot = "source/subdir";`, and similar lines in package derivations using the default `unpackPhase` is deprecated as it requires `unpackPhase` to always produce a directory named "source". Use `sourceRoot = src.name`, `sourceRoot = "${src.name}/subdir";`, or `setSourceRoot = "sourceRoot=$(echo */subdir)";` or similar instead.
 
 - The `django` alias in the python package set was upgraded to Django 4.x.
@@ -556,3 +624,105 @@ The module update takes care of the new config syntax and the data itself (user
 - `teleport` has been upgraded from major version 12 to major version 14. Please see upstream [upgrade instructions](https://goteleport.com/docs/management/operations/upgrading/) and release notes for versions [13](https://goteleport.com/docs/changelog/#1300-050823) and [14](https://goteleport.com/docs/changelog/#1400-092023). Note that Teleport does not officially support upgrades across more than one major version at a time. If you're running Teleport server components, it is recommended to first upgrade to an intermediate 13.x version by setting `services.teleport.package = pkgs.teleport_13`. Afterwards, this option can be removed to upgrade to the default version (14).
 
 - The Linux kernel module `msr` (see [`msr(4)`](https://man7.org/linux/man-pages/man4/msr.4.html)), which provides an interface to read and write the model-specific registers (MSRs) of an x86 CPU, can now be configured via `hardware.cpu.x86.msr`.
+
+- Docker now defaults to 24, as 20.10 is stopping to receive security updates and bug fixes after [December 10, 2023](https://github.com/moby/moby/discussions/45104).
+
+- There is a new NixOS option when writing NixOS tests `testing.initrdBackdoor`, that enables `backdoor.service` in initrd. Requires `boot.initrd.systemd.enable` to be enabled. Boot will pause in stage 1 at `initrd.target`, and will listen for commands from the `Machine` python interface, just like stage 2 normally does. This enables commands to be sent to test and debug stage 1. Use `machine.switch_root()` to leave stage 1 and proceed to stage 2.
+
+## Nixpkgs library changes {#sec-release-23.11-lib}
+
+### Breaking changes {#sec-release-23.11-lib-breaking}
+
+- [`lib.lists.foldl'`](https://nixos.org/manual/nixpkgs/unstable#function-library-lib.lists.foldl-prime)
+  now always evaluates the initial accumulator argument first.
+  If you depend on the lazier behavior, consider using [`lib.lists.foldl`](https://nixos.org/manual/nixpkgs/unstable#function-library-lib.lists.foldl)
+  or [`builtins.foldl'`](https://nixos.org/manual/nix/stable/language/builtins.html#builtins-foldl') instead.
+- [`lib.attrsets.foldlAttrs`](https://nixos.org/manual/nixpkgs/unstable#function-library-lib.attrsets.foldlAttrs)
+  now always evaluates the initial accumulator argument first.
+- Now that the internal NixOS transition to Markdown documentation is complete,
+  `lib.options.literalDocBook` has been removed after deprecation in 22.11.
+- `lib.types.string` is now fully deprecated and gives a warning when used.
+
+### Additions and improvements {#sec-release-23.11-lib-additions-improvements}
+
+- [`lib.fileset`](https://nixos.org/manual/nixpkgs/unstable#sec-functions-library-fileset):
+  A new sub-library to select local files to use for sources,
+  designed to be easy and safe to use.
+
+  This aims to be a replacement for `lib.sources`-based filtering.
+  To learn more about it, see [the tutorial](https://nix.dev/tutorials/file-sets).
+
+- [`lib.gvariant`](https://nixos.org/manual/nixpkgs/unstable#sec-functions-library-gvariant):
+  A partial and basic implementation of GVariant formatted strings.
+  See [GVariant Format Strings](https://docs.gtk.org/glib/gvariant-format-strings.html) for details.
+
+  :::{.warning}
+  This API is not considered fully stable and it might therefore
+  change in backwards incompatible ways without prior notice.
+  :::
+
+- [`lib.asserts`](https://nixos.org/manual/nixpkgs/unstable#sec-functions-library-asserts): New function:
+  [`assertEachOneOf`](https://nixos.org/manual/nixpkgs/unstable#function-library-lib.asserts.assertEachOneOf).
+- [`lib.attrsets`](https://nixos.org/manual/nixpkgs/unstable#sec-functions-library-attrsets): New function:
+  [`attrsToList`](https://nixos.org/manual/nixpkgs/unstable#function-library-lib.attrsets.attrsToList).
+- [`lib.customisation`](https://nixos.org/manual/nixpkgs/unstable#sec-functions-library-customisation): New function:
+  [`makeScopeWithSplicing'`](https://nixos.org/manual/nixpkgs/unstable#function-library-lib.customisation.makeScopeWithSplicing-prime).
+- [`lib.fixedPoints`](https://nixos.org/manual/nixpkgs/unstable#sec-functions-library-fixedPoints): Documentation improvements for
+  [`lib.fixedPoints.fix`](https://nixos.org/manual/nixpkgs/unstable#function-library-lib.fixedPoints.fix).
+- `lib.generators`: New functions:
+  [`mkDconfKeyValue`](https://nixos.org/manual/nixpkgs/unstable#function-library-lib.generators.mkDconfKeyValue),
+  [`toDconfINI`](https://nixos.org/manual/nixpkgs/unstable#function-library-lib.generators.toDconfINI).
+
+  `lib.generators.toKeyValue` now supports the `indent` attribute in its first argument.
+- [`lib.lists`](https://nixos.org/manual/nixpkgs/unstable#sec-functions-library-lists): New functions:
+  [`findFirstIndex`](https://nixos.org/manual/nixpkgs/unstable#function-library-lib.lists.findFirstIndex),
+  [`hasPrefix`](https://nixos.org/manual/nixpkgs/unstable#function-library-lib.lists.hasPrefix),
+  [`removePrefix`](https://nixos.org/manual/nixpkgs/unstable#function-library-lib.lists.removePrefix),
+  [`commonPrefix`](https://nixos.org/manual/nixpkgs/unstable#function-library-lib.lists.commonPrefix),
+  [`allUnique`](https://nixos.org/manual/nixpkgs/unstable#function-library-lib.lists.allUnique).
+
+  Documentation improvements for
+  [`lib.lists.foldl'`](https://nixos.org/manual/nixpkgs/unstable#function-library-lib.lists.foldl-prime).
+- [`lib.meta`](https://nixos.org/manual/nixpkgs/unstable#sec-functions-library-meta): Documentation of functions now gets rendered
+- [`lib.path`](https://nixos.org/manual/nixpkgs/unstable#sec-functions-library-path): New functions:
+  [`hasPrefix`](https://nixos.org/manual/nixpkgs/unstable#function-library-lib.path.hasPrefix),
+  [`removePrefix`](https://nixos.org/manual/nixpkgs/unstable#function-library-lib.path.removePrefix),
+  [`splitRoot`](https://nixos.org/manual/nixpkgs/unstable#function-library-lib.path.splitRoot),
+  [`subpath.components`](https://nixos.org/manual/nixpkgs/unstable#function-library-lib.path.subpath.components).
+- [`lib.strings`](https://nixos.org/manual/nixpkgs/unstable#sec-functions-library-strings): New functions:
+  [`replicate`](https://nixos.org/manual/nixpkgs/unstable#function-library-lib.strings.replicate),
+  [`cmakeOptionType`](https://nixos.org/manual/nixpkgs/unstable#function-library-lib.strings.cmakeOptionType),
+  [`cmakeBool`](https://nixos.org/manual/nixpkgs/unstable#function-library-lib.strings.cmakeBool),
+  [`cmakeFeature`](https://nixos.org/manual/nixpkgs/unstable#function-library-lib.strings.cmakeFeature).
+- [`lib.trivial`](https://nixos.org/manual/nixpkgs/unstable#sec-functions-library-trivial): New function:
+  [`mirrorFunctionArgs`](https://nixos.org/manual/nixpkgs/unstable#function-library-lib.trivial.mirrorFunctionArgs).
+- `lib.systems`: New function:
+  [`equals`](https://nixos.org/manual/nixpkgs/unstable#function-library-lib.systems.equals).
+- [`lib.options`](https://nixos.org/manual/nixpkgs/unstable#sec-functions-library-options): Improved documentation for
+  [`mkPackageOption`](https://nixos.org/manual/nixpkgs/unstable#function-library-lib.options.mkPackageOption).
+
+  [`mkPackageOption`](https://nixos.org/manual/nixpkgs/unstable#function-library-lib.options.mkPackageOption).
+  now also supports the `pkgsText` attribute.
+
+Module system:
+- Options in the `options` module argument now have the `declarationPositions` attribute
+  containing the position where the option was declared:
+  ```
+  $ nix repl -f '<nixpkgs/nixos>'
+  [...]
+  nix-repl> :p options.environment.systemPackages.declarationPositions
+  [ {
+    column = 7;
+    file = "/nix/store/vm9zf9wvfd628cchj0hdij1g4hzjrcz9-source/nixos/modules/config/system-path.nix";
+    line = 62;
+  } ]
+  ```
+  Not to be confused with `definitionsWithLocations`, which is the same but for option _definitions_.
+- Improved error message for option declarations missing `mkOption`
+
+### Deprecations {#sec-release-23.11-lib-deprecations}
+
+- `lib.meta.getExe pkg` (also available as `lib.getExe`) now gives a warning if `pkg.meta.mainProgram` is not set,
+  but it continues to default to the derivation name.
+  Nixpkgs accepts PRs that set `meta.mainProgram` on packages where it makes sense.
+  Use `lib.getExe' pkg "some-command"` to avoid the warning and/or select a different executable.
diff --git a/nixos/doc/manual/release-notes/rl-2405.section.md b/nixos/doc/manual/release-notes/rl-2405.section.md
new file mode 100644
index 0000000000000..e2f99c20cfc8b
--- /dev/null
+++ b/nixos/doc/manual/release-notes/rl-2405.section.md
@@ -0,0 +1,29 @@
+# Release 24.05 (“Uakari”, 2024.05/??) {#sec-release-24.05}
+
+Support is planned until the end of December 2024, handing over to 24.11.
+
+## Highlights {#sec-release-24.05-highlights}
+
+In addition to numerous new and upgraded packages, this release has the following highlights:
+
+<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
+
+- Create the first release note entry in this section!
+
+## New Services {#sec-release-24.05-new-services}
+
+<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
+
+- Create the first release note entry in this section!
+
+## Backward Incompatibilities {#sec-release-24.05-incompatibilities}
+
+<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
+
+- Create the first release note entry in this section!
+
+## Other Notable Changes {#sec-release-24.05-notable-changes}
+
+<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
+
+- Create the first release note entry in this section!
diff --git a/nixos/lib/systemd-lib.nix b/nixos/lib/systemd-lib.nix
index 7b600464bb415..820ccbcbf72a1 100644
--- a/nixos/lib/systemd-lib.nix
+++ b/nixos/lib/systemd-lib.nix
@@ -376,24 +376,23 @@ in rec {
 
   serviceToUnit = name: def:
     { inherit (def) aliases wantedBy requiredBy enable overrideStrategy;
-      text = commonUnitText def +
-        ''
-          [Service]
-          ${let env = cfg.globalEnvironment // def.environment;
-            in concatMapStrings (n:
-              let s = optionalString (env.${n} != null)
-                "Environment=${builtins.toJSON "${n}=${env.${n}}"}\n";
-              # systemd max line length is now 1MiB
-              # https://github.com/systemd/systemd/commit/e6dde451a51dc5aaa7f4d98d39b8fe735f73d2af
-              in if stringLength s >= 1048576 then throw "The value of the environment variable ‘${n}’ in systemd service ‘${name}.service’ is too long." else s) (attrNames env)}
-          ${if def ? reloadIfChanged && def.reloadIfChanged then ''
-            X-ReloadIfChanged=true
-          '' else if (def ? restartIfChanged && !def.restartIfChanged) then ''
-            X-RestartIfChanged=false
-          '' else ""}
-          ${optionalString (def ? stopIfChanged && !def.stopIfChanged) "X-StopIfChanged=false"}
-          ${attrsToSection def.serviceConfig}
-        '';
+      text = commonUnitText def + ''
+        [Service]
+      '' + (let env = cfg.globalEnvironment // def.environment;
+        in concatMapStrings (n:
+          let s = optionalString (env.${n} != null)
+            "Environment=${builtins.toJSON "${n}=${env.${n}}"}\n";
+          # systemd max line length is now 1MiB
+          # https://github.com/systemd/systemd/commit/e6dde451a51dc5aaa7f4d98d39b8fe735f73d2af
+          in if stringLength s >= 1048576 then throw "The value of the environment variable ‘${n}’ in systemd service ‘${name}.service’ is too long." else s) (attrNames env))
+      + (if def ? reloadIfChanged && def.reloadIfChanged then ''
+        X-ReloadIfChanged=true
+      '' else if (def ? restartIfChanged && !def.restartIfChanged) then ''
+        X-RestartIfChanged=false
+      '' else "")
+       + optionalString (def ? stopIfChanged && !def.stopIfChanged) ''
+         X-StopIfChanged=false
+      '' + attrsToSection def.serviceConfig;
     };
 
   socketToUnit = name: def:
diff --git a/nixos/lib/systemd-network-units.nix b/nixos/lib/systemd-network-units.nix
index 8bda1a8bfdcf6..1d5f823f3678d 100644
--- a/nixos/lib/systemd-network-units.nix
+++ b/nixos/lib/systemd-network-units.nix
@@ -23,6 +23,12 @@ in {
     '' + optionalString (def.vlanConfig != { }) ''
       [VLAN]
       ${attrsToSection def.vlanConfig}
+    '' + optionalString (def.ipvlanConfig != { }) ''
+      [IPVLAN]
+      ${attrsToSection def.ipvlanConfig}
+    '' + optionalString (def.ipvtapConfig != { }) ''
+      [IPVTAP]
+      ${attrsToSection def.ipvtapConfig}
     '' + optionalString (def.macvlanConfig != { }) ''
       [MACVLAN]
       ${attrsToSection def.macvlanConfig}
diff --git a/nixos/lib/test-driver/test_driver/machine.py b/nixos/lib/test-driver/test_driver/machine.py
index 529de41d892a9..f430321bb6076 100644
--- a/nixos/lib/test-driver/test_driver/machine.py
+++ b/nixos/lib/test-driver/test_driver/machine.py
@@ -1278,3 +1278,19 @@ class Machine:
     def run_callbacks(self) -> None:
         for callback in self.callbacks:
             callback()
+
+    def switch_root(self) -> None:
+        """
+        Transition from stage 1 to stage 2. This requires the
+        machine to be configured with `testing.initrdBackdoor = true`
+        and `boot.initrd.systemd.enable = true`.
+        """
+        self.wait_for_unit("initrd.target")
+        self.execute(
+            "systemctl isolate --no-block initrd-switch-root.target 2>/dev/null >/dev/null",
+            check_return=False,
+            check_output=False,
+        )
+        self.wait_for_console_text(r"systemd\[1\]:.*Switching root\.")
+        self.connected = False
+        self.connect()
diff --git a/nixos/maintainers/scripts/lxd/lxd-container-image-inner.nix b/nixos/maintainers/scripts/lxd/lxd-container-image-inner.nix
index 62a6e1f9aa3a8..ef00c6f86cbd6 100644
--- a/nixos/maintainers/scripts/lxd/lxd-container-image-inner.nix
+++ b/nixos/maintainers/scripts/lxd/lxd-container-image-inner.nix
@@ -8,7 +8,7 @@
   imports =
     [
       # Include the default lxd configuration.
-      "${modulesPath}/modules/virtualisation/lxc-container.nix"
+      "${modulesPath}/virtualisation/lxc-container.nix"
       # Include the container-specific autogenerated configuration.
       ./lxd.nix
     ];
diff --git a/nixos/modules/config/stevenblack.nix b/nixos/modules/config/stevenblack.nix
index 30ef7ff259f09..7e6235169847a 100644
--- a/nixos/modules/config/stevenblack.nix
+++ b/nixos/modules/config/stevenblack.nix
@@ -30,5 +30,5 @@ in
       ++ optionals (activatedHosts == [ ]) [ "${pkgs.stevenblack-blocklist}/hosts" ];
   };
 
-  meta.maintainers = [ maintainers.fortuneteller2k maintainers.artturin ];
+  meta.maintainers = [ maintainers.moni maintainers.artturin ];
 }
diff --git a/nixos/modules/config/terminfo.nix b/nixos/modules/config/terminfo.nix
index d1dbc4e0d0598..ebd1aaea8f04a 100644
--- a/nixos/modules/config/terminfo.nix
+++ b/nixos/modules/config/terminfo.nix
@@ -16,10 +16,7 @@ with lib;
     };
 
     security.sudo.keepTerminfo = mkOption {
-      default = config.security.sudo.package.pname != "sudo-rs";
-      defaultText = literalMD ''
-        `true` unless using `sudo-rs`
-      '';
+      default = true;
       type = types.bool;
       description = lib.mdDoc ''
         Whether to preserve the `TERMINFO` and `TERMINFO_DIRS`
diff --git a/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix
index b4251214876ef..39aac9fb821bd 100644
--- a/nixos/modules/config/users-groups.nix
+++ b/nixos/modules/config/users-groups.nix
@@ -153,7 +153,7 @@ let
           {file}`pam_mount.conf.xml`.
           Useful attributes might include `path`,
           `options`, `fstype`, and `server`.
-          See <http://pam-mount.sourceforge.net/pam_mount.conf.5.html>
+          See <https://pam-mount.sourceforge.net/pam_mount.conf.5.html>
           for more information.
         '';
       };
diff --git a/nixos/modules/config/xdg/portal.nix b/nixos/modules/config/xdg/portal.nix
index e19e5cf28b3bc..07d4fa76c2e8b 100644
--- a/nixos/modules/config/xdg/portal.nix
+++ b/nixos/modules/config/xdg/portal.nix
@@ -8,6 +8,10 @@ let
     mkRenamedOptionModule
     teams
     types;
+
+  associationOptions = with types; attrsOf (
+    coercedTo (either (listOf str) str) (x: lib.concatStringsSep ";" (lib.toList x)) str
+  );
 in
 
 {
@@ -72,20 +76,76 @@ in
         See [#160923](https://github.com/NixOS/nixpkgs/issues/160923) for more info.
       '';
     };
+
+    config = mkOption {
+      type = types.attrsOf associationOptions;
+      default = { };
+      example = {
+        x-cinnamon = {
+          default = [ "xapp" "gtk" ];
+        };
+        pantheon = {
+          default = [ "pantheon" "gtk" ];
+          "org.freedesktop.impl.portal.Secret" = [ "gnome-keyring" ];
+        };
+        common = {
+          default = [ "gtk" ];
+        };
+      };
+      description = lib.mdDoc ''
+        Sets which portal backend should be used to provide the implementation
+        for the requested interface. For details check {manpage}`portals.conf(5)`.
+
+        Configs will be linked to `/etx/xdg/xdg-desktop-portal/` with the name `$desktop-portals.conf`
+        for `xdg.portal.config.$desktop` and `portals.conf` for `xdg.portal.config.common`
+        as an exception.
+      '';
+    };
+
+    configPackages = mkOption {
+      type = types.listOf types.package;
+      default = [ ];
+      example = lib.literalExpression "[ pkgs.gnome.gnome-session ]";
+      description = lib.mdDoc ''
+        List of packages that provide XDG desktop portal configuration, usually in
+        the form of `share/xdg-desktop-portal/$desktop-portals.conf`.
+
+        Note that configs in `xdg.portal.config` will be preferred if set.
+      '';
+    };
   };
 
   config =
     let
       cfg = config.xdg.portal;
       packages = [ pkgs.xdg-desktop-portal ] ++ cfg.extraPortals;
+      configPackages = cfg.configPackages;
+
       joinedPortals = pkgs.buildEnv {
         name = "xdg-portals";
         paths = packages;
         pathsToLink = [ "/share/xdg-desktop-portal/portals" "/share/applications" ];
       };
 
+      joinedPortalConfigs = pkgs.buildEnv {
+        name = "xdg-portal-configs";
+        paths = configPackages;
+        pathsToLink = [ "/share/xdg-desktop-portal" ];
+      };
     in
     mkIf cfg.enable {
+      warnings = lib.optional (cfg.configPackages == [ ] && cfg.config == { }) ''
+        xdg-desktop-portal 1.17 reworked how portal implementations are loaded, you
+        should either set `xdg.portal.config` or `xdg.portal.configPackages`
+        to specify which portal backend to use for the requested interface.
+
+        https://github.com/flatpak/xdg-desktop-portal/blob/1.18.1/doc/portals.conf.rst.in
+
+        If you simply want to keep the behaviour in < 1.17, which uses the first
+        portal implementation found in lexicographical order, use the following:
+
+        xdg.portal.config.common.default = "*";
+      '';
 
       assertions = [
         {
@@ -108,7 +168,14 @@ in
           GTK_USE_PORTAL = mkIf cfg.gtkUsePortal "1";
           NIXOS_XDG_OPEN_USE_PORTAL = mkIf cfg.xdgOpenUsePortal "1";
           XDG_DESKTOP_PORTAL_DIR = "${joinedPortals}/share/xdg-desktop-portal/portals";
+          NIXOS_XDG_DESKTOP_PORTAL_CONFIG_DIR = mkIf (cfg.configPackages != [ ]) "${joinedPortalConfigs}/share/xdg-desktop-portal";
         };
+
+        etc = lib.concatMapAttrs
+          (desktop: conf: lib.optionalAttrs (conf != { }) {
+            "xdg/xdg-desktop-portal/${lib.optionalString (desktop != "common") "${desktop}-"}portals.conf".text =
+              lib.generators.toINI { } { preferred = conf; };
+          }) cfg.config;
       };
     };
 }
diff --git a/nixos/modules/hardware/video/nvidia.nix b/nixos/modules/hardware/video/nvidia.nix
index c36775dd24bba..c76883b656d40 100644
--- a/nixos/modules/hardware/video/nvidia.nix
+++ b/nixos/modules/hardware/video/nvidia.nix
@@ -261,7 +261,16 @@ in {
         ];
         boot = {
           blacklistedKernelModules = ["nouveau" "nvidiafb"];
-          kernelModules = [ "nvidia-uvm" ];
+
+          # Don't add `nvidia-uvm` to `kernelModules`, because we want
+          # `nvidia-uvm` be loaded only after `udev` rules for `nvidia` kernel
+          # module are applied.
+          #
+          # Instead, we use `softdep` to lazily load `nvidia-uvm` kernel module
+          # after `nvidia` kernel module is loaded and `udev` rules are applied.
+          extraModprobeConfig = ''
+            softdep nvidia post: nvidia-uvm
+          '';
         };
         systemd.tmpfiles.rules =
           lib.optional config.virtualisation.docker.enableNvidia
diff --git a/nixos/modules/installer/tools/nix-fallback-paths.nix b/nixos/modules/installer/tools/nix-fallback-paths.nix
index 10c37a46fdac1..e4241e9654036 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/3wqasl97rjiza3vd7fxjnvli2w9l30mk-nix-2.17.0";
-  i686-linux = "/nix/store/z360xswxfx55pmm1fng3hw748rbs0kkj-nix-2.17.0";
-  aarch64-linux = "/nix/store/9670sxa916xmv8n1kqs7cdvmnsrhrdjv-nix-2.17.0";
-  x86_64-darwin = "/nix/store/2rdbky9j8hc3mbgl6pnda4hkjllyfwnn-nix-2.17.0";
-  aarch64-darwin = "/nix/store/jl9qma14fb4zk9lq1k0syw2k9qm2gqjw-nix-2.17.0";
+  x86_64-linux = "/nix/store/azvn85cras6xv4z5j85fiy406f24r1q0-nix-2.18.1";
+  i686-linux = "/nix/store/9bnwy7f9h0kzdzmcnjjsjg0aak5waj40-nix-2.18.1";
+  aarch64-linux = "/nix/store/hh65xwqm9s040s3cgn9vzcmrxj0sf5ij-nix-2.18.1";
+  x86_64-darwin = "/nix/store/6zi5fqzn9n17wrk8r41rhdw4j7jqqsi3-nix-2.18.1";
+  aarch64-darwin = "/nix/store/0pbq6wzr2f1jgpn5212knyxpwmkjgjah-nix-2.18.1";
 }
diff --git a/nixos/modules/installer/tools/nixos-generate-config.pl b/nixos/modules/installer/tools/nixos-generate-config.pl
index 85180bf2d1b4d..2f9edba4f0c9c 100644
--- a/nixos/modules/installer/tools/nixos-generate-config.pl
+++ b/nixos/modules/installer/tools/nixos-generate-config.pl
@@ -130,7 +130,7 @@ sub pciCheck {
     debug "\n";
 
     if (defined $module) {
-        # See the bottom of http://pciids.sourceforge.net/pci.ids for
+        # See the bottom of https://pciids.sourceforge.net/pci.ids for
         # device classes.
         if (# Mass-storage controller.  Definitely important.
             $class =~ /^0x01/ ||
@@ -257,6 +257,7 @@ foreach my $path (glob "/sys/class/{block,mmc_host}/*") {
 
 # Add bcache module, if needed.
 my @bcacheDevices = glob("/dev/bcache*");
+@bcacheDevices = grep(!qr#dev/bcachefs.*#, @bcacheDevices);
 if (scalar @bcacheDevices > 0) {
     push @initrdAvailableKernelModules, "bcache";
 }
@@ -467,6 +468,19 @@ EOF
     # boot.tmp.useTmpfs option in configuration.nix (managed declaratively).
     next if ($mountPoint eq "/tmp" && $fsType eq "tmpfs");
 
+    # This should work for single and multi-device systems.
+    # still needs subvolume support
+    if ($fsType eq "bcachefs") {
+        my ($status, @info) = runCommand("bcachefs fs usage $rootDir$mountPoint");
+        my $UUID = $info[0];
+
+        if ($status == 0 && $UUID =~ /^Filesystem:[ \t\n]*([0-9a-z-]+)/) {
+            $stableDevPath = "UUID=$1";
+        } else {
+            print STDERR "warning: can't find bcachefs mount UUID falling back to device-path";
+        }
+    }
+
     # Emit the filesystem.
     $fileSystems .= <<EOF;
   fileSystems.\"$mountPoint\" =
diff --git a/nixos/modules/installer/tools/nixos-option/default.nix b/nixos/modules/installer/tools/nixos-option/default.nix
deleted file mode 100644
index 061460f38a3b6..0000000000000
--- a/nixos/modules/installer/tools/nixos-option/default.nix
+++ /dev/null
@@ -1 +0,0 @@
-{ pkgs, ... }: pkgs.nixos-option
diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix
index 5b278b5e80625..18928a6bf21bb 100644
--- a/nixos/modules/misc/ids.nix
+++ b/nixos/modules/misc/ids.nix
@@ -86,7 +86,7 @@ in
       #rtkit = 45; # dynamically allocated 2021-09-03
       dovecot2 = 46;
       dovenull2 = 47;
-      prayer = 49;
+      # prayer = 49; # dropped in 23.11
       mpd = 50;
       clamav = 51;
       #fprot = 52; # unused
@@ -411,7 +411,7 @@ in
       #rtkit = 45; # unused
       dovecot2 = 46;
       dovenull2 = 47;
-      prayer = 49;
+      # prayer = 49; # dropped in 23.11
       mpd = 50;
       clamav = 51;
       #fprot = 52; # unused
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 6679e5bb7c653..2e2b94e5a97bc 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -474,6 +474,7 @@
   ./services/desktops/pipewire/pipewire.nix
   ./services/desktops/pipewire/wireplumber.nix
   ./services/desktops/profile-sync-daemon.nix
+  ./services/desktops/seatd.nix
   ./services/desktops/system-config-printer.nix
   ./services/desktops/system76-scheduler.nix
   ./services/desktops/telepathy.nix
@@ -799,6 +800,7 @@
   ./services/monitoring/munin.nix
   ./services/monitoring/nagios.nix
   ./services/monitoring/netdata.nix
+  ./services/monitoring/ocsinventory-agent.nix
   ./services/monitoring/opentelemetry-collector.nix
   ./services/monitoring/osquery.nix
   ./services/monitoring/parsedmarc.nix
@@ -880,6 +882,7 @@
   ./services/networking/bitlbee.nix
   ./services/networking/blockbook-frontend.nix
   ./services/networking/blocky.nix
+  ./services/networking/centrifugo.nix
   ./services/networking/cgit.nix
   ./services/networking/charybdis.nix
   ./services/networking/chisel-server.nix
@@ -1041,7 +1044,6 @@
   ./services/networking/powerdns.nix
   ./services/networking/pppd.nix
   ./services/networking/pptpd.nix
-  ./services/networking/prayer.nix
   ./services/networking/privoxy.nix
   ./services/networking/prosody.nix
   ./services/networking/quassel.nix
@@ -1148,7 +1150,6 @@
   ./services/search/elasticsearch-curator.nix
   ./services/search/elasticsearch.nix
   ./services/search/hound.nix
-  ./services/search/kibana.nix
   ./services/search/meilisearch.nix
   ./services/search/opensearch.nix
   ./services/search/qdrant.nix
@@ -1242,7 +1243,6 @@
   ./services/web-apps/changedetection-io.nix
   ./services/web-apps/chatgpt-retrieval-plugin.nix
   ./services/web-apps/cloudlog.nix
-  ./services/web-apps/code-server.nix
   ./services/web-apps/convos.nix
   ./services/web-apps/dex.nix
   ./services/web-apps/discourse.nix
@@ -1406,6 +1406,7 @@
   ./services/x11/xautolock.nix
   ./services/x11/xbanish.nix
   ./services/x11/xfs.nix
+  ./services/x11/xscreensaver.nix
   ./services/x11/xserver.nix
   ./system/activation/activatable-system.nix
   ./system/activation/activation-script.nix
diff --git a/nixos/modules/programs/ausweisapp.nix b/nixos/modules/programs/ausweisapp.nix
index ef1f059568c6a..91870df20246d 100644
--- a/nixos/modules/programs/ausweisapp.nix
+++ b/nixos/modules/programs/ausweisapp.nix
@@ -7,11 +7,11 @@ let
 in
 {
   options.programs.ausweisapp = {
-    enable = mkEnableOption (lib.mdDoc "AusweisApp2");
+    enable = mkEnableOption (lib.mdDoc "AusweisApp");
 
     openFirewall = mkOption {
       description = lib.mdDoc ''
-        Whether to open the required firewall ports for the Smartphone as Card Reader (SaC) functionality of AusweisApp2.
+        Whether to open the required firewall ports for the Smartphone as Card Reader (SaC) functionality of AusweisApp.
       '';
       default = false;
       type = lib.types.bool;
@@ -19,7 +19,7 @@ in
   };
 
   config = mkIf cfg.enable {
-    environment.systemPackages = with pkgs; [ AusweisApp2 ];
+    environment.systemPackages = with pkgs; [ ausweisapp ];
     networking.firewall.allowedUDPPorts = lib.optionals cfg.openFirewall [ 24727 ];
   };
 }
diff --git a/nixos/modules/programs/git.nix b/nixos/modules/programs/git.nix
index 4e271a8c134b2..710dee349d590 100644
--- a/nixos/modules/programs/git.nix
+++ b/nixos/modules/programs/git.nix
@@ -58,6 +58,10 @@ in
         '';
       };
 
+      prompt = {
+        enable = mkEnableOption "automatically sourcing git-prompt.sh. This does not change $PS1; it simply provides relevant utility functions";
+      };
+
       lfs = {
         enable = mkEnableOption (lib.mdDoc "git-lfs");
 
@@ -89,6 +93,11 @@ in
         };
       };
     })
+    (mkIf (cfg.enable && cfg.prompt.enable) {
+      environment.interactiveShellInit = ''
+        source ${cfg.package}/share/bash-completion/completions/git-prompt.sh
+      '';
+    })
   ];
 
   meta.maintainers = with maintainers; [ figsoda ];
diff --git a/nixos/modules/programs/hyprland.nix b/nixos/modules/programs/hyprland.nix
index 638dfb98e8ab0..7e5cae1d045ef 100644
--- a/nixos/modules/programs/hyprland.nix
+++ b/nixos/modules/programs/hyprland.nix
@@ -64,6 +64,7 @@ in
     xdg.portal = {
       enable = mkDefault true;
       extraPortals = [ finalPortalPackage ];
+      configPackages = mkDefault [ cfg.finalPackage ];
     };
   };
 
diff --git a/nixos/modules/programs/npm.nix b/nixos/modules/programs/npm.nix
index 48dc48e668f33..c41fea326149f 100644
--- a/nixos/modules/programs/npm.nix
+++ b/nixos/modules/programs/npm.nix
@@ -34,7 +34,7 @@ in
           prefix = ''${HOME}/.npm
           https-proxy=proxy.example.com
           init-license=MIT
-          init-author-url=http://npmjs.org
+          init-author-url=https://www.npmjs.com/
           color=true
         '';
       };
diff --git a/nixos/modules/programs/singularity.nix b/nixos/modules/programs/singularity.nix
index 05fdb4842c543..79695b29becae 100644
--- a/nixos/modules/programs/singularity.nix
+++ b/nixos/modules/programs/singularity.nix
@@ -45,6 +45,18 @@ in
         Use `lib.mkForce` to forcefully specify the overridden package.
       '';
     };
+    enableExternalLocalStateDir = mkOption {
+      type = types.bool;
+      default = true;
+      example = false;
+      description = mdDoc ''
+        Whether to use top-level directories as LOCALSTATEDIR
+        instead of the store path ones.
+        This affects the SESSIONDIR of Apptainer/Singularity.
+        If set to true, the SESSIONDIR will become
+        `/var/lib/''${projectName}/mnt/session`.
+      '';
+    };
     enableFakeroot = mkOption {
       type = types.bool;
       default = true;
@@ -65,7 +77,9 @@ in
 
   config = mkIf cfg.enable {
     programs.singularity.packageOverriden = (cfg.package.override (
-      optionalAttrs cfg.enableFakeroot {
+      optionalAttrs cfg.enableExternalLocalStateDir {
+        externalLocalStateDir = "/var/lib";
+      } // optionalAttrs cfg.enableFakeroot {
         newuidmapPath = "/run/wrappers/bin/newuidmap";
         newgidmapPath = "/run/wrappers/bin/newgidmap";
       } // optionalAttrs cfg.enableSuid {
@@ -80,12 +94,8 @@ in
       group = "root";
       source = "${cfg.packageOverriden}/libexec/${cfg.packageOverriden.projectName}/bin/starter-suid.orig";
     };
-    systemd.tmpfiles.rules = [
+    systemd.tmpfiles.rules = mkIf cfg.enableExternalLocalStateDir [
       "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/wayland/sway.nix b/nixos/modules/programs/wayland/sway.nix
index 698d9c2b46c46..f96c833856dbb 100644
--- a/nixos/modules/programs/wayland/sway.nix
+++ b/nixos/modules/programs/wayland/sway.nix
@@ -149,6 +149,8 @@ in {
             "sway/config".source = mkOptionDefault "${cfg.package}/etc/sway/config";
           };
         };
+        # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1050913
+        xdg.portal.config.sway.default = mkDefault [ "wlr" "gtk" ];
         # To make a Sway session available if a display manager like SDDM is enabled:
         services.xserver.displayManager.sessionPackages = optionals (cfg.package != null) [ cfg.package ]; }
       (import ./wayland-session.nix { inherit lib pkgs; })
diff --git a/nixos/modules/programs/wayland/wayfire.nix b/nixos/modules/programs/wayland/wayfire.nix
index 9ea2010cf59c8..899f244f68092 100644
--- a/nixos/modules/programs/wayland/wayfire.nix
+++ b/nixos/modules/programs/wayland/wayfire.nix
@@ -43,6 +43,8 @@ in
     xdg.portal = {
       enable = lib.mkDefault true;
       wlr.enable = lib.mkDefault true;
+      # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1050914
+      config.wayfire.default = lib.mkDefault [ "wlr" "gtk" ];
     };
   };
 }
diff --git a/nixos/modules/programs/zsh/oh-my-zsh.md b/nixos/modules/programs/zsh/oh-my-zsh.md
index 73d425244ce79..6a310006edbfc 100644
--- a/nixos/modules/programs/zsh/oh-my-zsh.md
+++ b/nixos/modules/programs/zsh/oh-my-zsh.md
@@ -78,7 +78,7 @@ If third-party customizations (e.g. new themes) are supposed to be added to
 
   - Completion scripts are supposed to be stored at
     `$out/share/zsh/site-functions`. This directory is part of the
-    [`fpath`](http://zsh.sourceforge.net/Doc/Release/Functions.html)
+    [`fpath`](https://zsh.sourceforge.io/Doc/Release/Functions.html)
     and the package should be compatible with pure `ZSH`
     setups. The module will automatically link the contents of
     `site-functions` to completions directory in the proper
diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix
index 0fbb2351f9863..3fab863adb7f6 100644
--- a/nixos/modules/rename.nix
+++ b/nixos/modules/rename.nix
@@ -111,6 +111,7 @@ in
     (mkRemovedOptionModule [ "services" "riak" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "cryptpad" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "rtsp-simple-server" ] "Package has been completely rebranded by upstream as mediamtx, and thus the service and the package were renamed in NixOS as well.")
+    (mkRemovedOptionModule [ "services" "prayer" ] "The corresponding package was removed from nixpkgs.")
 
     (mkRemovedOptionModule [ "i18n" "inputMethod" "fcitx" ] "The fcitx module has been removed. Please use fcitx5 instead")
     (mkRemovedOptionModule [ "services" "dhcpd4" ] ''
diff --git a/nixos/modules/security/acme/default.nix b/nixos/modules/security/acme/default.nix
index 932bf3e791159..7cc302969fb6d 100644
--- a/nixos/modules/security/acme/default.nix
+++ b/nixos/modules/security/acme/default.nix
@@ -345,6 +345,10 @@ let
       serviceConfig = commonServiceConfig // {
         Group = data.group;
 
+        # Let's Encrypt Failed Validation Limit allows 5 retries per hour, per account, hostname and hour.
+        # This avoids eating them all up if something is misconfigured upon the first try.
+        RestartSec = 15 * 60;
+
         # Keep in mind that these directories will be deleted if the user runs
         # systemctl clean --what=state
         # acme/.lego/${cert} is listed for this reason.
diff --git a/nixos/modules/security/google_oslogin.nix b/nixos/modules/security/google_oslogin.nix
index f75b4df1851a5..95975943ff801 100644
--- a/nixos/modules/security/google_oslogin.nix
+++ b/nixos/modules/security/google_oslogin.nix
@@ -42,6 +42,10 @@ in
     security.sudo.extraConfig = ''
       #includedir /run/google-sudoers.d
     '';
+    security.sudo-rs.extraConfig = ''
+      #includedir /run/google-sudoers.d
+    '';
+
     systemd.tmpfiles.rules = [
       "d /run/google-sudoers.d 750 root root -"
       "d /var/google-users.d 750 root root -"
diff --git a/nixos/modules/security/lock-kernel-modules.nix b/nixos/modules/security/lock-kernel-modules.nix
index 333b648014263..461b9ffe7ee0b 100644
--- a/nixos/modules/security/lock-kernel-modules.nix
+++ b/nixos/modules/security/lock-kernel-modules.nix
@@ -1,4 +1,4 @@
-{ config, pkgs, lib, ... }:
+{ config, lib, ... }:
 
 with lib;
 
@@ -49,7 +49,7 @@ with lib;
         };
 
       script = ''
-        ${pkgs.udev}/bin/udevadm settle
+        ${config.systemd.package}/bin/udevadm settle
         echo -n 1 >/proc/sys/kernel/modules_disabled
       '';
     };
diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix
index 709bb8b94a659..b7e1ea526535d 100644
--- a/nixos/modules/security/pam.nix
+++ b/nixos/modules/security/pam.nix
@@ -1531,6 +1531,10 @@ in
         (map (module: "mr ${module},"))
         concatLines
       ]);
-  };
 
+    security.sudo.extraConfig = optionalString config.security.pam.enableSSHAgentAuth ''
+      # Keep SSH_AUTH_SOCK so that pam_ssh_agent_auth.so can do its magic.
+      Defaults env_keep+=SSH_AUTH_SOCK
+    '';
+    };
 }
diff --git a/nixos/modules/security/pam_mount.nix b/nixos/modules/security/pam_mount.nix
index ad78f38b08663..26f906f2a76af 100644
--- a/nixos/modules/security/pam_mount.nix
+++ b/nixos/modules/security/pam_mount.nix
@@ -33,7 +33,7 @@ in
         default = [];
         description = lib.mdDoc ''
           List of volume definitions for pam_mount.
-          For more information, visit <http://pam-mount.sourceforge.net/pam_mount.conf.5.html>.
+          For more information, visit <https://pam-mount.sourceforge.net/pam_mount.conf.5.html>.
         '';
       };
 
@@ -78,7 +78,7 @@ in
         description = lib.mdDoc ''
           Sets the Debug-Level. 0 disables debugging, 1 enables pam_mount tracing,
           and 2 additionally enables tracing in mount.crypt. The default is 0.
-          For more information, visit <http://pam-mount.sourceforge.net/pam_mount.conf.5.html>.
+          For more information, visit <https://pam-mount.sourceforge.net/pam_mount.conf.5.html>.
         '';
       };
 
@@ -88,7 +88,7 @@ in
         description = lib.mdDoc ''
           Amount of microseconds to wait until killing remaining processes after
           final logout.
-          For more information, visit <http://pam-mount.sourceforge.net/pam_mount.conf.5.html>.
+          For more information, visit <https://pam-mount.sourceforge.net/pam_mount.conf.5.html>.
         '';
       };
 
diff --git a/nixos/modules/security/polkit.nix b/nixos/modules/security/polkit.nix
index de427ccb295bb..327f49c0b6378 100644
--- a/nixos/modules/security/polkit.nix
+++ b/nixos/modules/security/polkit.nix
@@ -35,7 +35,7 @@ in
       description = lib.mdDoc
         ''
           Any polkit rules to be added to config (in JavaScript ;-). See:
-          http://www.freedesktop.org/software/polkit/docs/latest/polkit.8.html#polkit-rules
+          <https://www.freedesktop.org/software/polkit/docs/latest/polkit.8.html#polkit-rules>
         '';
     };
 
@@ -117,4 +117,3 @@ in
   };
 
 }
-
diff --git a/nixos/modules/security/sudo.nix b/nixos/modules/security/sudo.nix
index c665c15242a53..3dd5d2e525d91 100644
--- a/nixos/modules/security/sudo.nix
+++ b/nixos/modules/security/sudo.nix
@@ -6,6 +6,8 @@ let
 
   cfg = config.security.sudo;
 
+  inherit (config.security.pam) enableSSHAgentAuth;
+
   toUserString = user: if (isInt user) then "#${toString user}" else "${user}";
   toGroupString = group: if (isInt group) then "%#${toString group}" else "%${group}";
 
@@ -28,9 +30,18 @@ in
 
   ###### interface
 
-  options = {
+  options.security.sudo = {
+
+    defaultOptions = mkOption {
+      type = with types; listOf str;
+      default = [ "SETENV" ];
+      description = mdDoc ''
+        Options used for the default rules, granting `root` and the
+        `wheel` group permission to run any command as any user.
+      '';
+    };
 
-    security.sudo.enable = mkOption {
+    enable = mkOption {
       type = types.bool;
       default = true;
       description =
@@ -40,29 +51,21 @@ in
         '';
     };
 
-    security.sudo.package = mkOption {
-      type = types.package;
-      default = pkgs.sudo;
-      defaultText = literalExpression "pkgs.sudo";
-      description = lib.mdDoc ''
-        Which package to use for `sudo`.
-      '';
-    };
+    package = mkPackageOption pkgs "sudo" { };
 
-    security.sudo.wheelNeedsPassword = mkOption {
+    wheelNeedsPassword = mkOption {
       type = types.bool;
       default = true;
-      description =
-        lib.mdDoc ''
-          Whether users of the `wheel` group must
-          provide a password to run commands as super user via {command}`sudo`.
-        '';
+      description = mdDoc ''
+        Whether users of the `wheel` group must
+        provide a password to run commands as super user via {command}`sudo`.
+      '';
       };
 
-    security.sudo.execWheelOnly = mkOption {
+    execWheelOnly = mkOption {
       type = types.bool;
       default = false;
-      description = lib.mdDoc ''
+      description = mdDoc ''
         Only allow members of the `wheel` group to execute sudo by
         setting the executable's permissions accordingly.
         This prevents users that are not members of `wheel` from
@@ -70,19 +73,18 @@ in
       '';
     };
 
-    security.sudo.configFile = mkOption {
+    configFile = mkOption {
       type = types.lines;
       # Note: if syntax errors are detected in this file, the NixOS
       # configuration will fail to build.
-      description =
-        lib.mdDoc ''
-          This string contains the contents of the
-          {file}`sudoers` file.
-        '';
+      description = mdDoc ''
+        This string contains the contents of the
+        {file}`sudoers` file.
+      '';
     };
 
-    security.sudo.extraRules = mkOption {
-      description = lib.mdDoc ''
+    extraRules = mkOption {
+      description = mdDoc ''
         Define specific rules to be in the {file}`sudoers` file.
         More specific rules should come after more general ones in order to
         yield the expected behavior. You can use mkBefore/mkAfter to ensure
@@ -112,7 +114,7 @@ in
         options = {
           users = mkOption {
             type = with types; listOf (either str int);
-            description = lib.mdDoc ''
+            description = mdDoc ''
               The usernames / UIDs this rule should apply for.
             '';
             default = [];
@@ -120,7 +122,7 @@ in
 
           groups = mkOption {
             type = with types; listOf (either str int);
-            description = lib.mdDoc ''
+            description = mdDoc ''
               The groups / GIDs this rule should apply for.
             '';
             default = [];
@@ -129,7 +131,7 @@ in
           host = mkOption {
             type = types.str;
             default = "ALL";
-            description = lib.mdDoc ''
+            description = mdDoc ''
               For what host this rule should apply.
             '';
           };
@@ -137,7 +139,7 @@ in
           runAs = mkOption {
             type = with types; str;
             default = "ALL:ALL";
-            description = lib.mdDoc ''
+            description = mdDoc ''
               Under which user/group the specified command is allowed to run.
 
               A user can be specified using just the username: `"foo"`.
@@ -147,7 +149,7 @@ in
           };
 
           commands = mkOption {
-            description = lib.mdDoc ''
+            description = mdDoc ''
               The commands for which the rule should apply.
             '';
             type = with types; listOf (either str (submodule {
@@ -155,7 +157,7 @@ in
               options = {
                 command = mkOption {
                   type = with types; str;
-                  description = lib.mdDoc ''
+                  description = mdDoc ''
                     A command being either just a path to a binary to allow any arguments,
                     the full command with arguments pre-set or with `""` used as the argument,
                     not allowing arguments to the command at all.
@@ -164,7 +166,7 @@ in
 
                 options = mkOption {
                   type = with types; listOf (enum [ "NOPASSWD" "PASSWD" "NOEXEC" "EXEC" "SETENV" "NOSETENV" "LOG_INPUT" "NOLOG_INPUT" "LOG_OUTPUT" "NOLOG_OUTPUT" ]);
-                  description = lib.mdDoc ''
+                  description = mdDoc ''
                     Options for running the command. Refer to the [sudo manual](https://www.sudo.ws/man/1.7.10/sudoers.man.html).
                   '';
                   default = [];
@@ -177,10 +179,10 @@ in
       });
     };
 
-    security.sudo.extraConfig = mkOption {
+    extraConfig = mkOption {
       type = types.lines;
       default = "";
-      description = lib.mdDoc ''
+      description = mdDoc ''
         Extra configuration text appended to {file}`sudoers`.
       '';
     };
@@ -190,44 +192,55 @@ in
   ###### implementation
 
   config = mkIf cfg.enable {
-    assertions = [
-      { assertion = cfg.package.pname != "sudo-rs";
-        message = "The NixOS `sudo` module does not work with `sudo-rs` yet."; }
-    ];
-
-    # We `mkOrder 600` so that the default rule shows up first, but there is
-    # still enough room for a user to `mkBefore` it.
-    security.sudo.extraRules = mkOrder 600 [
-      { groups = [ "wheel" ];
-        commands = [ { command = "ALL"; options = (if cfg.wheelNeedsPassword then [ "SETENV" ] else [ "NOPASSWD" "SETENV" ]); } ];
-      }
-    ];
-
-    security.sudo.configFile =
+    assertions = [ {
+      assertion = cfg.package.pname != "sudo-rs";
+      message = ''
+        NixOS' `sudo` module does not support `sudo-rs`; see `security.sudo-rs` instead.
+      '';
+    } ];
+
+    security.sudo.extraRules =
+      let
+        defaultRule = { users ? [], groups ? [], opts ? [] }: [ {
+          inherit users groups;
+          commands = [ {
+            command = "ALL";
+            options = opts ++ cfg.defaultOptions;
+          } ];
+        } ];
+      in mkMerge [
+        # This is ordered before users' `mkBefore` rules,
+        # so as not to introduce unexpected changes.
+        (mkOrder 400 (defaultRule { users = [ "root" ]; }))
+
+        # This is ordered to show before (most) other rules, but
+        # late-enough for a user to `mkBefore` it.
+        (mkOrder 600 (defaultRule {
+          groups = [ "wheel" ];
+          opts = (optional (!cfg.wheelNeedsPassword) "NOPASSWD");
+        }))
+      ];
+
+    security.sudo.configFile = concatStringsSep "\n" (filter (s: s != "") [
       ''
         # Don't edit this file. Set the NixOS options ‘security.sudo.configFile’
         # or ‘security.sudo.extraRules’ instead.
-
-        # Keep SSH_AUTH_SOCK so that pam_ssh_agent_auth.so can do its magic.
-        Defaults env_keep+=SSH_AUTH_SOCK
-
-        # "root" is allowed to do anything.
-        root        ALL=(ALL:ALL) SETENV: ALL
-
-        # extraRules
-        ${concatStringsSep "\n" (
-          lists.flatten (
-            map (
-              rule: optionals (length rule.commands != 0) [
-                (map (user: "${toUserString user}	${rule.host}=(${rule.runAs})	${toCommandsString rule.commands}") rule.users)
-                (map (group: "${toGroupString group}	${rule.host}=(${rule.runAs})	${toCommandsString rule.commands}") rule.groups)
-              ]
-            ) cfg.extraRules
-          )
-        )}
-
+      ''
+      (pipe cfg.extraRules [
+        (filter (rule: length rule.commands != 0))
+        (map (rule: [
+          (map (user: "${toUserString user}     ${rule.host}=(${rule.runAs})    ${toCommandsString rule.commands}") rule.users)
+          (map (group: "${toGroupString group}  ${rule.host}=(${rule.runAs})    ${toCommandsString rule.commands}") rule.groups)
+        ]))
+        flatten
+        (concatStringsSep "\n")
+      ])
+      "\n"
+      (optionalString (cfg.extraConfig != "") ''
+        # extraConfig
         ${cfg.extraConfig}
-      '';
+      '')
+    ]);
 
     security.wrappers = let
       owner = "root";
diff --git a/nixos/modules/services/audio/jack.nix b/nixos/modules/services/audio/jack.nix
index 105e99cb2f5ed..b51f2a78c9832 100644
--- a/nixos/modules/services/audio/jack.nix
+++ b/nixos/modules/services/audio/jack.nix
@@ -225,7 +225,7 @@ in {
         description = "JACK Audio system service user";
         isSystemUser = true;
       };
-      # http://jackaudio.org/faq/linux_rt_config.html
+      # https://jackaudio.org/faq/linux_rt_config.html
       security.pam.loginLimits = [
         { domain = "@jackaudio"; type = "-"; item = "rtprio"; value = "99"; }
         { domain = "@jackaudio"; type = "-"; item = "memlock"; value = "unlimited"; }
diff --git a/nixos/modules/services/audio/navidrome.nix b/nixos/modules/services/audio/navidrome.nix
index e18e61eb6d44b..77a0e74af9ca2 100644
--- a/nixos/modules/services/audio/navidrome.nix
+++ b/nixos/modules/services/audio/navidrome.nix
@@ -28,10 +28,17 @@ in {
         '';
       };
 
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc "Whether to open the TCP port in the firewall";
+      };
     };
   };
 
   config = mkIf cfg.enable {
+    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [cfg.settings.Port];
+
     systemd.services.navidrome = {
       description = "Navidrome Media Server";
       after = [ "network.target" ];
diff --git a/nixos/modules/services/audio/slimserver.nix b/nixos/modules/services/audio/slimserver.nix
index 9fbc68b71364e..cdd9d551c501f 100644
--- a/nixos/modules/services/audio/slimserver.nix
+++ b/nixos/modules/services/audio/slimserver.nix
@@ -54,7 +54,7 @@ in {
       serviceConfig = {
         User = "slimserver";
         # Issue 40589: Disable broken image/video support (audio still works!)
-        ExecStart = "${cfg.package}/slimserver.pl --logdir ${cfg.dataDir}/logs --prefsdir ${cfg.dataDir}/prefs --cachedir ${cfg.dataDir}/cache --noimage --novideo";
+        ExecStart = "${lib.getExe cfg.package} --logdir ${cfg.dataDir}/logs --prefsdir ${cfg.dataDir}/prefs --cachedir ${cfg.dataDir}/cache --noimage --novideo";
       };
     };
 
diff --git a/nixos/modules/services/backup/restic.nix b/nixos/modules/services/backup/restic.nix
index fcdd3082f5a6c..87595f39796d9 100644
--- a/nixos/modules/services/backup/restic.nix
+++ b/nixos/modules/services/backup/restic.nix
@@ -133,13 +133,15 @@ in
         };
 
         timerConfig = mkOption {
-          type = types.attrsOf unitOption;
+          type = types.nullOr (types.attrsOf unitOption);
           default = {
             OnCalendar = "daily";
             Persistent = true;
           };
           description = lib.mdDoc ''
-            When to run the backup. See {manpage}`systemd.timer(5)` for details.
+            When to run the backup. See {manpage}`systemd.timer(5)` for
+            details. If null no timer is created and the backup will only
+            run when explicitly started.
           '';
           example = {
             OnCalendar = "00:05";
@@ -378,7 +380,7 @@ in
           wantedBy = [ "timers.target" ];
           timerConfig = backup.timerConfig;
         })
-        config.services.restic.backups;
+        (filterAttrs (_: backup: backup.timerConfig != null) config.services.restic.backups);
 
     # generate wrapper scripts, as described in the createWrapper option
     environment.systemPackages = lib.mapAttrsToList (name: backup: let
diff --git a/nixos/modules/services/backup/syncoid.nix b/nixos/modules/services/backup/syncoid.nix
index 1a1df38617b55..f770108295759 100644
--- a/nixos/modules/services/backup/syncoid.nix
+++ b/nixos/modules/services/backup/syncoid.nix
@@ -123,9 +123,7 @@ in
     };
 
     sshKey = mkOption {
-      type = types.nullOr types.path;
-      # Prevent key from being copied to store
-      apply = mapNullable toString;
+      type = with types; nullOr (coercedTo path toString str);
       default = null;
       description = lib.mdDoc ''
         SSH private key file to use to login to the remote system. Can be
@@ -205,9 +203,7 @@ in
           recursive = mkEnableOption (lib.mdDoc ''the transfer of child datasets'');
 
           sshKey = mkOption {
-            type = types.nullOr types.path;
-            # Prevent key from being copied to store
-            apply = mapNullable toString;
+            type = with types; nullOr (coercedTo path toString str);
             description = lib.mdDoc ''
               SSH private key file to use to login to the remote system.
               Defaults to {option}`services.syncoid.sshKey` option.
diff --git a/nixos/modules/services/computing/boinc/client.nix b/nixos/modules/services/computing/boinc/client.nix
index 51475171bf3f9..ff16795c82084 100644
--- a/nixos/modules/services/computing/boinc/client.nix
+++ b/nixos/modules/services/computing/boinc/client.nix
@@ -54,7 +54,7 @@ in
           only the hosts listed in {var}`dataDir`/remote_hosts.cfg will be allowed to
           connect.
 
-          See also: <http://boinc.berkeley.edu/wiki/Controlling_BOINC_remotely#Remote_access>
+          See also: <https://boinc.berkeley.edu/wiki/Controlling_BOINC_remotely#Remote_access>
         '';
       };
 
diff --git a/nixos/modules/services/computing/slurm/slurm.nix b/nixos/modules/services/computing/slurm/slurm.nix
index 344c43a429b32..1cbe7b893f83a 100644
--- a/nixos/modules/services/computing/slurm/slurm.nix
+++ b/nixos/modules/services/computing/slurm/slurm.nix
@@ -6,7 +6,7 @@ let
 
   cfg = config.services.slurm;
   opt = options.services.slurm;
-  # configuration file can be generated by http://slurm.schedmd.com/configurator.html
+  # configuration file can be generated by https://slurm.schedmd.com/configurator.html
 
   defaultUser = "slurm";
 
diff --git a/nixos/modules/services/databases/couchdb.nix b/nixos/modules/services/databases/couchdb.nix
index 0a81a8dceeeed..bfecfbb3664fb 100644
--- a/nixos/modules/services/databases/couchdb.nix
+++ b/nixos/modules/services/databases/couchdb.nix
@@ -79,7 +79,7 @@ in {
         '';
       };
 
-      # couchdb options: http://docs.couchdb.org/en/latest/config/index.html
+      # couchdb options: https://docs.couchdb.org/en/latest/config/index.html
 
       databaseDir = mkOption {
         type = types.path;
diff --git a/nixos/modules/services/databases/firebird.nix b/nixos/modules/services/databases/firebird.nix
index 26ed46f0e60c0..3927c81d953dd 100644
--- a/nixos/modules/services/databases/firebird.nix
+++ b/nixos/modules/services/databases/firebird.nix
@@ -17,7 +17,7 @@
 # There are at least two ways to run firebird. superserver has been chosen
 # however there are no strong reasons to prefer this or the other one AFAIK
 # Eg superserver is said to be most efficiently using resources according to
-# http://www.firebirdsql.org/manual/qsg25-classic-or-super.html
+# https://www.firebirdsql.org/manual/qsg25-classic-or-super.html
 
 with lib;
 
diff --git a/nixos/modules/services/databases/pgmanage.nix b/nixos/modules/services/databases/pgmanage.nix
index 12c8253ab49cb..a0933a5ffc45d 100644
--- a/nixos/modules/services/databases/pgmanage.nix
+++ b/nixos/modules/services/databases/pgmanage.nix
@@ -66,7 +66,7 @@ in {
         pgmanage requires at least one PostgreSQL server be defined.
 
         Detailed information about PostgreSQL connection strings is available at:
-        <http://www.postgresql.org/docs/current/static/libpq-connect.html>
+        <https://www.postgresql.org/docs/current/libpq-connect.html>
 
         Note that you should not specify your user name or password. That
         information will be entered on the login screen. If you specify a
diff --git a/nixos/modules/services/databases/postgresql.md b/nixos/modules/services/databases/postgresql.md
index e4b679a3eee00..e5e0b7efec29a 100644
--- a/nixos/modules/services/databases/postgresql.md
+++ b/nixos/modules/services/databases/postgresql.md
@@ -5,7 +5,7 @@
 
 *Source:* {file}`modules/services/databases/postgresql.nix`
 
-*Upstream documentation:* <http://www.postgresql.org/docs/>
+*Upstream documentation:* <https://www.postgresql.org/docs/>
 
 <!-- FIXME: more stuff, like maintainer? -->
 
@@ -39,6 +39,125 @@ By default, PostgreSQL stores its databases in {file}`/var/lib/postgresql/$psqlS
 services.postgresql.dataDir = "/data/postgresql";
 ```
 
+## Initializing {#module-services-postgres-initializing}
+
+As of NixOS 23.11,
+`services.postgresql.ensureUsers.*.ensurePermissions` has been
+deprecated, after a change to default permissions in PostgreSQL 15
+invalidated most of its previous use cases:
+
+- In psql < 15, `ALL PRIVILEGES` used to include `CREATE TABLE`, where
+  in psql >= 15 that would be a separate permission
+- psql >= 15 instead gives only the database owner create permissions
+- Even on psql < 15 (or databases migrated to >= 15), it is
+  recommended to manually assign permissions along these lines
+  - https://www.postgresql.org/docs/release/15.0/
+  - https://www.postgresql.org/docs/15/ddl-schemas.html#DDL-SCHEMAS-PRIV
+
+### Assigning ownership {#module-services-postgres-initializing-ownership}
+
+Usually, the database owner should be a database user of the same
+name. This can be done with
+`services.postgresql.ensureUsers.*.ensureDBOwnership = true;`.
+
+If the database user name equals the connecting system user name,
+postgres by default will accept a passwordless connection via unix
+domain socket. This makes it possible to run many postgres-backed
+services without creating any database secrets at all
+
+### Assigning extra permissions {#module-services-postgres-initializing-extra-permissions}
+
+For many cases, it will be enough to have the database user be the
+owner. Until `services.postgresql.ensureUsers.*.ensurePermissions` has
+been re-thought, if more users need access to the database, please use
+one of the following approaches:
+
+**WARNING:** `services.postgresql.initialScript` is not recommended
+for `ensurePermissions` replacement, as that is *only run on first
+start of PostgreSQL*.
+
+**NOTE:** all of these methods may be obsoleted, when `ensure*` is
+reworked, but it is expected that they will stay viable for running
+database migrations.
+
+**NOTE:** please make sure that any added migrations are idempotent (re-runnable).
+
+#### as superuser {#module-services-postgres-initializing-extra-permissions-superuser}
+
+**Advantage:** compatible with postgres < 15, because it's run
+as the database superuser `postgres`.
+
+##### in database `postStart` {#module-services-postgres-initializing-extra-permissions-superuser-post-start}
+
+**Disadvantage:** need to take care of ordering yourself. In this
+example, `mkAfter` ensures that permissions are assigned after any
+databases from `ensureDatabases` and `extraUser1` from `ensureUsers`
+are already created.
+
+```nix
+    systemd.services.postgresql.postStart = lib.mkAfter ''
+      $PSQL service1 -c 'GRANT SELECT ON ALL TABLES IN SCHEMA public TO "extraUser1"'
+      $PSQL service1 -c 'GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO "extraUser1"'
+      # ....
+    '';
+```
+
+##### in intermediate oneshot service {#module-services-postgres-initializing-extra-permissions-superuser-oneshot}
+
+```nix
+    systemd.services."migrate-service1-db1" = {
+      serviceConfig.Type = "oneshot";
+      requiredBy = "service1.service";
+      before = "service1.service";
+      after = "postgresql.service";
+      serviceConfig.User = "postgres";
+      environment.PSQL = "psql --port=${toString services.postgresql.port}";
+      path = [ postgresql ];
+      script = ''
+        $PSQL service1 -c 'GRANT SELECT ON ALL TABLES IN SCHEMA public TO "extraUser1"'
+        $PSQL service1 -c 'GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO "extraUser1"'
+        # ....
+      '';
+    };
+```
+
+#### as service user {#module-services-postgres-initializing-extra-permissions-service-user}
+
+**Advantage:** re-uses systemd's dependency ordering;
+
+**Disadvantage:** relies on service user having grant permission. To be combined with `ensureDBOwnership`.
+
+##### in service `preStart` {#module-services-postgres-initializing-extra-permissions-service-user-pre-start}
+
+```nix
+    environment.PSQL = "psql --port=${toString services.postgresql.port}";
+    path = [ postgresql ];
+    systemd.services."service1".preStart = ''
+      $PSQL -c 'GRANT SELECT ON ALL TABLES IN SCHEMA public TO "extraUser1"'
+      $PSQL -c 'GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO "extraUser1"'
+      # ....
+    '';
+```
+
+##### in intermediate oneshot service {#module-services-postgres-initializing-extra-permissions-service-user-oneshot}
+
+```nix
+    systemd.services."migrate-service1-db1" = {
+      serviceConfig.Type = "oneshot";
+      requiredBy = "service1.service";
+      before = "service1.service";
+      after = "postgresql.service";
+      serviceConfig.User = "service1";
+      environment.PSQL = "psql --port=${toString services.postgresql.port}";
+      path = [ postgresql ];
+      script = ''
+        $PSQL -c 'GRANT SELECT ON ALL TABLES IN SCHEMA public TO "extraUser1"'
+        $PSQL -c 'GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO "extraUser1"'
+        # ....
+      '';
+    };
+```
+
 ## Upgrading {#module-services-postgres-upgrading}
 
 ::: {.note}
diff --git a/nixos/modules/services/databases/postgresql.nix b/nixos/modules/services/databases/postgresql.nix
index 21e6a60e32a35..d69ffff7b47b6 100644
--- a/nixos/modules/services/databases/postgresql.nix
+++ b/nixos/modules/services/databases/postgresql.nix
@@ -168,7 +168,12 @@ in
             ensurePermissions = mkOption {
               type = types.attrsOf types.str;
               default = {};
+              visible = false; # This option has been deprecated.
               description = lib.mdDoc ''
+                This option is DEPRECATED and should not be used in nixpkgs anymore,
+                use `ensureDBOwnership` instead. It can also break with newer
+                versions of PostgreSQL (≥ 15).
+
                 Permissions to ensure for the user, specified as an attribute set.
                 The attribute names specify the database and tables to grant the permissions for.
                 The attribute values specify the permissions to grant. You may specify one or
@@ -187,6 +192,16 @@ in
               '';
             };
 
+            ensureDBOwnership = mkOption {
+              type = types.bool;
+              default = false;
+              description = mdDoc ''
+                Grants the user ownership to a database with the same name.
+                This database must be defined manually in
+                [](#opt-services.postgresql.ensureDatabases).
+              '';
+            };
+
             ensureClauses = mkOption {
               description = lib.mdDoc ''
                 An attrset of clauses to grant to the user. Under the hood this uses the
@@ -338,26 +353,21 @@ in
         });
         default = [];
         description = lib.mdDoc ''
-          Ensures that the specified users exist and have at least the ensured permissions.
+          Ensures that the specified users exist.
           The PostgreSQL users will be identified using peer authentication. This authenticates the Unix user with the
           same name only, and that without the need for a password.
-          This option will never delete existing users or remove permissions, especially not when the value of this
-          option is changed. This means that users created and permissions assigned once through this option or
-          otherwise have to be removed manually.
+          This option will never delete existing users or remove DB ownership of databases
+          once granted with `ensureDBOwnership = true;`. This means that this must be
+          cleaned up manually when changing after changing the config in here.
         '';
         example = literalExpression ''
           [
             {
               name = "nextcloud";
-              ensurePermissions = {
-                "DATABASE nextcloud" = "ALL PRIVILEGES";
-              };
             }
             {
               name = "superuser";
-              ensurePermissions = {
-                "ALL TABLES IN SCHEMA public" = "ALL PRIVILEGES";
-              };
+              ensureDBOwnership = true;
             }
           ]
         '';
@@ -445,6 +455,27 @@ in
 
   config = mkIf cfg.enable {
 
+    assertions = map ({ name, ensureDBOwnership, ... }: {
+      assertion = ensureDBOwnership -> builtins.elem name cfg.ensureDatabases;
+      message = ''
+        For each database user defined with `services.postgresql.ensureUsers` and
+        `ensureDBOwnership = true;`, a database with the same name must be defined
+        in `services.postgresql.ensureDatabases`.
+
+        Offender: ${name} has not been found among databases.
+      '';
+    }) cfg.ensureUsers;
+    # `ensurePermissions` is now deprecated, let's avoid it.
+    warnings = lib.optional (any ({ ensurePermissions, ... }: ensurePermissions != {}) cfg.ensureUsers) "
+      `services.postgresql.ensureUsers.*.ensurePermissions` is used in your expressions,
+      this option is known to be broken with newer PostgreSQL versions,
+      consider migrating to `services.postgresql.ensureUsers.*.ensureDBOwnership` or
+      consult the release notes or manual for more migration guidelines.
+
+      This option will be removed in NixOS 24.05 unless it sees significant
+      maintenance improvements.
+    ";
+
     services.postgresql.settings =
       {
         hba_file = "${pkgs.writeText "pg_hba.conf" cfg.authentication}";
@@ -556,12 +587,15 @@ in
             ${
               concatMapStrings
               (user:
-                let
+              let
                   userPermissions = concatStringsSep "\n"
                     (mapAttrsToList
                       (database: permission: ''$PSQL -tAc 'GRANT ${permission} ON ${database} TO "${user.name}"' '')
                       user.ensurePermissions
                     );
+                  dbOwnershipStmt = optionalString
+                    user.ensureDBOwnership
+                    ''$PSQL -tAc 'ALTER DATABASE "${user.name}" OWNER TO "${user.name}";' '';
 
                   filteredClauses = filterAttrs (name: value: value != null) user.ensureClauses;
 
@@ -572,6 +606,8 @@ in
                   $PSQL -tAc "SELECT 1 FROM pg_roles WHERE rolname='${user.name}'" | grep -q 1 || $PSQL -tAc 'CREATE USER "${user.name}"'
                   ${userPermissions}
                   ${userClauses}
+
+                  ${dbOwnershipStmt}
                 ''
               )
               cfg.ensureUsers
@@ -588,7 +624,7 @@ in
                    else "simple";
 
             # Shut down Postgres using SIGINT ("Fast Shutdown mode").  See
-            # http://www.postgresql.org/docs/current/static/server-shutdown.html
+            # https://www.postgresql.org/docs/current/server-shutdown.html
             KillSignal = "SIGINT";
             KillMode = "mixed";
 
diff --git a/nixos/modules/services/databases/redis.nix b/nixos/modules/services/databases/redis.nix
index 86b295dadf494..315a0282cd736 100644
--- a/nixos/modules/services/databases/redis.nix
+++ b/nixos/modules/services/databases/redis.nix
@@ -63,7 +63,7 @@ in {
 
       vmOverCommit = mkEnableOption (lib.mdDoc ''
         setting of vm.overcommit_memory to 1
-        (Suggested for Background Saving: http://redis.io/topics/faq)
+        (Suggested for Background Saving: <https://redis.io/docs/get-started/faq/>)
       '');
 
       servers = mkOption {
diff --git a/nixos/modules/services/desktops/flatpak.md b/nixos/modules/services/desktops/flatpak.md
index 65b1554d79b4e..af71d85b5a157 100644
--- a/nixos/modules/services/desktops/flatpak.md
+++ b/nixos/modules/services/desktops/flatpak.md
@@ -18,6 +18,7 @@ in other cases, you will need to add something like the following to your
 {file}`configuration.nix`:
 ```
   xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-gtk ];
+  xdg.portal.config.common.default = "gtk";
 ```
 
 Then, you will need to add a repository, for example,
diff --git a/nixos/modules/services/desktops/gnome/at-spi2-core.nix b/nixos/modules/services/desktops/gnome/at-spi2-core.nix
index 10a2f1f9eca0d..d0320c1e6307a 100644
--- a/nixos/modules/services/desktops/gnome/at-spi2-core.nix
+++ b/nixos/modules/services/desktops/gnome/at-spi2-core.nix
@@ -51,7 +51,7 @@ with lib;
     })
 
     (mkIf (!config.services.gnome.at-spi2-core.enable) {
-      environment.variables = {
+      environment.sessionVariables = {
         NO_AT_BRIDGE = "1";
         GTK_A11Y = "none";
       };
diff --git a/nixos/modules/services/desktops/gnome/gnome-initial-setup.nix b/nixos/modules/services/desktops/gnome/gnome-initial-setup.nix
index f24e6f1eb1550..6eaf861e49743 100644
--- a/nixos/modules/services/desktops/gnome/gnome-initial-setup.nix
+++ b/nixos/modules/services/desktops/gnome/gnome-initial-setup.nix
@@ -93,6 +93,9 @@ in
       "gnome-initial-setup.service"
     ];
 
+    programs.dconf.profiles.gnome-initial-setup.databases = [
+      "${pkgs.gnome.gnome-initial-setup}/share/gnome-initial-setup/initial-setup-dconf-defaults"
+    ];
   };
 
 }
diff --git a/nixos/modules/services/desktops/pipewire/pipewire.nix b/nixos/modules/services/desktops/pipewire/pipewire.nix
index ae695baf42c60..07ca2727cf48e 100644
--- a/nixos/modules/services/desktops/pipewire/pipewire.nix
+++ b/nixos/modules/services/desktops/pipewire/pipewire.nix
@@ -115,8 +115,7 @@ in {
     environment.systemPackages = [ cfg.package ]
                                  ++ lib.optional cfg.jack.enable jack-libs;
 
-    systemd.packages = [ cfg.package ]
-                       ++ lib.optional cfg.pulse.enable cfg.package.pulse;
+    systemd.packages = [ cfg.package ];
 
     # PipeWire depends on DBUS but doesn't list it. Without this booting
     # into a terminal results in the service crashing with an error.
@@ -130,9 +129,13 @@ in {
     systemd.user.sockets.pipewire.enable = !cfg.systemWide;
     systemd.user.services.pipewire.enable = !cfg.systemWide;
 
+    # Mask pw-pulse if it's not wanted
+    systemd.user.services.pipewire-pulse.enable = cfg.pulse.enable;
+    systemd.user.sockets.pipewire-pulse.enable = cfg.pulse.enable;
+
     systemd.sockets.pipewire.wantedBy = lib.mkIf cfg.socketActivation [ "sockets.target" ];
     systemd.user.sockets.pipewire.wantedBy = lib.mkIf cfg.socketActivation [ "sockets.target" ];
-    systemd.user.sockets.pipewire-pulse.wantedBy = lib.mkIf (cfg.socketActivation && cfg.pulse.enable) ["sockets.target"];
+    systemd.user.sockets.pipewire-pulse.wantedBy = lib.mkIf cfg.socketActivation [ "sockets.target" ];
 
     services.udev.packages = [ cfg.package ];
 
@@ -140,14 +143,14 @@ in {
     environment.etc."alsa/conf.d/49-pipewire-modules.conf" = mkIf cfg.alsa.enable {
       text = ''
         pcm_type.pipewire {
-          libs.native = ${cfg.package.lib}/lib/alsa-lib/libasound_module_pcm_pipewire.so ;
+          libs.native = ${cfg.package}/lib/alsa-lib/libasound_module_pcm_pipewire.so ;
           ${optionalString enable32BitAlsaPlugins
-            "libs.32Bit = ${pkgs.pkgsi686Linux.pipewire.lib}/lib/alsa-lib/libasound_module_pcm_pipewire.so ;"}
+            "libs.32Bit = ${pkgs.pkgsi686Linux.pipewire}/lib/alsa-lib/libasound_module_pcm_pipewire.so ;"}
         }
         ctl_type.pipewire {
-          libs.native = ${cfg.package.lib}/lib/alsa-lib/libasound_module_ctl_pipewire.so ;
+          libs.native = ${cfg.package}/lib/alsa-lib/libasound_module_ctl_pipewire.so ;
           ${optionalString enable32BitAlsaPlugins
-            "libs.32Bit = ${pkgs.pkgsi686Linux.pipewire.lib}/lib/alsa-lib/libasound_module_ctl_pipewire.so ;"}
+            "libs.32Bit = ${pkgs.pkgsi686Linux.pipewire}/lib/alsa-lib/libasound_module_ctl_pipewire.so ;"}
         }
       '';
     };
diff --git a/nixos/modules/services/desktops/seatd.nix b/nixos/modules/services/desktops/seatd.nix
new file mode 100644
index 0000000000000..51977dfd2153a
--- /dev/null
+++ b/nixos/modules/services/desktops/seatd.nix
@@ -0,0 +1,51 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.seatd;
+  inherit (lib) mkEnableOption mkOption mdDoc types;
+in
+{
+  meta.maintainers = with lib.maintainers; [ sinanmohd ];
+
+  options.services.seatd = {
+    enable = mkEnableOption (mdDoc "seatd");
+
+    user = mkOption {
+      type = types.str;
+      default = "root";
+      description = mdDoc "User to own the seatd socket";
+    };
+    group = mkOption {
+      type = types.str;
+      default = "seat";
+      description = mdDoc "Group to own the seatd socket";
+    };
+    logLevel = mkOption {
+      type = types.enum [ "debug" "info" "error" "silent" ];
+      default = "info";
+      description = mdDoc "Logging verbosity";
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = with pkgs; [ seatd sdnotify-wrapper ];
+    users.groups.seat = lib.mkIf (cfg.group == "seat") {};
+
+    systemd.services.seatd = {
+      description = "Seat management daemon";
+      documentation = [ "man:seatd(1)" ];
+
+      wantedBy = [ "multi-user.target" ];
+      restartIfChanged = false;
+
+      serviceConfig = {
+        Type = "notify";
+        NotifyAccess = "all";
+        SyslogIdentifier = "seatd";
+        ExecStart = "${pkgs.sdnotify-wrapper}/bin/sdnotify-wrapper ${pkgs.seatd.bin}/bin/seatd -n 1 -u ${cfg.user} -g ${cfg.group} -l ${cfg.logLevel}";
+        RestartSec = 1;
+        Restart = "always";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/development/zammad.nix b/nixos/modules/services/development/zammad.nix
index 7dd143eebf12f..d24ed24ef3956 100644
--- a/nixos/modules/services/development/zammad.nix
+++ b/nixos/modules/services/development/zammad.nix
@@ -204,7 +204,7 @@ in
 
     assertions = [
       {
-        assertion = cfg.database.createLocally -> cfg.database.user == "zammad";
+        assertion = cfg.database.createLocally -> cfg.database.user == "zammad" && cfg.database.name == "zammad";
         message = "services.zammad.database.user must be set to \"zammad\" if services.zammad.database.createLocally is set to true";
       }
       {
@@ -231,7 +231,7 @@ in
       ensureUsers = [
         {
           name = cfg.database.user;
-          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/finance/odoo.nix b/nixos/modules/services/finance/odoo.nix
index eec7c4e30cc48..b8574ed09af9c 100644
--- a/nixos/modules/services/finance/odoo.nix
+++ b/nixos/modules/services/finance/odoo.nix
@@ -121,7 +121,7 @@ in
       ensureDatabases = [ "odoo" ];
       ensureUsers = [{
         name = "odoo";
-        ensurePermissions = { "DATABASE odoo" = "ALL PRIVILEGES"; };
+        ensureDBOwnership = true;
       }];
     };
   });
diff --git a/nixos/modules/services/games/factorio.nix b/nixos/modules/services/games/factorio.nix
index b349ffa2375f7..e6c4522d5d1d7 100644
--- a/nixos/modules/services/games/factorio.nix
+++ b/nixos/modules/services/games/factorio.nix
@@ -37,7 +37,8 @@ let
     autosave_only_on_server = true;
     non_blocking_saving = cfg.nonBlockingSaving;
   } // cfg.extraSettings;
-  serverSettingsFile = pkgs.writeText "server-settings.json" (builtins.toJSON (filterAttrsRecursive (n: v: v != null) serverSettings));
+  serverSettingsString = builtins.toJSON (filterAttrsRecursive (n: v: v != null) serverSettings);
+  serverSettingsFile = pkgs.writeText "server-settings.json" serverSettingsString;
   serverAdminsFile = pkgs.writeText "server-adminlist.json" (builtins.toJSON cfg.admins);
   modDir = pkgs.factorio-utils.mkModDirDrv cfg.mods cfg.mods-dat;
 in
@@ -115,6 +116,23 @@ in
           customizations.
         '';
       };
+      extraSettingsFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = lib.mdDoc ''
+          File, which is dynamically applied to server-settings.json before
+          startup.
+
+          This option should be used for credentials.
+
+          For example a settings file could contain:
+          ```json
+          {
+            "game-password": "hunter1"
+          }
+          ```
+        '';
+      };
       stateDirName = mkOption {
         type = types.str;
         default = "factorio";
@@ -186,6 +204,8 @@ in
         default = null;
         description = lib.mdDoc ''
           Your factorio.com login credentials. Required for games with visibility public.
+
+          This option is insecure. Use extraSettingsFile instead.
         '';
       };
       package = mkOption {
@@ -202,6 +222,8 @@ in
         default = null;
         description = lib.mdDoc ''
           Your factorio.com login credentials. Required for games with visibility public.
+
+          This option is insecure. Use extraSettingsFile instead.
         '';
       };
       token = mkOption {
@@ -216,6 +238,8 @@ in
         default = null;
         description = lib.mdDoc ''
           Game password.
+
+          This option is insecure. Use extraSettingsFile instead.
         '';
       };
       requireUserVerification = mkOption {
@@ -251,14 +275,18 @@ in
       wantedBy      = [ "multi-user.target" ];
       after         = [ "network.target" ];
 
-      preStart = toString [
-        "test -e ${stateDir}/saves/${cfg.saveName}.zip"
-        "||"
-        "${cfg.package}/bin/factorio"
+      preStart =
+        (toString [
+          "test -e ${stateDir}/saves/${cfg.saveName}.zip"
+          "||"
+          "${cfg.package}/bin/factorio"
           "--config=${cfg.configFile}"
           "--create=${mkSavePath cfg.saveName}"
           (optionalString (cfg.mods != []) "--mod-directory=${modDir}")
-      ];
+        ])
+        + (optionalString (cfg.extraSettingsFile != null) ("\necho ${lib.strings.escapeShellArg serverSettingsString}"
+          + " \"$(cat ${cfg.extraSettingsFile})\" | ${lib.getExe pkgs.jq} -s add"
+          + " > ${stateDir}/server-settings.json"));
 
       serviceConfig = {
         Restart = "always";
@@ -272,7 +300,11 @@ in
           "--port=${toString cfg.port}"
           "--bind=${cfg.bind}"
           (optionalString (!cfg.loadLatestSave) "--start-server=${mkSavePath cfg.saveName}")
-          "--server-settings=${serverSettingsFile}"
+          "--server-settings=${
+            if (cfg.extraSettingsFile != null)
+            then "${stateDir}/server-settings.json"
+            else serverSettingsFile
+          }"
           (optionalString cfg.loadLatestSave "--start-server-load-latest")
           (optionalString (cfg.mods != []) "--mod-directory=${modDir}")
           (optionalString (cfg.admins != []) "--server-adminlist=${serverAdminsFile}")
diff --git a/nixos/modules/services/hardware/fwupd.nix b/nixos/modules/services/hardware/fwupd.nix
index 7a938459d0cb5..7b6c336bd2211 100644
--- a/nixos/modules/services/hardware/fwupd.nix
+++ b/nixos/modules/services/hardware/fwupd.nix
@@ -187,13 +187,20 @@ in {
       # fwupd-refresh expects a user that we do not create, so just run with DynamicUser
       # instead and ensure we take ownership of /var/lib/fwupd
       services.fwupd-refresh.serviceConfig = {
-        DynamicUser = true;
         StateDirectory = "fwupd";
+        # Better for debugging, upstream sets stderr to null for some reason..
+        StandardError = "inherit";
       };
 
       timers.fwupd-refresh.wantedBy = [ "timers.target" ];
     };
 
+    users.users.fwupd-refresh = {
+      isSystemUser = true;
+      group = "fwupd-refresh";
+    };
+    users.groups.fwupd-refresh = {};
+
     security.polkit.enable = true;
   };
 
diff --git a/nixos/modules/services/hardware/udev.nix b/nixos/modules/services/hardware/udev.nix
index 24987374ab0d6..08ca7a0d247d0 100644
--- a/nixos/modules/services/hardware/udev.nix
+++ b/nixos/modules/services/hardware/udev.nix
@@ -279,7 +279,7 @@ in
       default = true;
       type = types.bool;
       description = lib.mdDoc ''
-        Whether to assign [predictable names to network interfaces](http://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames).
+        Whether to assign [predictable names to network interfaces](https://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames/).
         If enabled, interfaces
         are assigned names that contain topology information
         (e.g. `wlp3s0`) and thus should be stable
diff --git a/nixos/modules/services/hardware/usbmuxd.nix b/nixos/modules/services/hardware/usbmuxd.nix
index 9466ea26995b8..d05ad3af8b127 100644
--- a/nixos/modules/services/hardware/usbmuxd.nix
+++ b/nixos/modules/services/hardware/usbmuxd.nix
@@ -77,7 +77,7 @@ in
       serviceConfig = {
         # Trigger the udev rule manually. This doesn't require replugging the
         # device when first enabling the option to get it to work
-        ExecStartPre = "${pkgs.udev}/bin/udevadm trigger -s usb -a idVendor=${apple}";
+        ExecStartPre = "${config.systemd.package}/bin/udevadm trigger -s usb -a idVendor=${apple}";
         ExecStart = "${cfg.package}/bin/usbmuxd -U ${cfg.user} -v";
       };
     };
diff --git a/nixos/modules/services/home-automation/home-assistant.nix b/nixos/modules/services/home-automation/home-assistant.nix
index 789b06af19b1c..54fd3e17292f6 100644
--- a/nixos/modules/services/home-automation/home-assistant.nix
+++ b/nixos/modules/services/home-automation/home-assistant.nix
@@ -16,7 +16,8 @@ let
     cp ${format.generate "configuration.yaml" filteredConfig} $out
     sed -i -e "s/'\!\([a-z_]\+\) \(.*\)'/\!\1 \2/;s/^\!\!/\!/;" $out
   '';
-  lovelaceConfig = cfg.lovelaceConfig or {};
+  lovelaceConfig = if (cfg.lovelaceConfig == null) then {}
+    else (lib.recursiveUpdate customLovelaceModulesResources cfg.lovelaceConfig);
   lovelaceConfigFile = format.generate "ui-lovelace.yaml" lovelaceConfig;
 
   # Components advertised by the home-assistant package
@@ -62,8 +63,24 @@ let
     # Respect overrides that already exist in the passed package and
     # concat it with values passed via the module.
     extraComponents = oldArgs.extraComponents or [] ++ extraComponents;
-    extraPackages = ps: (oldArgs.extraPackages or (_: []) ps) ++ (cfg.extraPackages ps);
+    extraPackages = ps: (oldArgs.extraPackages or (_: []) ps)
+      ++ (cfg.extraPackages ps)
+      ++ (lib.concatMap (component: component.propagatedBuildInputs or []) cfg.customComponents);
   }));
+
+  # Create a directory that holds all lovelace modules
+  customLovelaceModulesDir = pkgs.buildEnv {
+    name = "home-assistant-custom-lovelace-modules";
+    paths = cfg.customLovelaceModules;
+  };
+
+  # Create parts of the lovelace config that reference lovelave modules as resources
+  customLovelaceModulesResources = {
+    lovelace.resources = map (card: {
+      url = "/local/nixos-lovelace-modules/${card.entrypoint or card.pname}.js?${card.version}";
+      type = "module";
+    }) cfg.customLovelaceModules;
+  };
 in {
   imports = [
     # Migrations in NixOS 22.05
@@ -137,6 +154,41 @@ in {
       '';
     };
 
+    customComponents = mkOption {
+      type = types.listOf types.package;
+      default = [];
+      example = literalExpression ''
+        with pkgs.home-assistant-custom-components; [
+          prometheus-sensor
+        ];
+      '';
+      description = lib.mdDoc ''
+        List of custom component packages to install.
+
+        Available components can be found below `pkgs.home-assistant-custom-components`.
+      '';
+    };
+
+    customLovelaceModules = mkOption {
+      type = types.listOf types.package;
+      default = [];
+      example = literalExpression ''
+        with pkgs.home-assistant-custom-lovelace-modules; [
+          mini-graph-card
+          mini-media-player
+        ];
+      '';
+      description = lib.mdDoc ''
+        List of custom lovelace card packages to load as lovelace resources.
+
+        Available cards can be found below `pkgs.home-assistant-custom-lovelace-modules`.
+
+        ::: {.note}
+        Automatic loading only works with lovelace in `yaml` mode.
+        :::
+      '';
+    };
+
     config = mkOption {
       type = types.nullOr (types.submodule {
         freeformType = format.type;
@@ -408,9 +460,35 @@ in {
           rm -f "${cfg.configDir}/ui-lovelace.yaml"
           ln -s /etc/home-assistant/ui-lovelace.yaml "${cfg.configDir}/ui-lovelace.yaml"
         '';
+        copyCustomLovelaceModules = if cfg.customLovelaceModules != [] then ''
+          mkdir -p "${cfg.configDir}/www"
+          ln -fns ${customLovelaceModulesDir} "${cfg.configDir}/www/nixos-lovelace-modules"
+        '' else ''
+          rm -f "${cfg.configDir}/www/nixos-lovelace-modules"
+        '';
+        copyCustomComponents = ''
+          mkdir -p "${cfg.configDir}/custom_components"
+
+          # remove components symlinked in from below the /nix/store
+          components="$(find "${cfg.configDir}/custom_components" -maxdepth 1 -type l)"
+          for component in "$components"; do
+            if [[ "$(readlink "$component")" =~ ^${escapeShellArg builtins.storeDir} ]]; then
+              rm "$component"
+            fi
+          done
+
+          # recreate symlinks for desired components
+          declare -a components=(${escapeShellArgs cfg.customComponents})
+          for component in "''${components[@]}"; do
+            path="$(dirname $(find "$component" -name "manifest.json"))"
+            ln -fns "$path" "${cfg.configDir}/custom_components/"
+          done
+        '';
       in
         (optionalString (cfg.config != null) copyConfig) +
-        (optionalString (cfg.lovelaceConfig != null) copyLovelaceConfig)
+        (optionalString (cfg.lovelaceConfig != null) copyLovelaceConfig) +
+        copyCustomLovelaceModules +
+        copyCustomComponents
       ;
       environment.PYTHONPATH = package.pythonPath;
       serviceConfig = let
diff --git a/nixos/modules/services/logging/logstash.nix b/nixos/modules/services/logging/logstash.nix
index e9e3ae1f14ce7..42d52a61639e2 100644
--- a/nixos/modules/services/logging/logstash.nix
+++ b/nixos/modules/services/logging/logstash.nix
@@ -123,7 +123,7 @@ in
         example = ''
           if [type] == "syslog" {
             # Keep only relevant systemd fields
-            # http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html
+            # https://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html
             prune {
               whitelist_names => [
                 "type", "@timestamp", "@version",
diff --git a/nixos/modules/services/mail/listmonk.nix b/nixos/modules/services/mail/listmonk.nix
index 11b2a5186229b..cea1bc9560815 100644
--- a/nixos/modules/services/mail/listmonk.nix
+++ b/nixos/modules/services/mail/listmonk.nix
@@ -168,7 +168,7 @@ in {
 
       ensureUsers = [{
         name = "listmonk";
-        ensurePermissions = { "DATABASE listmonk" = "ALL PRIVILEGES"; };
+        ensureDBOwnership = true;
       }];
 
       ensureDatabases = [ "listmonk" ];
diff --git a/nixos/modules/services/mail/mailman.nix b/nixos/modules/services/mail/mailman.nix
index a7e8aee1f2a26..76035625fbe17 100644
--- a/nixos/modules/services/mail/mailman.nix
+++ b/nixos/modules/services/mail/mailman.nix
@@ -493,6 +493,9 @@ in {
           RuntimeDirectory = "mailman";
           LogsDirectory = "mailman";
           PIDFile = "/run/mailman/master.pid";
+          Restart = "on-failure";
+          TimeoutStartSec = 180;
+          TimeoutStopSec = 180;
         };
       };
 
@@ -596,6 +599,7 @@ in {
           User = cfg.webUser;
           Group = "mailman";
           RuntimeDirectory = "mailman-uwsgi";
+          Restart = "on-failure";
         };
       });
 
@@ -620,6 +624,7 @@ in {
           User = cfg.webUser;
           Group = "mailman";
           WorkingDirectory = "/var/lib/mailman-web";
+          Restart = "on-failure";
         };
       };
     } // flip lib.mapAttrs' {
diff --git a/nixos/modules/services/mail/roundcube.nix b/nixos/modules/services/mail/roundcube.nix
index 22a4e3c451ab1..4e29f567ed92c 100644
--- a/nixos/modules/services/mail/roundcube.nix
+++ b/nixos/modules/services/mail/roundcube.nix
@@ -179,14 +179,22 @@ in
       };
     };
 
+    assertions = [
+      {
+        assertion = localDB -> cfg.database.username == cfg.database.dbname;
+        message = ''
+          When setting up a DB and its owner user, the owner and the DB name must be
+          equal!
+        '';
+      }
+    ];
+
     services.postgresql = mkIf localDB {
       enable = true;
       ensureDatabases = [ cfg.database.dbname ];
       ensureUsers = [ {
         name = cfg.database.username;
-        ensurePermissions = {
-          "DATABASE ${cfg.database.username}" = "ALL PRIVILEGES";
-        };
+        ensureDBOwnership = true;
       } ];
     };
 
diff --git a/nixos/modules/services/mail/sympa.nix b/nixos/modules/services/mail/sympa.nix
index 7a5047b2bea54..04ae46f66eeaf 100644
--- a/nixos/modules/services/mail/sympa.nix
+++ b/nixos/modules/services/mail/sympa.nix
@@ -218,7 +218,7 @@ in
         default = null;
         example = "/run/keys/sympa-dbpassword";
         description = lib.mdDoc ''
-          A file containing the password for {option}`services.sympa.database.user`.
+          A file containing the password for {option}`services.sympa.database.name`.
         '';
       };
 
@@ -342,6 +342,7 @@ in
 
       db_type = cfg.database.type;
       db_name = cfg.database.name;
+      db_user = cfg.database.name;
     }
     // (optionalAttrs (cfg.database.host != null) {
       db_host = cfg.database.host;
@@ -355,9 +356,6 @@ in
     // (optionalAttrs (cfg.database.port != null) {
       db_port = cfg.database.port;
     })
-    // (optionalAttrs (cfg.database.user != null) {
-      db_user = cfg.database.user;
-    })
     // (optionalAttrs (cfg.mta.type == "postfix") {
       sendmail_aliases = "${dataDir}/sympa_transport";
       aliases_program  = "${pkgs.postfix}/bin/postmap";
@@ -393,7 +391,7 @@ in
     users.groups.${group} = {};
 
     assertions = [
-      { assertion = cfg.database.createLocally -> cfg.database.user == user;
+      { assertion = cfg.database.createLocally -> cfg.database.user == user && cfg.database.name == cfg.database.user;
         message = "services.sympa.database.user must be set to ${user} if services.sympa.database.createLocally is set to true";
       }
       { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
@@ -579,7 +577,7 @@ in
       ensureDatabases = [ cfg.database.name ];
       ensureUsers = [
         { name = cfg.database.user;
-          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/matrix/matrix-sliding-sync.nix b/nixos/modules/services/matrix/matrix-sliding-sync.nix
index 9807cde409197..84bb38f35aeb0 100644
--- a/nixos/modules/services/matrix/matrix-sliding-sync.nix
+++ b/nixos/modules/services/matrix/matrix-sliding-sync.nix
@@ -74,9 +74,9 @@ in
     services.postgresql = lib.optionalAttrs cfg.createDatabase {
       enable = true;
       ensureDatabases = [ "matrix-sliding-sync" ];
-      ensureUsers = [ rec {
+      ensureUsers = [ {
         name = "matrix-sliding-sync";
-        ensurePermissions."DATABASE \"${name}\"" = "ALL PRIVILEGES";
+        ensureDBOwnership = true;
       } ];
     };
 
diff --git a/nixos/modules/services/matrix/mautrix-facebook.nix b/nixos/modules/services/matrix/mautrix-facebook.nix
index 671040500df84..d7cf024bb8077 100644
--- a/nixos/modules/services/matrix/mautrix-facebook.nix
+++ b/nixos/modules/services/matrix/mautrix-facebook.nix
@@ -135,9 +135,7 @@ in {
       ensureDatabases = ["mautrix-facebook"];
       ensureUsers = [{
         name = "mautrix-facebook";
-        ensurePermissions = {
-          "DATABASE \"mautrix-facebook\"" = "ALL PRIVILEGES";
-        };
+        ensureDBOwnership = true;
       }];
     };
 
diff --git a/nixos/modules/services/misc/amazon-ssm-agent.nix b/nixos/modules/services/misc/amazon-ssm-agent.nix
index 0be79e759c319..02e44c73d87a6 100644
--- a/nixos/modules/services/misc/amazon-ssm-agent.nix
+++ b/nixos/modules/services/misc/amazon-ssm-agent.nix
@@ -33,19 +33,22 @@ in {
   };
 
   config = mkIf cfg.enable {
+    # See https://github.com/aws/amazon-ssm-agent/blob/mainline/packaging/linux/amazon-ssm-agent.service
     systemd.services.amazon-ssm-agent = {
       inherit (cfg.package.meta) description;
-      after    = [ "network.target" ];
+      after    = [ "network-online.target" ];
       wantedBy = [ "multi-user.target" ];
 
       path = [ fake-lsb-release pkgs.coreutils ];
+
       serviceConfig = {
         ExecStart = "${cfg.package}/bin/amazon-ssm-agent";
         KillMode = "process";
         # We want this restating pretty frequently. It could be our only means
         # of accessing the instance.
         Restart = "always";
-        RestartSec = "1min";
+        RestartPreventExitStatus = 194;
+        RestartSec = "90";
       };
     };
 
@@ -70,7 +73,7 @@ in {
       group = "ssm-user";
     };
 
-    environment.etc."amazon/ssm/seelog.xml".source = "${cfg.package}/seelog.xml.template";
+    environment.etc."amazon/ssm/seelog.xml".source = "${cfg.package}/etc/amazon/ssm/seelog.xml.template";
 
     environment.etc."amazon/ssm/amazon-ssm-agent.json".source =  "${cfg.package}/etc/amazon/ssm/amazon-ssm-agent.json.template";
 
diff --git a/nixos/modules/services/misc/apache-kafka.nix b/nixos/modules/services/misc/apache-kafka.nix
index 598907aaf1c61..44db77ac754ce 100644
--- a/nixos/modules/services/misc/apache-kafka.nix
+++ b/nixos/modules/services/misc/apache-kafka.nix
@@ -5,75 +5,117 @@ with lib;
 let
   cfg = config.services.apache-kafka;
 
-  serverProperties =
-    if cfg.serverProperties != null then
-      cfg.serverProperties
-    else
-      ''
-        # Generated by nixos
-        broker.id=${toString cfg.brokerId}
-        port=${toString cfg.port}
-        host.name=${cfg.hostname}
-        log.dirs=${concatStringsSep "," cfg.logDirs}
-        zookeeper.connect=${cfg.zookeeper}
-        ${toString cfg.extraProperties}
-      '';
+  # The `javaProperties` generator takes care of various escaping rules and
+  # generation of the properties file, but we'll handle stringly conversion
+  # ourselves in mkPropertySettings and stringlySettings, since we know more
+  # about the specifically allowed format eg. for lists of this type, and we
+  # don't want to coerce-downsample values to str too early by having the
+  # coercedTypes from javaProperties directly in our NixOS option types.
+  #
+  # Make sure every `freeformType` and any specific option type in `settings` is
+  # supported here.
+
+  mkPropertyString = let
+    render = {
+      bool = boolToString;
+      int = toString;
+      list = concatMapStringsSep "," mkPropertyString;
+      string = id;
+    };
+  in
+    v: render.${builtins.typeOf v} v;
 
-  serverConfig = pkgs.writeText "server.properties" serverProperties;
-  logConfig = pkgs.writeText "log4j.properties" cfg.log4jProperties;
+  stringlySettings = mapAttrs (_: mkPropertyString)
+    (filterAttrs (_: v:  v != null) cfg.settings);
 
+  generator = (pkgs.formats.javaProperties {}).generate;
 in {
 
   options.services.apache-kafka = {
-    enable = mkOption {
-      description = lib.mdDoc "Whether to enable Apache Kafka.";
-      default = false;
-      type = types.bool;
-    };
-
-    brokerId = mkOption {
-      description = lib.mdDoc "Broker ID.";
-      default = -1;
-      type = types.int;
-    };
+    enable = mkEnableOption (lib.mdDoc "Apache Kafka event streaming broker");
 
-    port = mkOption {
-      description = lib.mdDoc "Port number the broker should listen on.";
-      default = 9092;
-      type = types.port;
+    settings = mkOption {
+      description = lib.mdDoc ''
+        [Kafka broker configuration](https://kafka.apache.org/documentation.html#brokerconfigs)
+        {file}`server.properties`.
+
+        Note that .properties files contain mappings from string to string.
+        Keys with dots are NOT represented by nested attrs in these settings,
+        but instead as quoted strings (ie. `settings."broker.id"`, NOT
+        `settings.broker.id`).
+     '';
+      type = types.submodule {
+        freeformType = with types; let
+          primitive = oneOf [bool int str];
+        in lazyAttrsOf (nullOr (either primitive (listOf primitive)));
+
+        options = {
+          "broker.id" = mkOption {
+            description = lib.mdDoc "Broker ID. -1 or null to auto-allocate in zookeeper mode.";
+            default = null;
+            type = with types; nullOr int;
+          };
+
+          "log.dirs" = mkOption {
+            description = lib.mdDoc "Log file directories.";
+            # Deliberaly leave out old default and use the rewrite opportunity
+            # to have users choose a safer value -- /tmp might be volatile and is a
+            # slightly scary default choice.
+            # default = [ "/tmp/apache-kafka" ];
+            type = with types; listOf path;
+          };
+
+          "listeners" = mkOption {
+            description = lib.mdDoc ''
+              Kafka Listener List.
+              See [listeners](https://kafka.apache.org/documentation/#brokerconfigs_listeners).
+            '';
+            type = types.listOf types.str;
+            default = [ "PLAINTEXT://localhost:9092" ];
+          };
+        };
+      };
     };
 
-    hostname = mkOption {
-      description = lib.mdDoc "Hostname the broker should bind to.";
-      default = "localhost";
-      type = types.str;
+    clusterId = mkOption {
+      description = lib.mdDoc ''
+        KRaft mode ClusterId used for formatting log directories. Can be generated with `kafka-storage.sh random-uuid`
+      '';
+      type = with types; nullOr str;
+      default = null;
     };
 
-    logDirs = mkOption {
-      description = lib.mdDoc "Log file directories";
-      default = [ "/tmp/kafka-logs" ];
-      type = types.listOf types.path;
+    configFiles.serverProperties = mkOption {
+      description = lib.mdDoc ''
+        Kafka server.properties configuration file path.
+        Defaults to the rendered `settings`.
+      '';
+      type = types.path;
     };
 
-    zookeeper = mkOption {
-      description = lib.mdDoc "Zookeeper connection string";
-      default = "localhost:2181";
-      type = types.str;
+    configFiles.log4jProperties = mkOption {
+      description = lib.mdDoc "Kafka log4j property configuration file path";
+      type = types.path;
+      default = pkgs.writeText "log4j.properties" cfg.log4jProperties;
+      defaultText = ''pkgs.writeText "log4j.properties" cfg.log4jProperties'';
     };
 
-    extraProperties = mkOption {
-      description = lib.mdDoc "Extra properties for server.properties.";
-      type = types.nullOr types.lines;
-      default = null;
+    formatLogDirs = mkOption {
+      description = lib.mdDoc ''
+        Whether to format log dirs in KRaft mode if all log dirs are
+        unformatted, ie. they contain no meta.properties.
+      '';
+      type = types.bool;
+      default = false;
     };
 
-    serverProperties = mkOption {
+    formatLogDirsIgnoreFormatted = mkOption {
       description = lib.mdDoc ''
-        Complete server.properties content. Other server.properties config
-        options will be ignored if this option is used.
+        Whether to ignore already formatted log dirs when formatting log dirs,
+        instead of failing. Useful when replacing or adding disks.
       '';
-      type = types.nullOr types.lines;
-      default = null;
+      type = types.bool;
+      default = false;
     };
 
     log4jProperties = mkOption {
@@ -112,40 +154,70 @@ in {
       defaultText = literalExpression "pkgs.apacheKafka.passthru.jre";
       type = types.package;
     };
-
   };
 
-  config = mkIf cfg.enable {
+  imports = [
+    (mkRenamedOptionModule
+      [ "services" "apache-kafka" "brokerId" ]
+      [ "services" "apache-kafka" "settings" ''broker.id'' ])
+    (mkRenamedOptionModule
+      [ "services" "apache-kafka" "logDirs" ]
+      [ "services" "apache-kafka" "settings" ''log.dirs'' ])
+    (mkRenamedOptionModule
+      [ "services" "apache-kafka" "zookeeper" ]
+      [ "services" "apache-kafka" "settings" ''zookeeper.connect'' ])
+
+    (mkRemovedOptionModule [ "services" "apache-kafka" "port" ]
+      "Please see services.apache-kafka.settings.listeners and its documentation instead")
+    (mkRemovedOptionModule [ "services" "apache-kafka" "hostname" ]
+      "Please see services.apache-kafka.settings.listeners and its documentation instead")
+    (mkRemovedOptionModule [ "services" "apache-kafka" "extraProperties" ]
+      "Please see services.apache-kafka.settings and its documentation instead")
+    (mkRemovedOptionModule [ "services" "apache-kafka" "serverProperties" ]
+      "Please see services.apache-kafka.settings and its documentation instead")
+  ];
 
-    environment.systemPackages = [cfg.package];
+  config = mkIf cfg.enable {
+    services.apache-kafka.configFiles.serverProperties = generator "server.properties" stringlySettings;
 
     users.users.apache-kafka = {
       isSystemUser = true;
       group = "apache-kafka";
       description = "Apache Kafka daemon user";
-      home = head cfg.logDirs;
     };
     users.groups.apache-kafka = {};
 
-    systemd.tmpfiles.rules = map (logDir: "d '${logDir}' 0700 apache-kafka - - -") cfg.logDirs;
+    systemd.tmpfiles.rules = map (logDir: "d '${logDir}' 0700 apache-kafka - - -") cfg.settings."log.dirs";
 
     systemd.services.apache-kafka = {
       description = "Apache Kafka Daemon";
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
+      preStart = mkIf cfg.formatLogDirs
+        (if cfg.formatLogDirsIgnoreFormatted then ''
+          ${cfg.package}/bin/kafka-storage.sh format -t "${cfg.clusterId}" -c ${cfg.configFiles.serverProperties} --ignore-formatted
+        '' else ''
+          if ${concatMapStringsSep " && " (l: ''[ ! -f "${l}/meta.properties" ]'') cfg.settings."log.dirs"}; then
+            ${cfg.package}/bin/kafka-storage.sh format -t "${cfg.clusterId}" -c ${cfg.configFiles.serverProperties}
+          fi
+        '');
       serviceConfig = {
         ExecStart = ''
           ${cfg.jre}/bin/java \
             -cp "${cfg.package}/libs/*" \
-            -Dlog4j.configuration=file:${logConfig} \
+            -Dlog4j.configuration=file:${cfg.configFiles.log4jProperties} \
             ${toString cfg.jvmOptions} \
             kafka.Kafka \
-            ${serverConfig}
+            ${cfg.configFiles.serverProperties}
         '';
         User = "apache-kafka";
         SuccessExitStatus = "0 143";
       };
     };
-
   };
+
+  meta.doc = ./kafka.md;
+  meta.maintainers = with lib.maintainers; [
+    srhb
+  ];
 }
diff --git a/nixos/modules/services/misc/atuin.nix b/nixos/modules/services/misc/atuin.nix
index 8d2c1b5242ffa..2d6ffc510ce55 100644
--- a/nixos/modules/services/misc/atuin.nix
+++ b/nixos/modules/services/misc/atuin.nix
@@ -73,9 +73,7 @@ in
       enable = true;
       ensureUsers = [{
         name = "atuin";
-        ensurePermissions = {
-          "DATABASE atuin" = "ALL PRIVILEGES";
-        };
+        ensureDBOwnership = true;
       }];
       ensureDatabases = [ "atuin" ];
     };
diff --git a/nixos/modules/services/misc/autofs.nix b/nixos/modules/services/misc/autofs.nix
index 55ab15ff003d1..723b67e8bb6bc 100644
--- a/nixos/modules/services/misc/autofs.nix
+++ b/nixos/modules/services/misc/autofs.nix
@@ -74,7 +74,7 @@ in
 
   config = mkIf cfg.enable {
 
-    boot.kernelModules = [ "autofs4" ];
+    boot.kernelModules = [ "autofs" ];
 
     systemd.services.autofs =
       { description = "Automounts filesystems on demand";
diff --git a/nixos/modules/services/misc/forgejo.md b/nixos/modules/services/misc/forgejo.md
new file mode 100644
index 0000000000000..3df8bc20976ad
--- /dev/null
+++ b/nixos/modules/services/misc/forgejo.md
@@ -0,0 +1,79 @@
+# Forgejo {#module-forgejo}
+
+Forgejo is a soft-fork of gitea, with strong community focus, as well
+as on self-hosting and federation. [Codeberg](https://codeberg.org) is
+deployed from it.
+
+See [upstream docs](https://forgejo.org/docs/latest/).
+
+The method of choice for running forgejo is using [`services.forgejo`](#opt-services.forgejo.enable).
+
+::: {.warning}
+Running forgejo using `services.gitea.package = pkgs.forgejo` is no longer
+recommended.
+If you experience issues with your instance using `services.gitea`,
+**DO NOT** report them to the `services.gitea` module maintainers.
+**DO** report them to the `services.forgejo` module maintainers instead.
+:::
+
+## Migration from Gitea {#module-forgejo-migration-gitea}
+
+::: {.note}
+Migrating is, while not strictly necessary at this point, highly recommended.
+Both modules and projects are likely to divide further with each release.
+Which might lead to an even more involved migration.
+:::
+
+### Full-Migration {#module-forgejo-migration-gitea-default}
+
+This will migrate the state directory (data), rename and chown the database and
+delete the gitea user.
+
+::: {.note}
+This will also change the git remote ssh-url user from `gitea@` to `forgejo@`,
+when using the host's openssh server (default) instead of the integrated one.
+:::
+
+Instructions for PostgreSQL (default). Adapt accordingly for other databases:
+
+```sh
+systemctl stop gitea
+mv /var/lib/gitea /var/lib/forgejo
+runuser -u postgres -- psql -c '
+  ALTER USER gitea RENAME TO forgejo;
+  ALTER DATABASE gitea RENAME TO forgejo;
+'
+nixos-rebuild switch
+systemctl stop forgejo
+chown -R forgejo:forgejo /var/lib/forgejo
+systemctl restart forgejo
+```
+
+### Alternatively, keeping the gitea user {#module-forgejo-migration-gitea-impersonate}
+
+Alternatively, instead of renaming the database, copying the state folder and
+changing the user, the forgejo module can be set up to re-use the old storage
+locations and database, instead of having to copy or rename them.
+Make sure to disable `services.gitea`, when doing this.
+
+```nix
+services.gitea.enable = false;
+
+services.forgejo = {
+  enable = true;
+  user = "gitea";
+  group = "gitea";
+  stateDir = "/var/lib/gitea";
+  database.name = "gitea";
+  database.user = "gitea";
+};
+
+users.users.gitea = {
+  home = "/var/lib/gitea";
+  useDefaultShell = true;
+  group = "gitea";
+  isSystemUser = true;
+};
+
+users.groups.gitea = {};
+```
diff --git a/nixos/modules/services/misc/forgejo.nix b/nixos/modules/services/misc/forgejo.nix
index 90b5f16f4189b..454febda58938 100644
--- a/nixos/modules/services/misc/forgejo.nix
+++ b/nixos/modules/services/misc/forgejo.nix
@@ -357,6 +357,14 @@ in
         assertion = cfg.database.createDatabase -> useSqlite || cfg.database.user == cfg.user;
         message = "services.forgejo.database.user must match services.forgejo.user if the database is to be automatically provisioned";
       }
+      { assertion = cfg.database.createDatabase && usePostgresql -> cfg.database.user == cfg.database.name;
+        message = ''
+          When creating a database via NixOS, the db user and db name must be equal!
+          If you already have an existing DB+user and this assertion is new, you can safely set
+          `services.forgejo.createDatabase` to `false` because removal of `ensureUsers`
+          and `ensureDatabases` doesn't have any effect.
+        '';
+      }
     ];
 
     services.forgejo.settings = {
@@ -423,22 +431,11 @@ in
       ensureUsers = [
         {
           name = cfg.database.user;
-          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }
       ];
     };
 
-    # Work around 'pq: permission denied for schema public' with postgres v15, until a
-    # solution for `services.postgresql.ensureUsers` is found.
-    # See https://github.com/NixOS/nixpkgs/issues/216989
-    systemd.services.postgresql.postStart = lib.mkIf (
-      usePostgresql
-      && cfg.database.createDatabase
-      && lib.strings.versionAtLeast config.services.postgresql.package.version "15.0"
-    ) (lib.mkAfter ''
-      $PSQL -tAc 'ALTER DATABASE "${cfg.database.name}" OWNER TO "${cfg.database.user}";'
-    '');
-
     services.mysql = optionalAttrs (useMysql && cfg.database.createDatabase) {
       enable = mkDefault true;
       package = mkDefault pkgs.mariadb;
@@ -677,5 +674,6 @@ in
     };
   };
 
+  meta.doc = ./forgejo.md;
   meta.maintainers = with lib.maintainers; [ bendlas emilylange ];
 }
diff --git a/nixos/modules/services/misc/gitea.nix b/nixos/modules/services/misc/gitea.nix
index 3f690f85d623b..be528a2989918 100644
--- a/nixos/modules/services/misc/gitea.nix
+++ b/nixos/modules/services/misc/gitea.nix
@@ -394,6 +394,14 @@ in
       { assertion = cfg.database.createDatabase -> useSqlite || cfg.database.user == cfg.user;
         message = "services.gitea.database.user must match services.gitea.user if the database is to be automatically provisioned";
       }
+      { assertion = cfg.database.createDatabase && usePostgresql -> cfg.database.user == cfg.database.name;
+        message = ''
+          When creating a database via NixOS, the db user and db name must be equal!
+          If you already have an existing DB+user and this assertion is new, you can safely set
+          `services.gitea.createDatabase` to `false` because removal of `ensureUsers`
+          and `ensureDatabases` doesn't have any effect.
+        '';
+      }
     ];
 
     services.gitea.settings = {
@@ -461,7 +469,7 @@ in
       ensureDatabases = [ cfg.database.name ];
       ensureUsers = [
         { name = cfg.database.user;
-          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/misc/kafka.md b/nixos/modules/services/misc/kafka.md
new file mode 100644
index 0000000000000..370bb3b482d2c
--- /dev/null
+++ b/nixos/modules/services/misc/kafka.md
@@ -0,0 +1,63 @@
+# Apache Kafka {#module-services-apache-kafka}
+
+[Apache Kafka](https://kafka.apache.org/) is an open-source distributed event
+streaming platform
+
+## Basic Usage {#module-services-apache-kafka-basic-usage}
+
+The Apache Kafka service is configured almost exclusively through its
+[settings](#opt-services.apache-kafka.settings) option, with each attribute
+corresponding to the [upstream configuration
+manual](https://kafka.apache.org/documentation/#configuration) broker settings.
+
+## KRaft {#module-services-apache-kafka-kraft}
+
+Unlike in Zookeeper mode, Kafka in
+[KRaft](https://kafka.apache.org/documentation/#kraft) mode requires each log
+dir to be "formatted" (which means a cluster-specific a metadata file must
+exist in each log dir)
+
+The upstream intention is for users to execute the [storage
+tool](https://kafka.apache.org/documentation/#kraft_storage) to achieve this,
+but this module contains a few extra options to automate this:
+
+- [](#opt-services.apache-kafka.clusterId)
+- [](#opt-services.apache-kafka.formatLogDirs)
+- [](#opt-services.apache-kafka.formatLogDirsIgnoreFormatted)
+
+## Migrating to settings {#module-services-apache-kafka-migrating-to-settings}
+
+Migrating a cluster to the new `settings`-based changes requires adapting removed options to the corresponding upstream settings.
+
+This means that the upstream [Broker Configs documentation](https://kafka.apache.org/documentation/#brokerconfigs) should be followed closely.
+
+Note that dotted options in the upstream docs do _not_ correspond to nested Nix attrsets, but instead as quoted top level `settings` attributes, as in `services.apache-kafka.settings."broker.id"`, *NOT* `services.apache-kafka.settings.broker.id`.
+
+Care should be taken, especially when migrating clusters from the old module, to ensure that the same intended configuration is reproduced faithfully via `settings`.
+
+To assist in the comparison, the final config can be inspected by building the config file itself, ie. with: `nix-build <nixpkgs/nixos> -A config.services.apache-kafka.configFiles.serverProperties`.
+
+Notable changes to be aware of include:
+
+- Removal of `services.apache-kafka.extraProperties` and `services.apache-kafka.serverProperties`
+  - Translate using arbitrary properties using [](#opt-services.apache-kafka.settings)
+  - [Upstream docs](https://kafka.apache.org/documentation.html#brokerconfigs)
+  - The intention is for all broker properties to be fully representable via [](#opt-services.apache-kafka.settings).
+  - If this is not the case, please do consider raising an issue.
+  - Until it can be remedied, you *can* bail out by using [](#opt-services.apache-kafka.configFiles.serverProperties) to the path of a fully rendered properties file.
+
+- Removal of `services.apache-kafka.hostname` and `services.apache-kafka.port`
+  - Translate using: `services.apache-kafka.settings.listeners`
+  - [Upstream docs](https://kafka.apache.org/documentation.html#brokerconfigs_listeners)
+
+- Removal of `services.apache-kafka.logDirs`
+  - Translate using: `services.apache-kafka.settings."log.dirs"`
+  - [Upstream docs](https://kafka.apache.org/documentation.html#brokerconfigs_log.dirs)
+
+- Removal of `services.apache-kafka.brokerId`
+  - Translate using: `services.apache-kafka.settings."broker.id"`
+  - [Upstream docs](https://kafka.apache.org/documentation.html#brokerconfigs_broker.id)
+
+- Removal of `services.apache-kafka.zookeeper`
+  - Translate using: `services.apache-kafka.settings."zookeeper.connect"`
+  - [Upstream docs](https://kafka.apache.org/documentation.html#brokerconfigs_zookeeper.connect)
diff --git a/nixos/modules/services/misc/mediatomb.nix b/nixos/modules/services/misc/mediatomb.nix
index 632b7caaac403..335b1b684b1a1 100644
--- a/nixos/modules/services/misc/mediatomb.nix
+++ b/nixos/modules/services/misc/mediatomb.nix
@@ -186,7 +186,7 @@ let
   defaultFirewallRules = {
     # udp 1900 port needs to be opened for SSDP (not configurable within
     # mediatomb/gerbera) cf.
-    # http://docs.gerbera.io/en/latest/run.html?highlight=udp%20port#network-setup
+    # https://docs.gerbera.io/en/latest/run.html?highlight=udp%20port#network-setup
     allowedUDPPorts = [ 1900 cfg.port ];
     allowedTCPPorts = [ cfg.port ];
   };
diff --git a/nixos/modules/services/misc/redmine.nix b/nixos/modules/services/misc/redmine.nix
index a296fd3816bbb..20fa71507b6b0 100644
--- a/nixos/modules/services/misc/redmine.nix
+++ b/nixos/modules/services/misc/redmine.nix
@@ -267,7 +267,7 @@ in
       { assertion = cfg.database.passwordFile != null || cfg.database.socket != null;
         message = "one of services.redmine.database.socket or services.redmine.database.passwordFile must be set";
       }
-      { assertion = cfg.database.createLocally -> cfg.database.user == cfg.user;
+      { assertion = cfg.database.createLocally -> cfg.database.user == cfg.user && cfg.database.user == cfg.database.name;
         message = "services.redmine.database.user must be set to ${cfg.user} if services.redmine.database.createLocally is set true";
       }
       { assertion = cfg.database.createLocally -> cfg.database.socket != null;
@@ -315,7 +315,7 @@ in
       ensureDatabases = [ cfg.database.name ];
       ensureUsers = [
         { name = cfg.database.user;
-          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/misc/soft-serve.nix b/nixos/modules/services/misc/soft-serve.nix
index 0f246493880b9..2b63b6bcd8673 100644
--- a/nixos/modules/services/misc/soft-serve.nix
+++ b/nixos/modules/services/misc/soft-serve.nix
@@ -12,7 +12,7 @@ in
 {
   options = {
     services.soft-serve = {
-      enable = mkEnableOption "Enable soft-serve service";
+      enable = mkEnableOption "soft-serve";
 
       package = mkPackageOption pkgs "soft-serve" { };
 
@@ -20,7 +20,7 @@ in
         type = format.type;
         default = { };
         description = mdDoc ''
-          The contents of the configuration file.
+          The contents of the configuration file for soft-serve.
 
           See <${docUrl}>.
         '';
diff --git a/nixos/modules/services/misc/sourcehut/default.nix b/nixos/modules/services/misc/sourcehut/default.nix
index bee9716629722..f2b09f4bc4b61 100644
--- a/nixos/modules/services/misc/sourcehut/default.nix
+++ b/nixos/modules/services/misc/sourcehut/default.nix
@@ -25,7 +25,7 @@ let
       || head srvMatch == srv # Include sections for the service being configured
       then v
       # Enable Web links and integrations between services.
-      else if tail srvMatch == [ null ] && elem (head srvMatch) cfg.services
+      else if tail srvMatch == [ null ] && cfg.${head srvMatch}.enable
       then {
         inherit (v) origin;
         # mansrht crashes without it
@@ -38,9 +38,9 @@ let
       # for services needing access to them.
       "builds.sr.ht::worker".buildlogs = "/var/log/sourcehut/buildsrht-worker";
       "git.sr.ht".post-update-script = "/usr/bin/gitsrht-update-hook";
-      "git.sr.ht".repos = "/var/lib/sourcehut/gitsrht/repos";
+      "git.sr.ht".repos = cfg.settings."git.sr.ht".repos;
       "hg.sr.ht".changegroup-script = "/usr/bin/hgsrht-hook-changegroup";
-      "hg.sr.ht".repos = "/var/lib/sourcehut/hgsrht/repos";
+      "hg.sr.ht".repos = cfg.settings."hg.sr.ht".repos;
       # Making this a per service option despite being in a global section,
       # so that it uses the redis-server used by the service.
       "sr.ht".redis-host = cfg.${srv}.redis.host;
@@ -77,6 +77,14 @@ let
       type = types.path;
       apply = s: "<" + toString s;
     };
+    api-origin = mkOption {
+      description = lib.mdDoc "Origin URL for the API";
+      type = types.str;
+      default = "http://${cfg.listenAddress}:${toString (cfg.${srv}.port + 100)}";
+      defaultText = lib.literalMD ''
+        `"http://''${`[](#opt-services.sourcehut.listenAddress)`}:''${toString (`[](#opt-services.sourcehut.${srv}.port)` + 100)}"`
+      '';
+    };
   };
 
   # Specialized python containing all the modules
@@ -112,15 +120,6 @@ in
       and account management services
     '');
 
-    services = mkOption {
-      type = with types; listOf (enum
-        [ "builds" "git" "hg" "hub" "lists" "man" "meta" "pages" "paste" "todo" ]);
-      defaultText = "locally enabled services";
-      description = lib.mdDoc ''
-        Services that may be displayed as links in the title bar of the Web interface.
-      '';
-    };
-
     listenAddress = mkOption {
       type = types.str;
       default = "localhost";
@@ -400,8 +399,8 @@ in
               This setting is propagated to newer and existing repositories.
             '';
             type = types.str;
-            default = "${cfg.python}/bin/hgsrht-hook-changegroup";
-            defaultText = "\${cfg.python}/bin/hgsrht-hook-changegroup";
+            default = "${pkgs.sourcehut.hgsrht}/bin/hgsrht-hook-changegroup";
+            defaultText = "\${pkgs.sourcehut.hgsrht}/bin/hgsrht-hook-changegroup";
           };
           repos = mkOption {
             description = lib.mdDoc ''
@@ -501,12 +500,6 @@ in
         options."meta.sr.ht" =
           removeAttrs (commonServiceSettings "meta")
             ["oauth-client-id" "oauth-client-secret"] // {
-          api-origin = mkOption {
-            description = lib.mdDoc "Origin URL for API, 100 more than web.";
-            type = types.str;
-            default = "http://${cfg.listenAddress}:${toString (cfg.meta.port + 100)}";
-            defaultText = lib.literalMD ''`"http://''${`[](#opt-services.sourcehut.listenAddress)`}:''${toString (`[](#opt-services.sourcehut.meta.port)` + 100)}"`'';
-          };
           webhooks = mkOption {
             description = lib.mdDoc "The Redis connection used for the webhooks worker.";
             type = types.str;
@@ -784,6 +777,7 @@ in
         extraConfig = ''
           PermitUserEnvironment SRHT_*
         '';
+        startWhenNeeded = false;
       };
       environment.etc."ssh/sourcehut/config.ini".source =
         settingsFormat.generate "sourcehut-dispatch-config.ini"
@@ -792,15 +786,28 @@ in
       environment.etc."ssh/sourcehut/subdir/srht-dispatch" = {
         # sshd_config(5): The program must be owned by root, not writable by group or others
         mode = "0755";
-        source = pkgs.writeShellScript "srht-dispatch" ''
+        source = pkgs.writeShellScript "srht-dispatch-wrapper" ''
           set -e
+          set -x
           cd /etc/ssh/sourcehut/subdir
-          ${cfg.python}/bin/gitsrht-dispatch "$@"
+          ${pkgs.sourcehut.gitsrht}/bin/gitsrht-dispatch "$@"
         '';
       };
+      systemd.tmpfiles.settings."10-sourcehut-gitsrht" = mkIf cfg.git.enable (
+        builtins.listToAttrs (map (name: {
+          name = "/var/log/sourcehut/gitsrht-${name}";
+          value.f = {
+            inherit (cfg.git) user group;
+            mode = "0644";
+          };
+        }) [ "keys" "shell" "update-hook" ])
+      );
       systemd.services.sshd = {
-        #path = optional cfg.git.enable [ cfg.git.package ];
+        preStart = mkIf cfg.hg.enable ''
+          chown ${cfg.hg.user}:${cfg.hg.group} /var/log/sourcehut/hgsrht-keys
+        '';
         serviceConfig = {
+          LogsDirectory = "sourcehut";
           BindReadOnlyPaths =
             # Note that those /usr/bin/* paths are hardcoded in multiple places in *.sr.ht,
             # for instance to get the user from the [git.sr.ht::dispatch] settings.
@@ -813,7 +820,6 @@ in
               "${pkgs.writeShellScript "buildsrht-keys-wrapper" ''
                 set -e
                 cd /run/sourcehut/buildsrht/subdir
-                set -x
                 exec -a "$0" ${pkgs.sourcehut.buildsrht}/bin/buildsrht-keys "$@"
               ''}:/usr/bin/buildsrht-keys"
               "${pkgs.sourcehut.buildsrht}/bin/master-shell:/usr/bin/master-shell"
@@ -825,31 +831,26 @@ in
               "${pkgs.writeShellScript "gitsrht-keys-wrapper" ''
                 set -e
                 cd /run/sourcehut/gitsrht/subdir
-                set -x
                 exec -a "$0" ${pkgs.sourcehut.gitsrht}/bin/gitsrht-keys "$@"
               ''}:/usr/bin/gitsrht-keys"
               "${pkgs.writeShellScript "gitsrht-shell-wrapper" ''
                 set -e
                 cd /run/sourcehut/gitsrht/subdir
-                set -x
+                export PATH="${cfg.git.package}/bin:$PATH"
+                export SRHT_CONFIG=/run/sourcehut/gitsrht/config.ini
                 exec -a "$0" ${pkgs.sourcehut.gitsrht}/bin/gitsrht-shell "$@"
               ''}:/usr/bin/gitsrht-shell"
               "${pkgs.writeShellScript "gitsrht-update-hook" ''
                 set -e
-                test -e "''${PWD%/*}"/config.ini ||
-                # Git hooks are run relative to their repository's directory,
-                # but gitsrht-update-hook looks up ../config.ini
-                ln -s /run/sourcehut/gitsrht/config.ini "''${PWD%/*}"/config.ini
+                export SRHT_CONFIG=/run/sourcehut/gitsrht/config.ini
                 # hooks/post-update calls /usr/bin/gitsrht-update-hook as hooks/stage-3
                 # but this wrapper being a bash script, it overrides $0 with /usr/bin/gitsrht-update-hook
                 # hence this hack to put hooks/stage-3 back into gitsrht-update-hook's $0
                 if test "''${STAGE3:+set}"
                 then
-                  set -x
                   exec -a hooks/stage-3 ${pkgs.sourcehut.gitsrht}/bin/gitsrht-update-hook "$@"
                 else
                   export STAGE3=set
-                  set -x
                   exec -a "$0" ${pkgs.sourcehut.gitsrht}/bin/gitsrht-update-hook "$@"
                 fi
               ''}:/usr/bin/gitsrht-update-hook"
@@ -860,13 +861,11 @@ in
               "${pkgs.writeShellScript "hgsrht-keys-wrapper" ''
                 set -e
                 cd /run/sourcehut/hgsrht/subdir
-                set -x
                 exec -a "$0" ${pkgs.sourcehut.hgsrht}/bin/hgsrht-keys "$@"
               ''}:/usr/bin/hgsrht-keys"
               "${pkgs.writeShellScript "hgsrht-shell-wrapper" ''
                 set -e
                 cd /run/sourcehut/hgsrht/subdir
-                set -x
                 exec -a "$0" ${pkgs.sourcehut.hgsrht}/bin/hgsrht-shell "$@"
               ''}:/usr/bin/hgsrht-shell"
               # Mercurial's changegroup hooks are run relative to their repository's directory,
@@ -875,8 +874,7 @@ in
                 set -e
                 test -e "''$PWD"/config.ini ||
                 ln -s /run/sourcehut/hgsrht/config.ini "''$PWD"/config.ini
-                set -x
-                exec -a "$0" ${cfg.python}/bin/hgsrht-hook-changegroup "$@"
+                exec -a "$0" ${pkgs.sourcehut.hgsrht}/bin/hgsrht-hook-changegroup "$@"
               ''}:/usr/bin/hgsrht-hook-changegroup"
             ];
         };
@@ -1066,10 +1064,11 @@ in
           };
         })
       ];
-      extraServices.gitsrht-api = {
-        serviceConfig.Restart = "always";
-        serviceConfig.RestartSec = "5s";
-        serviceConfig.ExecStart = "${pkgs.sourcehut.gitsrht}/bin/gitsrht-api -b ${cfg.listenAddress}:${toString (cfg.git.port + 100)}";
+      extraServices.gitsrht-api.serviceConfig = {
+        Restart = "always";
+        RestartSec = "5s";
+        ExecStart = "${pkgs.sourcehut.gitsrht}/bin/gitsrht-api -b ${cfg.listenAddress}:${toString (cfg.git.port + 100)}";
+        BindPaths = [ "${cfg.settings."git.sr.ht".repos}:/var/lib/sourcehut/gitsrht/repos" ];
       };
       extraServices.gitsrht-fcgiwrap = mkIf cfg.nginx.enable {
         serviceConfig = {
@@ -1188,7 +1187,7 @@ in
       extraServices.listssrht-lmtp = {
         wants = [ "postfix.service" ];
         unitConfig.JoinsNamespaceOf = optional cfg.postfix.enable "postfix.service";
-        serviceConfig.ExecStart = "${cfg.python}/bin/listssrht-lmtp";
+        serviceConfig.ExecStart = "${pkgs.sourcehut.listssrht}/bin/listssrht-lmtp";
         # Avoid crashing: os.chown(sock, os.getuid(), sock_gid)
         serviceConfig.PrivateUsers = mkForce false;
       };
@@ -1252,55 +1251,30 @@ in
           ) cfg.settings));
         serviceConfig.ExecStart = "${pkgs.sourcehut.metasrht}/bin/metasrht-api -b ${cfg.listenAddress}:${toString (cfg.meta.port + 100)}";
       };
-      extraConfig = mkMerge [
-        {
-          assertions = [
-            { assertion = let s = cfg.settings."meta.sr.ht::billing"; in
-                          s.enabled == "yes" -> (s.stripe-public-key != null && s.stripe-secret-key != null);
-              message = "If meta.sr.ht::billing is enabled, the keys must be defined.";
-            }
-          ];
-          environment.systemPackages = optional cfg.meta.enable
-            (pkgs.writeShellScriptBin "metasrht-manageuser" ''
-              set -eux
-              if test "$(${pkgs.coreutils}/bin/id -n -u)" != '${cfg.meta.user}'
-              then exec sudo -u '${cfg.meta.user}' "$0" "$@"
-              else
-                # In order to load config.ini
-                if cd /run/sourcehut/metasrht
-                then exec ${cfg.python}/bin/metasrht-manageuser "$@"
-                else cat <<EOF
-                  Please run: sudo systemctl start metasrht
-              EOF
-                  exit 1
-                fi
+      extraConfig = {
+        assertions = [
+          { assertion = let s = cfg.settings."meta.sr.ht::billing"; in
+                        s.enabled == "yes" -> (s.stripe-public-key != null && s.stripe-secret-key != null);
+            message = "If meta.sr.ht::billing is enabled, the keys must be defined.";
+          }
+        ];
+        environment.systemPackages = optional cfg.meta.enable
+          (pkgs.writeShellScriptBin "metasrht-manageuser" ''
+            set -eux
+            if test "$(${pkgs.coreutils}/bin/id -n -u)" != '${cfg.meta.user}'
+            then exec sudo -u '${cfg.meta.user}' "$0" "$@"
+            else
+              # In order to load config.ini
+              if cd /run/sourcehut/metasrht
+              then exec ${pkgs.sourcehut.metasrht}/bin/metasrht-manageuser "$@"
+              else cat <<EOF
+                Please run: sudo systemctl start metasrht
+            EOF
+                exit 1
               fi
-            '');
-        }
-        (mkIf cfg.nginx.enable {
-          services.nginx.virtualHosts."meta.${domain}" = {
-            locations."/query" = {
-              proxyPass = cfg.settings."meta.sr.ht".api-origin;
-              extraConfig = ''
-                if ($request_method = 'OPTIONS') {
-                  add_header 'Access-Control-Allow-Origin' '*';
-                  add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
-                  add_header 'Access-Control-Allow-Headers' 'User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
-                  add_header 'Access-Control-Max-Age' 1728000;
-                  add_header 'Content-Type' 'text/plain; charset=utf-8';
-                  add_header 'Content-Length' 0;
-                  return 204;
-                }
-
-                add_header 'Access-Control-Allow-Origin' '*';
-                add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
-                add_header 'Access-Control-Allow-Headers' 'User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
-                add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
-              '';
-            };
-          };
-        })
-      ];
+            fi
+          '');
+      };
     })
 
     (import ./service.nix "pages" {
@@ -1356,7 +1330,7 @@ in
       extraServices.todosrht-lmtp = {
         wants = [ "postfix.service" ];
         unitConfig.JoinsNamespaceOf = optional cfg.postfix.enable "postfix.service";
-        serviceConfig.ExecStart = "${cfg.python}/bin/todosrht-lmtp";
+        serviceConfig.ExecStart = "${pkgs.sourcehut.todosrht}/bin/todosrht-lmtp";
         # Avoid crashing: os.chown(sock, os.getuid(), sock_gid)
         serviceConfig.PrivateUsers = mkForce false;
       };
@@ -1388,6 +1362,10 @@ in
         dispatch is deprecated. See https://sourcehut.org/blog/2022-08-01-dispatch-deprecation-plans/
         for more information.
     '')
+
+    (mkRemovedOptionModule [ "services" "sourcehut" "services"] ''
+        This option was removed in favor of individual <service>.enable flags.
+    '')
   ];
 
   meta.doc = ./default.md;
diff --git a/nixos/modules/services/misc/sourcehut/service.nix b/nixos/modules/services/misc/sourcehut/service.nix
index aae13e0cc2c92..f08d5eb468719 100644
--- a/nixos/modules/services/misc/sourcehut/service.nix
+++ b/nixos/modules/services/misc/sourcehut/service.nix
@@ -108,7 +108,7 @@ let
       #SocketBindDeny = "any";
       SystemCallFilter = [
         "@system-service"
-        "~@aio" "~@keyring" "~@memlock" "~@privileged" "~@resources" "~@timer"
+        "~@aio" "~@keyring" "~@memlock" "~@privileged" "~@timer"
         "@chown" "@setuid"
       ];
       SystemCallArchitectures = "native";
@@ -222,6 +222,23 @@ in
             expires 30d;
           '';
         };
+        locations."/query" = mkIf (cfg.settings.${iniKey} ? api-origin) {
+          proxyPass = cfg.settings.${iniKey}.api-origin;
+          extraConfig = ''
+            add_header 'Access-Control-Allow-Origin' '*';
+            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
+            add_header 'Access-Control-Allow-Headers' 'User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
+
+            if ($request_method = 'OPTIONS') {
+              add_header 'Access-Control-Max-Age' 1728000;
+              add_header 'Content-Type' 'text/plain; charset=utf-8';
+              add_header 'Content-Length' 0;
+              return 204;
+            }
+
+            add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
+          '';
+        };
       } cfg.nginx.virtualHost ];
     };
 
@@ -232,12 +249,12 @@ in
       ensureDatabases = [ srvCfg.postgresql.database ];
       ensureUsers = map (name: {
           inherit name;
-          ensurePermissions = { "DATABASE \"${srvCfg.postgresql.database}\"" = "ALL PRIVILEGES"; };
+          # We don't use it because we have a special default database name with dots.
+          # TODO(for maintainers of sourcehut): migrate away from custom preStart script.
+          ensureDBOwnership = false;
         }) [srvCfg.user];
     };
 
-    services.sourcehut.services = mkDefault (filter (s: cfg.${s}.enable)
-      [ "builds" "dispatch" "git" "hg" "hub" "lists" "man" "meta" "pages" "paste" "todo" ]);
 
     services.sourcehut.settings = mkMerge [
       {
@@ -363,6 +380,21 @@ in
         }
         extraService
       ])) extraServices)
+
+      # Work around 'pq: permission denied for schema public' with postgres v15.
+      # See https://github.com/NixOS/nixpkgs/issues/216989
+      # Workaround taken from nixos/forgejo: https://github.com/NixOS/nixpkgs/pull/262741
+      # TODO(to maintainers of sourcehut): please migrate away from this workaround
+      # by migrating away from database name defaults with dots.
+      (lib.mkIf (
+          cfg.postgresql.enable
+          && lib.strings.versionAtLeast config.services.postgresql.package.version "15.0"
+        ) {
+          postgresql.postStart = (lib.mkAfter ''
+            $PSQL -tAc 'ALTER DATABASE "${srvCfg.postgresql.database}" OWNER TO "${srvCfg.user}";'
+          '');
+        }
+      )
     ];
 
     systemd.timers = mapAttrs (timerName: timer:
diff --git a/nixos/modules/services/monitoring/bosun.nix b/nixos/modules/services/monitoring/bosun.nix
index dc75fda6ed8aa..1dc19743461bb 100644
--- a/nixos/modules/services/monitoring/bosun.nix
+++ b/nixos/modules/services/monitoring/bosun.nix
@@ -108,7 +108,7 @@ in {
           option.
 
           A detailed description of the supported syntax can be found at-spi2-atk
-          http://bosun.org/configuration.html
+          https://bosun.org/configuration.html
         '';
       };
 
diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix
index e90a0e9d16db6..f84d677f14d85 100644
--- a/nixos/modules/services/monitoring/grafana.nix
+++ b/nixos/modules/services/monitoring/grafana.nix
@@ -88,7 +88,7 @@ let
   # Get a submodule without any embedded metadata:
   _filter = x: filterAttrs (k: v: k != "_module") x;
 
-  # http://docs.grafana.org/administration/provisioning/#datasources
+  # https://grafana.com/docs/grafana/latest/administration/provisioning/#datasources
   grafanaTypes.datasourceConfig = types.submodule {
     freeformType = provisioningSettingsFormat.type;
 
@@ -140,7 +140,7 @@ let
     };
   };
 
-  # http://docs.grafana.org/administration/provisioning/#dashboards
+  # https://grafana.com/docs/grafana/latest/administration/provisioning/#dashboards
   grafanaTypes.dashboardConfig = types.submodule {
     freeformType = provisioningSettingsFormat.type;
 
@@ -1841,6 +1841,7 @@ in
       serviceConfig = {
         WorkingDirectory = cfg.dataDir;
         User = "grafana";
+        Restart = "on-failure";
         RuntimeDirectory = "grafana";
         RuntimeDirectoryMode = "0755";
         # Hardening
diff --git a/nixos/modules/services/monitoring/graphite.nix b/nixos/modules/services/monitoring/graphite.nix
index 65c91b8f79bb6..cc3d70976204d 100644
--- a/nixos/modules/services/monitoring/graphite.nix
+++ b/nixos/modules/services/monitoring/graphite.nix
@@ -102,7 +102,7 @@ in {
         default = "";
         description = lib.mdDoc ''
           Graphite webapp settings. See:
-          <http://graphite.readthedocs.io/en/latest/config-local-settings.html>
+          <https://graphite.readthedocs.io/en/latest/config-local-settings.html>
         '';
       };
     };
diff --git a/nixos/modules/services/monitoring/munin.nix b/nixos/modules/services/monitoring/munin.nix
index 90a51181ac30b..5ed7cac48ae71 100644
--- a/nixos/modules/services/monitoring/munin.nix
+++ b/nixos/modules/services/monitoring/munin.nix
@@ -4,7 +4,7 @@
 # TODO: LWP/Pg perl libs aren't recognized
 
 # TODO: support fastcgi
-# http://guide.munin-monitoring.org/en/latest/example/webserver/apache-cgi.html
+# https://guide.munin-monitoring.org/en/latest/example/webserver/apache-cgi.html
 # spawn-fcgi -s /run/munin/fastcgi-graph.sock -U www-data   -u munin -g munin /usr/lib/munin/cgi/munin-cgi-graph
 # spawn-fcgi -s /run/munin/fastcgi-html.sock  -U www-data   -u munin -g munin /usr/lib/munin/cgi/munin-cgi-html
 # https://paste.sh/vofcctHP#-KbDSXVeWoifYncZmLfZzgum
@@ -147,7 +147,7 @@ in
           Enable Munin Node agent. Munin node listens on 0.0.0.0 and
           by default accepts connections only from 127.0.0.1 for security reasons.
 
-          See <http://guide.munin-monitoring.org/en/latest/architecture/index.html>.
+          See <https://guide.munin-monitoring.org/en/latest/architecture/index.html>.
         '';
       };
 
@@ -156,7 +156,7 @@ in
         type = types.lines;
         description = lib.mdDoc ''
           {file}`munin-node.conf` extra configuration. See
-          <http://guide.munin-monitoring.org/en/latest/reference/munin-node.conf.html>
+          <https://guide.munin-monitoring.org/en/latest/reference/munin-node.conf.html>
         '';
       };
 
@@ -165,7 +165,7 @@ in
         type = types.lines;
         description = lib.mdDoc ''
           {file}`plugin-conf.d` extra plugin configuration. See
-          <http://guide.munin-monitoring.org/en/latest/plugin/use.html>
+          <https://guide.munin-monitoring.org/en/latest/plugin/use.html>
         '';
         example = ''
           [fail2ban_*]
@@ -273,9 +273,9 @@ in
         type = types.lines;
         description = lib.mdDoc ''
           {file}`munin.conf` extra global configuration.
-          See <http://guide.munin-monitoring.org/en/latest/reference/munin.conf.html>.
+          See <https://guide.munin-monitoring.org/en/latest/reference/munin.conf.html>.
           Useful to setup notifications, see
-          <http://guide.munin-monitoring.org/en/latest/tutorial/alert.html>
+          <https://guide.munin-monitoring.org/en/latest/tutorial/alert.html>
         '';
         example = ''
           contact.email.command mail -s "Munin notification for ''${var:host}" someone@example.com
@@ -288,7 +288,7 @@ in
         description = lib.mdDoc ''
           Definitions of hosts of nodes to collect data from. Needs at least one
           host for cron to succeed. See
-          <http://guide.munin-monitoring.org/en/latest/reference/munin.conf.html>
+          <https://guide.munin-monitoring.org/en/latest/reference/munin.conf.html>
         '';
         example = literalExpression ''
           '''
diff --git a/nixos/modules/services/monitoring/nagios.nix b/nixos/modules/services/monitoring/nagios.nix
index 8feff22c11826..dc5fa1be29228 100644
--- a/nixos/modules/services/monitoring/nagios.nix
+++ b/nixos/modules/services/monitoring/nagios.nix
@@ -88,7 +88,7 @@ in
 
   options = {
     services.nagios = {
-      enable = mkEnableOption (lib.mdDoc ''[Nagios](http://www.nagios.org/) to monitor your system or network.'');
+      enable = mkEnableOption (lib.mdDoc ''[Nagios](https://www.nagios.org/) to monitor your system or network.'');
 
       objectDefs = mkOption {
         description = lib.mdDoc ''
diff --git a/nixos/modules/services/monitoring/ocsinventory-agent.md b/nixos/modules/services/monitoring/ocsinventory-agent.md
new file mode 100644
index 0000000000000..50e246fb6531b
--- /dev/null
+++ b/nixos/modules/services/monitoring/ocsinventory-agent.md
@@ -0,0 +1,33 @@
+# OCS Inventory Agent {#module-services-ocsinventory-agent}
+
+[OCS Inventory NG](https://ocsinventory-ng.org/) or Open Computers and Software inventory
+is an application designed to help IT administrator to keep track of the hardware and software
+configurations of computers that are installed on their network.
+
+OCS Inventory collects information about the hardware and software of networked machines
+through the **OCS Inventory Agent** program.
+
+This NixOS module enables you to install and configure this agent so that it sends information from your computer to the OCS Inventory server.
+
+For more technical information about OCS Inventory Agent, refer to [the Wiki documentation](https://wiki.ocsinventory-ng.org/03.Basic-documentation/Setting-up-the-UNIX-agent-manually-on-client-computers/).
+
+
+## Basic Usage {#module-services-ocsinventory-agent-basic-usage}
+
+A minimal configuration looks like this:
+
+```nix
+{
+  services.ocsinventory-agent = {
+    enable = true;
+    settings = {
+      server = "https://ocsinventory.localhost:8080/ocsinventory";
+      tag = "01234567890123";
+    };
+  };
+}
+```
+
+This configuration will periodically run the ocsinventory-agent SystemD service.
+
+The OCS Inventory Agent will inventory the computer and then sends the results to the specified OCS Inventory Server.
diff --git a/nixos/modules/services/monitoring/ocsinventory-agent.nix b/nixos/modules/services/monitoring/ocsinventory-agent.nix
new file mode 100644
index 0000000000000..7585ae863750b
--- /dev/null
+++ b/nixos/modules/services/monitoring/ocsinventory-agent.nix
@@ -0,0 +1,134 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.ocsinventory-agent;
+
+  settingsFormat = pkgs.formats.keyValue {
+    mkKeyValue = lib.generators.mkKeyValueDefault { } "=";
+  };
+
+in
+{
+  meta = {
+    doc = ./ocsinventory-agent.md;
+    maintainers = with lib.maintainers; [ anthonyroussel ];
+  };
+
+  options = {
+    services.ocsinventory-agent = {
+      enable = lib.mkEnableOption (lib.mdDoc "OCS Inventory Agent");
+
+      package = lib.mkPackageOptionMD pkgs "ocsinventory-agent" { };
+
+      settings = lib.mkOption {
+        type = lib.types.submodule {
+          freeformType = settingsFormat.type.nestedTypes.elemType;
+
+          options = {
+            server = lib.mkOption {
+              type = lib.types.nullOr lib.types.str;
+              example = "https://ocsinventory.localhost:8080/ocsinventory";
+              default = null;
+              description = lib.mdDoc ''
+                The URI of the OCS Inventory server where to send the inventory file.
+
+                This option is ignored if {option}`services.ocsinventory-agent.settings.local` is set.
+              '';
+            };
+
+            local = lib.mkOption {
+              type = lib.types.nullOr lib.types.path;
+              example = "/var/lib/ocsinventory-agent/reports";
+              default = null;
+              description = lib.mdDoc ''
+                If specified, the OCS Inventory Agent will run in offline mode
+                and the resulting inventory file will be stored in the specified path.
+              '';
+            };
+
+            ca = lib.mkOption {
+              type = lib.types.path;
+              default = "/etc/ssl/certs/ca-certificates.crt";
+              description = lib.mdDoc ''
+                Path to CA certificates file in PEM format, for server
+                SSL certificate validation.
+              '';
+            };
+
+            tag = lib.mkOption {
+              type = lib.types.nullOr lib.types.str;
+              default = null;
+              example = "01234567890123";
+              description = lib.mdDoc "Tag for the generated inventory.";
+            };
+
+            debug = lib.mkEnableOption (lib.mdDoc "debug mode");
+          };
+        };
+        default = { };
+        example = {
+          ca = "/etc/ssl/certs/ca-certificates.crt";
+          debug = true;
+          server = "https://ocsinventory.localhost:8080/ocsinventory";
+          tag = "01234567890123";
+        };
+        description = lib.mdDoc ''
+          Configuration for /etc/ocsinventory-agent/ocsinventory-agent.cfg.
+
+          Refer to
+          {manpage}`ocsinventory-agent(1)` for available options.
+        '';
+      };
+
+      interval = lib.mkOption {
+        type = lib.types.str;
+        default = "daily";
+        example = "06:00";
+        description = lib.mdDoc ''
+          How often we run the ocsinventory-agent service. Runs by default every daily.
+
+          The format is described in
+          {manpage}`systemd.time(7)`.
+        '';
+      };
+    };
+  };
+
+  config =
+    let
+      configFile = settingsFormat.generate "ocsinventory-agent.cfg" cfg.settings;
+
+    in lib.mkIf cfg.enable {
+      # Path of the configuration file is hard-coded and cannot be changed
+      # https://github.com/OCSInventory-NG/UnixAgent/blob/v2.10.0/lib/Ocsinventory/Agent/Config.pm#L78
+      #
+      environment.etc."ocsinventory-agent/ocsinventory-agent.cfg".source = configFile;
+
+      systemd.services.ocsinventory-agent = {
+        description = "OCS Inventory Agent service";
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+
+        reloadTriggers = [ configFile ];
+
+        serviceConfig = {
+          ExecStart = lib.getExe cfg.package;
+          ConfigurationDirectory = "ocsinventory-agent";
+          StateDirectory = "ocsinventory-agent";
+        };
+      };
+
+      systemd.timers.ocsinventory-agent = {
+        description = "Launch OCS Inventory Agent regularly";
+        wantedBy = [ "timers.target" ];
+
+        timerConfig = {
+          OnCalendar = cfg.interval;
+          AccuracySec = "1h";
+          RandomizedDelaySec = 240;
+          Persistent = true;
+          Unit = "ocsinventory-agent.service";
+        };
+      };
+    };
+}
diff --git a/nixos/modules/services/monitoring/parsedmarc.nix b/nixos/modules/services/monitoring/parsedmarc.nix
index 44fc359b6a7de..a146e7ab95435 100644
--- a/nixos/modules/services/monitoring/parsedmarc.nix
+++ b/nixos/modules/services/monitoring/parsedmarc.nix
@@ -301,6 +301,7 @@ in
               description = lib.mdDoc ''
                 The addresses to send outgoing mail to.
               '';
+              apply = x: if x == [] then null else lib.concatStringsSep "," x;
             };
           };
 
diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix
index 305f235054be7..39abd293b2d18 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters.nix
@@ -2,8 +2,8 @@
 
 let
   inherit (lib) concatStrings foldl foldl' genAttrs literalExpression maintainers
-                mapAttrsToList mkDefault mkEnableOption mkIf mkMerge mkOption
-                optional types mkOptionDefault flip attrNames;
+    mapAttrs mapAttrsToList mkDefault mkEnableOption mkIf mkMerge mkOption
+    optional types mkOptionDefault flip attrNames;
 
   cfg = config.services.prometheus.exporters;
 
@@ -20,7 +20,7 @@ let
   #  systemd service must be provided by specifying either
   #  `serviceOpts.script` or `serviceOpts.serviceConfig.ExecStart`
 
-  exporterOpts = genAttrs [
+  exporterOpts = (genAttrs [
     "apcupsd"
     "artifactory"
     "bind"
@@ -34,14 +34,15 @@ let
     "domain"
     "dovecot"
     "fastly"
+    "flow"
     "fritzbox"
     "graphite"
     "idrac"
     "imap-mailstat"
     "influxdb"
     "ipmi"
-    "json"
     "jitsi"
+    "json"
     "junos-czerwonk"
     "kea"
     "keylight"
@@ -51,6 +52,7 @@ let
     "mikrotik"
     "minio"
     "modemmanager"
+    "mongodb"
     "mysqld"
     "nextcloud"
     "nginx"
@@ -74,9 +76,9 @@ let
     "scaphandre"
     "script"
     "shelly"
-    "snmp"
     "smartctl"
     "smokeping"
+    "snmp"
     "sql"
     "statsd"
     "surfboard"
@@ -88,10 +90,39 @@ let
     "v2ray"
     "varnish"
     "wireguard"
-    "flow"
     "zfs"
-  ] (name:
-    import (./. + "/exporters/${name}.nix") { inherit config lib pkgs options; }
+  ]
+    (name:
+      import (./. + "/exporters/${name}.nix") { inherit config lib pkgs options; }
+    )) // (mapAttrs
+    (name: params:
+      import (./. + "/exporters/${params.name}.nix") { inherit config lib pkgs options; type = params.type ; })
+    {
+      exportarr-bazarr = {
+        name = "exportarr";
+        type = "bazarr";
+      };
+      exportarr-lidarr = {
+        name = "exportarr";
+        type = "lidarr";
+      };
+      exportarr-prowlarr = {
+        name = "exportarr";
+        type = "prowlarr";
+      };
+      exportarr-radarr = {
+        name = "exportarr";
+        type = "radarr";
+      };
+      exportarr-readarr = {
+        name = "exportarr";
+        type = "readarr";
+      };
+      exportarr-sonarr = {
+        name = "exportarr";
+        type = "sonarr";
+      };
+    }
   );
 
   mkExporterOpts = ({ name, port }: {
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/exportarr.nix b/nixos/modules/services/monitoring/prometheus/exporters/exportarr.nix
new file mode 100644
index 0000000000000..1322093354109
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/exportarr.nix
@@ -0,0 +1,55 @@
+{ config, lib, pkgs, options, type }:
+
+let
+  cfg = config.services.prometheus.exporters."exportarr-${type}";
+  exportarrEnvironment = (
+    lib.mapAttrs (_: toString) cfg.environment
+  ) // {
+    PORT = toString cfg.port;
+    URL = cfg.url;
+    API_KEY_FILE = lib.mkIf (cfg.apiKeyFile != null) "%d/api-key";
+  };
+in
+{
+  port = 9708;
+  extraOpts = {
+    url = lib.mkOption {
+      type = lib.types.str;
+      default = "http://127.0.0.1";
+      description = lib.mdDoc ''
+        The full URL to Sonarr, Radarr, or Lidarr.
+      '';
+    };
+
+    apiKeyFile = lib.mkOption {
+      type = lib.types.nullOr lib.types.path;
+      default = null;
+      description = lib.mdDoc ''
+        File containing the api-key.
+      '';
+    };
+
+    package = lib.mkPackageOptionMD pkgs "exportarr" { };
+
+    environment = lib.mkOption {
+      type = lib.types.attrsOf lib.types.str;
+      default = { };
+      description = lib.mdDoc ''
+        See [the configuration guide](https://github.com/onedr0p/exportarr#configuration) for available options.
+      '';
+      example = {
+        PROWLARR__BACKFILL = true;
+      };
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      LoadCredential = lib.optionalString (cfg.apiKeyFile != null) "api-key:${cfg.apiKeyFile}";
+      ExecStart = ''${cfg.package}/bin/exportarr ${type} "$@"'';
+      ProcSubset = "pid";
+      ProtectProc = "invisible";
+      SystemCallFilter = ["@system-service" "~@privileged"];
+    };
+    environment = exportarrEnvironment;
+  };
+}
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/mongodb.nix b/nixos/modules/services/monitoring/prometheus/exporters/mongodb.nix
new file mode 100644
index 0000000000000..db5c4d15be662
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/mongodb.nix
@@ -0,0 +1,68 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.mongodb;
+in
+{
+  port = 9216;
+  extraOpts = {
+    uri = mkOption {
+      type = types.str;
+      default = "mongodb://localhost:27017/test";
+      example = "mongodb://localhost:27017/test";
+      description = lib.mdDoc "MongoDB URI to connect to.";
+    };
+    collStats = mkOption {
+      type = types.listOf types.str;
+      default = [ ];
+      example = [ "db1.coll1" "db2" ];
+      description = lib.mdDoc ''
+        List of comma separared databases.collections to get $collStats
+      '';
+    };
+    indexStats = mkOption {
+      type = types.listOf types.str;
+      default = [ ];
+      example = [ "db1.coll1" "db2" ];
+      description = lib.mdDoc ''
+        List of comma separared databases.collections to get $indexStats
+      '';
+    };
+    collector = mkOption {
+      type = types.listOf types.str;
+      default = [ ];
+      example = [ "diagnosticdata" "replicasetstatus" "dbstats" "topmetrics" "currentopmetrics" "indexstats" "dbstats" "profile" ];
+      description = lib.mdDoc "Enabled collectors";
+    };
+    collectAll = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Enable all collectors. Same as specifying all --collector.<name>
+      '';
+    };
+    telemetryPath = mkOption {
+      type = types.str;
+      default = "/metrics";
+      example = "/metrics";
+      description = lib.mdDoc "Metrics expose path";
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      RuntimeDirectory = "prometheus-mongodb-exporter";
+      ExecStart = ''
+        ${getExe pkgs.prometheus-mongodb-exporter} \
+          --mongodb.uri=${cfg.uri}
+          ${if cfg.collectAll then "--collect-all" else concatMapStringsSep " " (x: "--collect.${x}") cfg.collector} \
+          --collector.collstats=${concatStringsSep "," cfg.collStats} \
+          --collector.indexstats=${concatStringsSep "," cfg.indexStats} \
+          --web.listen-address=${cfg.listenAddress}:${toString cfg.port} \
+          --web.telemetry-path=${cfg.telemetryPath} \
+          ${escapeShellArgs cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/thanos.nix b/nixos/modules/services/monitoring/thanos.nix
index e6d8afc66624f..db8641aa61461 100644
--- a/nixos/modules/services/monitoring/thanos.nix
+++ b/nixos/modules/services/monitoring/thanos.nix
@@ -1,14 +1,37 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
+  inherit (lib)
+    collect
+    concatLists
+    concatStringsSep
+    flip
+    getAttrFromPath
+    hasPrefix
+    isList
+    length
+    literalExpression
+    literalMD
+    mapAttrsRecursiveCond
+    mapAttrsToList
+    mdDoc
+    mkEnableOption
+    mkIf
+    mkMerge
+    mkOption
+    mkPackageOptionMD
+    optional
+    optionalAttrs
+    optionalString
+    types
+    ;
+
   cfg = config.services.thanos;
 
   nullOpt = type: description: mkOption {
     type = types.nullOr type;
     default = null;
-    description = lib.mdDoc description;
+    description = mdDoc description;
   };
 
   optionToArgs = opt: v  : optional (v != null)  ''--${opt}="${toString v}"'';
@@ -32,7 +55,7 @@ let
     option = mkOption {
       type = types.bool;
       default = false;
-      description = lib.mdDoc description;
+      description = mdDoc description;
     };
   };
 
@@ -41,7 +64,7 @@ let
     option = mkOption {
       type = types.listOf types.str;
       default = [];
-      description = lib.mdDoc description;
+      description = mdDoc description;
     };
   };
 
@@ -50,7 +73,7 @@ let
     option = mkOption {
       type = types.attrsOf types.str;
       default = {};
-      description = lib.mdDoc description;
+      description = mdDoc description;
     };
   };
 
@@ -59,7 +82,7 @@ let
     option = mkOption {
       type = types.str;
       inherit default;
-      description = lib.mdDoc description;
+      description = mdDoc description;
     };
   };
 
@@ -86,7 +109,7 @@ let
     defaultText = literalMD ''
       calculated from `config.services.thanos.${cmd}`
     '';
-    description = lib.mdDoc ''
+    description = mdDoc ''
       Arguments to the `thanos ${cmd}` command.
 
       Defaults to a list of arguments formed by converting the structured
@@ -127,10 +150,10 @@ let
             if config.services.thanos.<cmd>.tracing.config == null then null
             else toString (toYAML "tracing.yaml" config.services.thanos.<cmd>.tracing.config);
           '';
-          description = lib.mdDoc ''
+          description = mdDoc ''
             Path to YAML file that contains tracing configuration.
 
-            See format details: <https://thanos.io/tracing.md/#configuration>
+            See format details: <https://thanos.io/tip/thanos/tracing.md/#configuration>
           '';
         };
       };
@@ -147,7 +170,7 @@ let
 
             If {option}`tracing.config-file` is set this option has no effect.
 
-            See format details: <https://thanos.io/tracing.md/#configuration>
+            See format details: <https://thanos.io/tip/thanos/tracing.md/#configuration>
           '';
         };
     };
@@ -192,10 +215,10 @@ let
             if config.services.thanos.<cmd>.objstore.config == null then null
             else toString (toYAML "objstore.yaml" config.services.thanos.<cmd>.objstore.config);
           '';
-          description = lib.mdDoc ''
+          description = mdDoc ''
             Path to YAML file that contains object store configuration.
 
-            See format details: <https://thanos.io/storage.md/#configuration>
+            See format details: <https://thanos.io/tip/thanos/storage.md/#configuring-access-to-object-storage>
           '';
         };
       };
@@ -212,7 +235,7 @@ let
 
             If {option}`objstore.config-file` is set this option has no effect.
 
-            See format details: <https://thanos.io/storage.md/#configuration>
+            See format details: <https://thanos.io/tip/thanos/storage.md/#configuring-access-to-object-storage>
           '';
         };
     };
@@ -231,7 +254,7 @@ let
           type = types.str;
           default = "/var/lib/${config.services.prometheus.stateDir}/data";
           defaultText = literalExpression ''"/var/lib/''${config.services.prometheus.stateDir}/data"'';
-          description = lib.mdDoc ''
+          description = mdDoc ''
             Data directory of TSDB.
           '';
         };
@@ -266,14 +289,14 @@ let
         Maximum size of concurrently allocatable bytes for chunks.
       '';
 
-      store.grpc.series-sample-limit = mkParamDef types.int 0 ''
-        Maximum amount of samples returned via a single Series call.
+      store.limits.request-samples = mkParamDef types.int 0 ''
+        The maximum samples allowed for a single Series request.
+        The Series call fails if this limit is exceeded.
 
         `0` means no limit.
 
-        NOTE: for efficiency we take 120 as the number of samples in chunk (it
-        cannot be bigger than that), so the actual number of samples might be
-        lower, even though the maximum could be hit.
+        NOTE: For efficiency the limit is internally implemented as 'chunks limit'
+        considering each chunk contains a maximum of 120 samples.
       '';
 
       store.grpc.series-max-concurrency = mkParamDef types.int 20 ''
@@ -371,24 +394,25 @@ let
         Maximum number of queries processed concurrently by query node.
       '';
 
-      query.replica-label = mkParam types.str ''
-        Label to treat as a replica indicator along which data is
+      query.replica-labels = mkAttrsParam "query.replica-label" ''
+        Labels to treat as a replica indicator along which data is
+
         deduplicated.
 
         Still you will be able to query without deduplication using
-        `dedup=false` parameter.
+        'dedup=false' parameter. Data includes time series, recording
+        rules, and alerting rules.
       '';
 
       selector-labels = mkAttrsParam "selector-label" ''
         Query selector labels that will be exposed in info endpoint.
       '';
 
-      store.addresses = mkListParam "store" ''
-        Addresses of statically configured store API servers.
+      endpoints = mkListParam "endpoint" ''
+        Addresses of statically configured Thanos API servers (repeatable).
 
-        The scheme may be prefixed with `dns+` or
-        `dnssrv+` to detect store API servers through
-        respective DNS lookups.
+        The scheme may be prefixed with 'dns+' or 'dnssrv+' to detect
+        Thanos API servers through respective DNS lookups.
       '';
 
       store.sd-files = mkListParam "store.sd-files" ''
@@ -430,6 +454,12 @@ let
       '';
     };
 
+    query-frontend = params.common cfg.query-frontend // {
+      query-frontend.downstream-url = mkParamDef types.str "http://localhost:9090" ''
+        URL of downstream Prometheus Query compatible API.
+      '';
+    };
+
     rule = params.common cfg.rule // params.objstore cfg.rule // {
 
       labels = mkAttrsParam "label" ''
@@ -447,7 +477,7 @@ let
         Rule files that should be used by rule manager. Can be in glob format.
       '';
 
-      eval-interval = mkParamDef types.str "30s" ''
+      eval-interval = mkParamDef types.str "1m" ''
         The default evaluation interval to use.
       '';
 
@@ -597,10 +627,6 @@ let
         to render all samples for a human eye anyway
       '';
 
-      block-sync-concurrency = mkParamDef types.int 20 ''
-        Number of goroutines to use when syncing block metadata from object storage.
-      '';
-
       compact.concurrency = mkParamDef types.int 1 ''
         Number of goroutines to use when compacting groups.
       '';
@@ -625,7 +651,7 @@ let
         Data directory relative to `/var/lib` of TSDB.
       '';
 
-      labels = mkAttrsParam "labels" ''
+      labels = mkAttrsParam "label" ''
         External labels to announce.
 
         This flag will be removed in the future when handling multiple tsdb
@@ -656,57 +682,56 @@ in {
 
   options.services.thanos = {
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.thanos;
-      defaultText = literalExpression "pkgs.thanos";
-      description = lib.mdDoc ''
-        The thanos package that should be used.
-      '';
-    };
+    package = mkPackageOptionMD pkgs "thanos" {};
 
     sidecar = paramsToOptions params.sidecar // {
       enable = mkEnableOption
-        (lib.mdDoc "the Thanos sidecar for Prometheus server");
+        (mdDoc "the Thanos sidecar for Prometheus server");
       arguments = mkArgumentsOption "sidecar";
     };
 
     store = paramsToOptions params.store // {
       enable = mkEnableOption
-        (lib.mdDoc "the Thanos store node giving access to blocks in a bucket provider.");
+        (mdDoc "the Thanos store node giving access to blocks in a bucket provider.");
       arguments = mkArgumentsOption "store";
     };
 
     query = paramsToOptions params.query // {
       enable = mkEnableOption
-        (lib.mdDoc ("the Thanos query node exposing PromQL enabled Query API " +
+        (mdDoc ("the Thanos query node exposing PromQL enabled Query API " +
          "with data retrieved from multiple store nodes"));
       arguments = mkArgumentsOption "query";
     };
 
+    query-frontend = paramsToOptions params.query-frontend // {
+      enable = mkEnableOption
+        (mdDoc ("the Thanos query frontend implements a service deployed in front of queriers to
+          improve query parallelization and caching."));
+      arguments = mkArgumentsOption "query-frontend";
+    };
+
     rule = paramsToOptions params.rule // {
       enable = mkEnableOption
-        (lib.mdDoc ("the Thanos ruler service which evaluates Prometheus rules against" +
+        (mdDoc ("the Thanos ruler service which evaluates Prometheus rules against" +
         " given Query nodes, exposing Store API and storing old blocks in bucket"));
       arguments = mkArgumentsOption "rule";
     };
 
     compact = paramsToOptions params.compact // {
       enable = mkEnableOption
-        (lib.mdDoc "the Thanos compactor which continuously compacts blocks in an object store bucket");
+        (mdDoc "the Thanos compactor which continuously compacts blocks in an object store bucket");
       arguments = mkArgumentsOption "compact";
     };
 
     downsample = paramsToOptions params.downsample // {
       enable = mkEnableOption
-        (lib.mdDoc "the Thanos downsampler which continuously downsamples blocks in an object store bucket");
+        (mdDoc "the Thanos downsampler which continuously downsamples blocks in an object store bucket");
       arguments = mkArgumentsOption "downsample";
     };
 
     receive = paramsToOptions params.receive // {
       enable = mkEnableOption
-        (lib.mdDoc ("the Thanos receiver which accept Prometheus remote write API requests " +
-         "and write to local tsdb (EXPERIMENTAL, this may change drastically without notice)"));
+        (mdDoc ("the Thanos receiver which accept Prometheus remote write API requests and write to local tsdb"));
       arguments = mkArgumentsOption "receive";
     };
   };
@@ -736,6 +761,7 @@ in {
           User = "prometheus";
           Restart = "always";
           ExecStart = thanos "sidecar";
+          ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
         };
       };
     })
@@ -751,6 +777,7 @@ in {
             StateDirectory = cfg.store.stateDir;
             Restart = "always";
             ExecStart = thanos "store";
+            ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
           };
         };
       }
@@ -764,6 +791,20 @@ in {
           DynamicUser = true;
           Restart = "always";
           ExecStart = thanos "query";
+          ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        };
+      };
+    })
+
+    (mkIf cfg.query-frontend.enable {
+      systemd.services.thanos-query-frontend = {
+        wantedBy = [ "multi-user.target" ];
+        after    = [ "network.target" ];
+        serviceConfig = {
+          DynamicUser = true;
+          Restart = "always";
+          ExecStart = thanos "query-frontend";
+          ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
         };
       };
     })
@@ -779,6 +820,7 @@ in {
             StateDirectory = cfg.rule.stateDir;
             Restart = "always";
             ExecStart = thanos "rule";
+            ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
           };
         };
       }
@@ -797,6 +839,7 @@ in {
               DynamicUser = true;
               StateDirectory = cfg.compact.stateDir;
               ExecStart = thanos "compact";
+              ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
             };
           } // optionalAttrs (!wait) { inherit (cfg.compact) startAt; };
       }
@@ -813,6 +856,7 @@ in {
             StateDirectory = cfg.downsample.stateDir;
             Restart = "always";
             ExecStart = thanos "downsample";
+            ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
           };
         };
       }
@@ -829,6 +873,7 @@ in {
             StateDirectory = cfg.receive.stateDir;
             Restart = "always";
             ExecStart = thanos "receive";
+            ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
           };
         };
       }
diff --git a/nixos/modules/services/monitoring/zabbix-proxy.nix b/nixos/modules/services/monitoring/zabbix-proxy.nix
index 85da416ba6c33..503e81b48a587 100644
--- a/nixos/modules/services/monitoring/zabbix-proxy.nix
+++ b/nixos/modules/services/monitoring/zabbix-proxy.nix
@@ -203,7 +203,7 @@ in
       { assertion = !config.services.zabbixServer.enable;
         message = "Please choose one of services.zabbixServer or services.zabbixProxy.";
       }
-      { assertion = cfg.database.createLocally -> cfg.database.user == user;
+      { assertion = cfg.database.createLocally -> cfg.database.user == user && cfg.database.name == cfg.database.user;
         message = "services.zabbixProxy.database.user must be set to ${user} if services.zabbixProxy.database.createLocally is set true";
       }
       { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
@@ -252,7 +252,7 @@ in
       ensureDatabases = [ cfg.database.name ];
       ensureUsers = [
         { name = cfg.database.user;
-          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/monitoring/zabbix-server.nix b/nixos/modules/services/monitoring/zabbix-server.nix
index 2b50280e3969a..0607188d21319 100644
--- a/nixos/modules/services/monitoring/zabbix-server.nix
+++ b/nixos/modules/services/monitoring/zabbix-server.nix
@@ -191,7 +191,7 @@ in
   config = mkIf cfg.enable {
 
     assertions = [
-      { assertion = cfg.database.createLocally -> cfg.database.user == user;
+      { assertion = cfg.database.createLocally -> cfg.database.user == user && cfg.database.user == cfg.database.name;
         message = "services.zabbixServer.database.user must be set to ${user} if services.zabbixServer.database.createLocally is set true";
       }
       { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
@@ -240,7 +240,7 @@ in
       ensureDatabases = [ cfg.database.name ];
       ensureUsers = [
         { name = cfg.database.user;
-          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/network-filesystems/xtreemfs.nix b/nixos/modules/services/network-filesystems/xtreemfs.nix
index 926c3c3bd5233..866661cf4e6f8 100644
--- a/nixos/modules/services/network-filesystems/xtreemfs.nix
+++ b/nixos/modules/services/network-filesystems/xtreemfs.nix
@@ -176,7 +176,7 @@ in
           description = lib.mdDoc ''
             Configuration of XtreemFS DIR service.
             WARNING: configuration is saved as plaintext inside nix store.
-            For more options: http://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
+            For more options: https://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
           '';
         };
         replication = {
@@ -218,7 +218,7 @@ in
             description = lib.mdDoc ''
               Configuration of XtreemFS DIR replication plugin.
               WARNING: configuration is saved as plaintext inside nix store.
-              For more options: http://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
+              For more options: https://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
             '';
           };
         };
@@ -319,7 +319,7 @@ in
           description = lib.mdDoc ''
             Configuration of XtreemFS MRC service.
             WARNING: configuration is saved as plaintext inside nix store.
-            For more options: http://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
+            For more options: https://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
           '';
         };
         replication = {
@@ -361,7 +361,7 @@ in
             description = lib.mdDoc ''
               Configuration of XtreemFS MRC replication plugin.
               WARNING: configuration is saved as plaintext inside nix store.
-              For more options: http://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
+              For more options: https://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
             '';
           };
         };
@@ -438,7 +438,7 @@ in
           description = lib.mdDoc ''
             Configuration of XtreemFS OSD service.
             WARNING: configuration is saved as plaintext inside nix store.
-            For more options: http://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
+            For more options: https://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
           '';
         };
       };
diff --git a/nixos/modules/services/networking/asterisk.nix b/nixos/modules/services/networking/asterisk.nix
index 5a1d03f072117..279927781edc4 100644
--- a/nixos/modules/services/networking/asterisk.nix
+++ b/nixos/modules/services/networking/asterisk.nix
@@ -139,7 +139,7 @@ in
           path.
 
           See
-          <http://www.asterisk.org/community/documentation>
+          <https://www.asterisk.org/community/documentation/>
           for more examples of what is possible here.
         '';
       };
diff --git a/nixos/modules/services/networking/centrifugo.nix b/nixos/modules/services/networking/centrifugo.nix
new file mode 100644
index 0000000000000..143fe6a24dc38
--- /dev/null
+++ b/nixos/modules/services/networking/centrifugo.nix
@@ -0,0 +1,123 @@
+{ config, lib, pkgs, ... }:
+let
+  cfg = config.services.centrifugo;
+
+  settingsFormat = pkgs.formats.json { };
+
+  configFile = settingsFormat.generate "centrifugo.json" cfg.settings;
+in
+{
+  options.services.centrifugo = {
+    enable = lib.mkEnableOption (lib.mdDoc "Centrifugo messaging server");
+
+    package = lib.mkPackageOptionMD pkgs "centrifugo" { };
+
+    settings = lib.mkOption {
+      type = settingsFormat.type;
+      default = { };
+      description = lib.mdDoc ''
+        Declarative Centrifugo configuration. See the [Centrifugo
+        documentation] for a list of options.
+
+        [Centrifugo documentation]: https://centrifugal.dev/docs/server/configuration
+      '';
+    };
+
+    credentials = lib.mkOption {
+      type = lib.types.attrsOf lib.types.path;
+      default = { };
+      example = {
+        CENTRIFUGO_UNI_GRPC_TLS_KEY = "/run/keys/centrifugo-uni-grpc-tls.key";
+      };
+      description = lib.mdDoc ''
+        Environment variables with absolute paths to credentials files to load
+        on service startup.
+      '';
+    };
+
+    environmentFiles = lib.mkOption {
+      type = lib.types.listOf lib.types.path;
+      default = [ ];
+      description = lib.mdDoc ''
+        Files to load environment variables from. Options set via environment
+        variables take precedence over {option}`settings`.
+
+        See the [Centrifugo documentation] for the environment variable name
+        format.
+
+        [Centrifugo documentation]: https://centrifugal.dev/docs/server/configuration#os-environment-variables
+      '';
+    };
+
+    extraGroups = lib.mkOption {
+      type = lib.types.listOf lib.types.str;
+      default = [ ];
+      example = [ "redis-centrifugo" ];
+      description = lib.mdDoc ''
+        Additional groups for the systemd service.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.centrifugo = {
+      description = "Centrifugo messaging server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        Type = "exec";
+
+        ExecStartPre = "${lib.getExe cfg.package} checkconfig --config ${configFile}";
+        ExecStart = "${lib.getExe cfg.package} --config ${configFile}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+
+        Restart = "always";
+        RestartSec = "1s";
+
+        # Copy files to the credentials directory with file name being the
+        # environment variable name. Note that "%d" specifier expands to the
+        # path of the credentials directory.
+        LoadCredential = lib.mapAttrsToList (name: value: "${name}:${value}") cfg.credentials;
+        Environment = lib.mapAttrsToList (name: _: "${name}=%d/${name}") cfg.credentials;
+
+        EnvironmentFile = cfg.environmentFiles;
+
+        SupplementaryGroups = cfg.extraGroups;
+
+        DynamicUser = true;
+        UMask = "0077";
+
+        ProtectHome = true;
+        ProtectProc = "invisible";
+        ProcSubset = "pid";
+        ProtectClock = true;
+        ProtectHostname = true;
+        ProtectControlGroups = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        PrivateUsers = true;
+        PrivateDevices = true;
+        RestrictRealtime = true;
+        RestrictNamespaces = true;
+        RestrictAddressFamilies = [
+          "AF_INET"
+          "AF_INET6"
+          "AF_UNIX"
+        ];
+        DeviceAllow = [ "" ];
+        DevicePolicy = "closed";
+        CapabilityBoundingSet = [ "" ];
+        MemoryDenyWriteExecute = true;
+        LockPersonality = true;
+        SystemCallArchitectures = "native";
+        SystemCallErrorNumber = "EPERM";
+        SystemCallFilter = [
+          "@system-service"
+          "~@privileged"
+        ];
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/firewall-iptables.nix b/nixos/modules/services/networking/firewall-iptables.nix
index 63e952194d671..e900868387203 100644
--- a/nixos/modules/services/networking/firewall-iptables.nix
+++ b/nixos/modules/services/networking/firewall-iptables.nix
@@ -301,6 +301,7 @@ in
       }
     ];
 
+    environment.systemPackages = [ pkgs.nixos-firewall-tool ];
     networking.firewall.checkReversePath = mkIf (!kernelHasRPFilter) (mkDefault false);
 
     systemd.services.firewall = {
diff --git a/nixos/modules/services/networking/harmonia.nix b/nixos/modules/services/networking/harmonia.nix
index 144fa6c708e28..4733165cf7d19 100644
--- a/nixos/modules/services/networking/harmonia.nix
+++ b/nixos/modules/services/networking/harmonia.nix
@@ -28,6 +28,8 @@ in
   };
 
   config = lib.mkIf cfg.enable {
+    nix.settings.extra-allowed-users = [ "harmonia" ];
+
     systemd.services.harmonia = {
       description = "harmonia binary cache service";
 
diff --git a/nixos/modules/services/networking/i2pd.nix b/nixos/modules/services/networking/i2pd.nix
index f872daf05b8f0..808e7b66d36e2 100644
--- a/nixos/modules/services/networking/i2pd.nix
+++ b/nixos/modules/services/networking/i2pd.nix
@@ -239,7 +239,7 @@ in
       enable = mkEnableOption (lib.mdDoc "I2Pd daemon") // {
         description = lib.mdDoc ''
           Enables I2Pd as a running service upon activation.
-          Please read http://i2pd.readthedocs.io/en/latest/ for further
+          Please read <https://i2pd.readthedocs.io/en/latest/> for further
           configuration help.
         '';
       };
diff --git a/nixos/modules/services/networking/ircd-hybrid/builder.sh b/nixos/modules/services/networking/ircd-hybrid/builder.sh
index d9d2e4264dfd1..07a3788abf7d2 100644
--- a/nixos/modules/services/networking/ircd-hybrid/builder.sh
+++ b/nixos/modules/services/networking/ircd-hybrid/builder.sh
@@ -1,4 +1,4 @@
-if [ -e .attrs.sh ]; then source .attrs.sh; fi
+if [ -e "$NIX_ATTRS_SH_FILE" ]; then . "$NIX_ATTRS_SH_FILE"; elif [ -f .attrs.sh ]; then . .attrs.sh; fi
 source $stdenv/setup
 
 doSub() {
diff --git a/nixos/modules/services/networking/mullvad-vpn.nix b/nixos/modules/services/networking/mullvad-vpn.nix
index 99ffbf56ccb00..8c7d5237971f3 100644
--- a/nixos/modules/services/networking/mullvad-vpn.nix
+++ b/nixos/modules/services/networking/mullvad-vpn.nix
@@ -65,7 +65,9 @@ with lib;
         pkgs.iproute2
         # Needed for ping
         "/run/wrappers"
-      ];
+        # See https://github.com/NixOS/nixpkgs/issues/262681
+      ] ++ (lib.optional config.networking.resolvconf.enable
+        config.networking.resolvconf.package);
       startLimitBurst = 5;
       startLimitIntervalSec = 20;
       serviceConfig = {
diff --git a/nixos/modules/services/networking/nar-serve.nix b/nixos/modules/services/networking/nar-serve.nix
index b8b76120e44f6..02b8979bd8bc7 100644
--- a/nixos/modules/services/networking/nar-serve.nix
+++ b/nixos/modules/services/networking/nar-serve.nix
@@ -6,7 +6,7 @@ let
 in
 {
   meta = {
-    maintainers = [ maintainers.rizary ];
+    maintainers = [ maintainers.rizary maintainers.zimbatm ];
   };
   options = {
     services.nar-serve = {
diff --git a/nixos/modules/services/networking/nix-serve.nix b/nixos/modules/services/networking/nix-serve.nix
index f37be31270b7c..86bb603e12719 100644
--- a/nixos/modules/services/networking/nix-serve.nix
+++ b/nixos/modules/services/networking/nix-serve.nix
@@ -67,6 +67,10 @@ in
   };
 
   config = mkIf cfg.enable {
+    nix.settings = lib.optionalAttrs (lib.versionAtLeast config.nix.package.version "2.4") {
+      extra-allowed-users = [ "nix-serve" ];
+    };
+
     systemd.services.nix-serve = {
       description = "nix-serve binary cache server";
       after = [ "network.target" ];
diff --git a/nixos/modules/services/networking/ntp/chrony.nix b/nixos/modules/services/networking/ntp/chrony.nix
index afd721e34da5a..3f10145033c55 100644
--- a/nixos/modules/services/networking/ntp/chrony.nix
+++ b/nixos/modules/services/networking/ntp/chrony.nix
@@ -9,6 +9,7 @@ let
   stateDir = cfg.directory;
   driftFile = "${stateDir}/chrony.drift";
   keyFile = "${stateDir}/chrony.keys";
+  rtcFile = "${stateDir}/chrony.rtc";
 
   configFile = pkgs.writeText "chrony.conf" ''
     ${concatMapStringsSep "\n" (server: "server " + server + " " + cfg.serverOption + optionalString (cfg.enableNTS) " nts") cfg.servers}
@@ -20,8 +21,10 @@ let
 
     driftfile ${driftFile}
     keyfile ${keyFile}
+    ${optionalString (cfg.enableRTCTrimming) "rtcfile ${rtcFile}"}
     ${optionalString (cfg.enableNTS) "ntsdumpdir ${stateDir}"}
 
+    ${optionalString (cfg.enableRTCTrimming) "rtcautotrim ${builtins.toString cfg.autotrimThreshold}"}
     ${optionalString (!config.time.hardwareClockInLocalTime) "rtconutc"}
 
     ${cfg.extraConfig}
@@ -85,6 +88,33 @@ in
         '';
       };
 
+      enableRTCTrimming = mkOption {
+        type = types.bool;
+        default = true;
+        description = lib.mdDoc ''
+          Enable tracking of the RTC offset to the system clock and automatic trimming.
+          See also [](#opt-services.chrony.autotrimThreshold)
+
+          ::: {.note}
+          This is not compatible with the `rtcsync` directive, which naively syncs the RTC time every 11 minutes.
+
+          Tracking the RTC drift will allow more precise timekeeping,
+          especially on intermittently running devices, where the RTC is very relevant.
+          :::
+        '';
+      };
+
+      autotrimThreshold = mkOption {
+        type = types.ints.positive;
+        default = 30;
+        example = 10;
+        description = ''
+          Maximum estimated error threshold for the `rtcautotrim` command.
+          When reached, the RTC will be trimmed.
+          Only used when [](#opt-services.chrony.enableRTCTrimming) is enabled.
+        '';
+      };
+
       enableNTS = mkOption {
         type = types.bool;
         default = false;
@@ -132,7 +162,7 @@ in
       };
 
       extraFlags = mkOption {
-        default = [];
+        default = [ ];
         example = [ "-s" ];
         type = types.listOf types.str;
         description = lib.mdDoc "Extra flags passed to the chronyd command.";
@@ -141,14 +171,15 @@ in
   };
 
   config = mkIf cfg.enable {
-    meta.maintainers = with lib.maintainers; [ thoughtpolice ];
+    meta.maintainers = with lib.maintainers; [ thoughtpolice vifino ];
 
     environment.systemPackages = [ chronyPkg ];
 
     users.groups.chrony.gid = config.ids.gids.chrony;
 
     users.users.chrony =
-      { uid = config.ids.uids.chrony;
+      {
+        uid = config.ids.uids.chrony;
         group = "chrony";
         description = "chrony daemon user";
         home = stateDir;
@@ -156,21 +187,29 @@ in
 
     services.timesyncd.enable = mkForce false;
 
+    # If chrony controls and tracks the RTC, writing it externally causes clock error.
+    systemd.services.save-hwclock = lib.mkIf cfg.enableRTCTrimming {
+      enable = lib.mkForce false;
+    };
+
     systemd.services.systemd-timedated.environment = { SYSTEMD_TIMEDATED_NTP_SERVICES = "chronyd.service"; };
 
     systemd.tmpfiles.rules = [
       "d ${stateDir} 0750 chrony chrony - -"
       "f ${driftFile} 0640 chrony chrony - -"
       "f ${keyFile} 0640 chrony chrony - -"
+    ] ++ lib.optionals cfg.enableRTCTrimming [
+      "f ${rtcFile} 0640 chrony chrony - -"
     ];
 
     systemd.services.chronyd =
-      { description = "chrony NTP daemon";
+      {
+        description = "chrony NTP daemon";
 
         wantedBy = [ "multi-user.target" ];
-        wants    = [ "time-sync.target" ];
-        before   = [ "time-sync.target" ];
-        after    = [ "network.target" "nss-lookup.target" ];
+        wants = [ "time-sync.target" ];
+        before = [ "time-sync.target" ];
+        after = [ "network.target" "nss-lookup.target" ];
         conflicts = [ "ntpd.service" "systemd-timesyncd.service" ];
 
         path = [ chronyPkg ];
@@ -218,5 +257,18 @@ in
           SystemCallFilter = [ "~@cpu-emulation @debug @keyring @mount @obsolete @privileged @resources" "@clock" "@setuid" "capset" "@chown" ];
         };
       };
+
+    assertions = [
+      {
+        assertion = !(cfg.enableRTCTrimming && builtins.any (line: (builtins.match "^ *rtcsync" line) != null) (lib.strings.splitString "\n" cfg.extraConfig));
+        message = ''
+          The chrony module now configures `rtcfile` and `rtcautotrim` for you.
+          These options conflict with `rtcsync` and cause chrony to crash.
+          Unless you are very sure the former isn't what you want, please remove
+          `rtcsync` from `services.chrony.extraConfig`.
+          Alternatively, disable this behaviour by `services.chrony.enableRTCTrimming = false;`
+        '';
+      }
+    ];
   };
 }
diff --git a/nixos/modules/services/networking/ntp/ntpd.nix b/nixos/modules/services/networking/ntp/ntpd.nix
index 036a8df635db0..2bc690cacf096 100644
--- a/nixos/modules/services/networking/ntp/ntpd.nix
+++ b/nixos/modules/services/networking/ntp/ntpd.nix
@@ -56,7 +56,7 @@ in
           The default flags prevent external hosts from using ntpd as a DDoS
           reflector, setting system time, and querying OS/ntpd version. As
           recommended in section 6.5.1.1.3, answer "No" of
-          http://support.ntp.org/bin/view/Support/AccessRestrictions
+          https://support.ntp.org/Support/AccessRestrictions
         '';
         default = [ "limited" "kod" "nomodify" "notrap" "noquery" "nopeer" ];
       };
diff --git a/nixos/modules/services/networking/pixiecore.nix b/nixos/modules/services/networking/pixiecore.nix
index f410be4716466..1f47a1d0b631d 100644
--- a/nixos/modules/services/networking/pixiecore.nix
+++ b/nixos/modules/services/networking/pixiecore.nix
@@ -16,7 +16,7 @@ in
         type = types.bool;
         default = false;
         description = lib.mdDoc ''
-          Open ports (67, 69 UDP and 4011, 'port', 'statusPort' TCP) in the firewall for Pixiecore.
+          Open ports (67, 69, 4011 UDP and 'port', 'statusPort' TCP) in the firewall for Pixiecore.
         '';
       };
 
@@ -103,8 +103,8 @@ in
     };
 
     networking.firewall = mkIf cfg.openFirewall {
-      allowedTCPPorts = [ 4011 cfg.port cfg.statusPort ];
-      allowedUDPPorts = [ 67 69 ];
+      allowedTCPPorts = [ cfg.port cfg.statusPort ];
+      allowedUDPPorts = [ 67 69 4011 ];
     };
 
     systemd.services.pixiecore = {
diff --git a/nixos/modules/services/networking/pleroma.nix b/nixos/modules/services/networking/pleroma.nix
index e9db7f3eab8ed..89e64d36c8a05 100644
--- a/nixos/modules/services/networking/pleroma.nix
+++ b/nixos/modules/services/networking/pleroma.nix
@@ -146,6 +146,6 @@ in {
     };
 
   };
-  meta.maintainers = with lib.maintainers; [ ninjatrappeur ];
+  meta.maintainers = with lib.maintainers; [ picnoir ];
   meta.doc = ./pleroma.md;
 }
diff --git a/nixos/modules/services/networking/prayer.nix b/nixos/modules/services/networking/prayer.nix
deleted file mode 100644
index 197aa8a6f4480..0000000000000
--- a/nixos/modules/services/networking/prayer.nix
+++ /dev/null
@@ -1,90 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  inherit (pkgs) prayer;
-
-  cfg = config.services.prayer;
-
-  stateDir = "/var/lib/prayer";
-
-  prayerUser = "prayer";
-  prayerGroup = "prayer";
-
-  prayerExtraCfg = pkgs.writeText "extraprayer.cf" ''
-    prefix = "${prayer}"
-    var_prefix = "${stateDir}"
-    prayer_user = "${prayerUser}"
-    prayer_group = "${prayerGroup}"
-    sendmail_path = "/run/wrappers/bin/sendmail"
-
-    use_http_port ${cfg.port}
-
-    ${cfg.extraConfig}
-  '';
-
-  prayerCfg = pkgs.runCommand "prayer.cf" { preferLocalBuild = true; } ''
-    # We have to remove the http_port 80, or it will start a server there
-    cat ${prayer}/etc/prayer.cf | grep -v http_port > $out
-    cat ${prayerExtraCfg} >> $out
-  '';
-
-in
-
-{
-
-  ###### interface
-
-  options = {
-
-    services.prayer = {
-
-      enable = mkEnableOption (lib.mdDoc "the prayer webmail http server");
-
-      port = mkOption {
-        default = 2080;
-        type = types.port;
-        description = lib.mdDoc ''
-          Port the prayer http server is listening to.
-        '';
-      };
-
-      extraConfig = mkOption {
-        type = types.lines;
-        default = "" ;
-        description = lib.mdDoc ''
-          Extra configuration. Contents will be added verbatim to the configuration file.
-        '';
-      };
-    };
-
-  };
-
-
-  ###### implementation
-
-  config = mkIf config.services.prayer.enable {
-    environment.systemPackages = [ prayer ];
-
-    users.users.${prayerUser} =
-      { uid = config.ids.uids.prayer;
-        description = "Prayer daemon user";
-        home = stateDir;
-      };
-
-    users.groups.${prayerGroup} =
-      { gid = config.ids.gids.prayer; };
-
-    systemd.services.prayer = {
-      wantedBy = [ "multi-user.target" ];
-      serviceConfig.Type = "forking";
-      preStart = ''
-        mkdir -m 0755 -p ${stateDir}
-        chown ${prayerUser}:${prayerGroup} ${stateDir}
-      '';
-      script = "${prayer}/sbin/prayer --config-file=${prayerCfg}";
-    };
-  };
-}
diff --git a/nixos/modules/services/networking/squid.nix b/nixos/modules/services/networking/squid.nix
index 914cd7f320c9b..f93bcf19f2b30 100644
--- a/nixos/modules/services/networking/squid.nix
+++ b/nixos/modules/services/networking/squid.nix
@@ -176,7 +176,7 @@ in
       serviceConfig = {
         PIDFile="/run/squid.pid";
         ExecStart  = "${cfg.package}/bin/squid --foreground -YCs -f ${squidConfig}";
-        ExecReload="kill -HUP $MAINPID";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
         KillMode="mixed";
         NotifyAccess="all";
       };
diff --git a/nixos/modules/services/networking/syncthing.nix b/nixos/modules/services/networking/syncthing.nix
index bdcdaf056d038..6d9af6141f12c 100644
--- a/nixos/modules/services/networking/syncthing.nix
+++ b/nixos/modules/services/networking/syncthing.nix
@@ -666,7 +666,9 @@ in {
             ${cfg.package}/bin/syncthing \
               -no-browser \
               -gui-address=${if isUnixGui then "unix://" else ""}${cfg.guiAddress} \
-              -home=${cfg.configDir} ${escapeShellArgs cfg.extraFlags}
+              -config=${cfg.configDir} \
+              -data=${cfg.dataDir} \
+              ${escapeShellArgs cfg.extraFlags}
           '';
           MemoryDenyWriteExecute = true;
           NoNewPrivileges = true;
diff --git a/nixos/modules/services/networking/tox-bootstrapd.nix b/nixos/modules/services/networking/tox-bootstrapd.nix
index 5c7e7a4c22082..0f310a28d266d 100644
--- a/nixos/modules/services/networking/tox-bootstrapd.nix
+++ b/nixos/modules/services/networking/tox-bootstrapd.nix
@@ -47,7 +47,7 @@ in
               lib.mdDoc ''
                 Configuration for bootstrap daemon.
                 See <https://github.com/irungentoo/toxcore/blob/master/other/bootstrap_daemon/tox-bootstrapd.conf>
-                and <http://wiki.tox.im/Nodes>.
+                and <https://wiki.tox.chat/users/nodes>.
              '';
           };
       };
diff --git a/nixos/modules/services/networking/unbound.nix b/nixos/modules/services/networking/unbound.nix
index 0426dbb0c83c3..b6579af10a793 100644
--- a/nixos/modules/services/networking/unbound.nix
+++ b/nixos/modules/services/networking/unbound.nix
@@ -166,7 +166,7 @@ in {
     services.unbound.settings = {
       server = {
         directory = mkDefault cfg.stateDir;
-        username = cfg.user;
+        username = ''""'';
         chroot = ''""'';
         pidfile = ''""'';
         # when running under systemd there is no need to daemonize
@@ -245,14 +245,13 @@ in {
         NotifyAccess = "main";
         Type = "notify";
 
-        # FIXME: Which of these do we actually need, can we drop the chroot flag?
         AmbientCapabilities = [
           "CAP_NET_BIND_SERVICE"
+          "CAP_NET_RAW" # needed if ip-transparent is set to true
+        ];
+        CapabilityBoundingSet = [
+          "CAP_NET_BIND_SERVICE"
           "CAP_NET_RAW"
-          "CAP_SETGID"
-          "CAP_SETUID"
-          "CAP_SYS_CHROOT"
-          "CAP_SYS_RESOURCE"
         ];
 
         User = cfg.user;
@@ -266,22 +265,19 @@ in {
         ProtectControlGroups = true;
         ProtectKernelModules = true;
         ProtectSystem = "strict";
+        ProtectClock = true;
+        ProtectHostname = true;
+        ProtectProc = "invisible";
+        ProcSubset = "pid";
+        ProtectKernelLogs = true;
+        ProtectKernelTunables = true;
         RuntimeDirectory = "unbound";
         ConfigurationDirectory = "unbound";
         StateDirectory = "unbound";
         RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_NETLINK" "AF_UNIX" ];
         RestrictRealtime = true;
         SystemCallArchitectures = "native";
-        SystemCallFilter = [
-          "~@clock"
-          "@cpu-emulation"
-          "@debug"
-          "@keyring"
-          "@module"
-          "mount"
-          "@obsolete"
-          "@resources"
-        ];
+        SystemCallFilter = [ "@system-service" ];
         RestrictNamespaces = true;
         LockPersonality = true;
         RestrictSUIDSGID = true;
diff --git a/nixos/modules/services/networking/unifi.nix b/nixos/modules/services/networking/unifi.nix
index 6b68371098065..537a4db95ca70 100644
--- a/nixos/modules/services/networking/unifi.nix
+++ b/nixos/modules/services/networking/unifi.nix
@@ -1,60 +1,61 @@
 { config, options, lib, pkgs, utils, ... }:
-with lib;
 let
   cfg = config.services.unifi;
   stateDir = "/var/lib/unifi";
-  cmd = ''
-    @${cfg.jrePackage}/bin/java java \
-        ${optionalString (lib.versionAtLeast (lib.getVersion cfg.jrePackage) "16")
-        ("--add-opens java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.time=ALL-UNNAMED "
-        + "--add-opens java.base/sun.security.util=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED "
-        + "--add-opens java.rmi/sun.rmi.transport=ALL-UNNAMED")} \
-        ${optionalString (cfg.initialJavaHeapSize != null) "-Xms${(toString cfg.initialJavaHeapSize)}m"} \
-        ${optionalString (cfg.maximumJavaHeapSize != null) "-Xmx${(toString cfg.maximumJavaHeapSize)}m"} \
-        -jar ${stateDir}/lib/ace.jar
-  '';
+  cmd = lib.escapeShellArgs ([ "@${cfg.jrePackage}/bin/java" "java" ]
+    ++ lib.optionals (lib.versionAtLeast (lib.getVersion cfg.jrePackage) "16") [
+      "--add-opens=java.base/java.lang=ALL-UNNAMED"
+      "--add-opens=java.base/java.time=ALL-UNNAMED"
+      "--add-opens=java.base/sun.security.util=ALL-UNNAMED"
+      "--add-opens=java.base/java.io=ALL-UNNAMED"
+      "--add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED"
+    ]
+    ++ (lib.optional (cfg.initialJavaHeapSize != null) "-Xms${(toString cfg.initialJavaHeapSize)}m")
+    ++ (lib.optional (cfg.maximumJavaHeapSize != null) "-Xmx${(toString cfg.maximumJavaHeapSize)}m")
+    ++ cfg.extraJvmOptions
+    ++ [ "-jar" "${stateDir}/lib/ace.jar" ]);
 in
 {
 
   options = {
 
-    services.unifi.enable = mkOption {
-      type = types.bool;
+    services.unifi.enable = lib.mkOption {
+      type = lib.types.bool;
       default = false;
       description = lib.mdDoc ''
         Whether or not to enable the unifi controller service.
       '';
     };
 
-    services.unifi.jrePackage = mkOption {
-      type = types.package;
+    services.unifi.jrePackage = lib.mkOption {
+      type = lib.types.package;
       default = if (lib.versionAtLeast (lib.getVersion cfg.unifiPackage) "7.5") then pkgs.jdk17_headless else if (lib.versionAtLeast (lib.getVersion cfg.unifiPackage) "7.3") then pkgs.jdk11 else pkgs.jre8;
-      defaultText = literalExpression ''if (lib.versionAtLeast (lib.getVersion cfg.unifiPackage) "7.5") then pkgs.jdk17_headless else if (lib.versionAtLeast (lib.getVersion cfg.unifiPackage) "7.3" then pkgs.jdk11 else pkgs.jre8'';
+      defaultText = lib.literalExpression ''if (lib.versionAtLeast (lib.getVersion cfg.unifiPackage) "7.5") then pkgs.jdk17_headless else if (lib.versionAtLeast (lib.getVersion cfg.unifiPackage) "7.3" then pkgs.jdk11 else pkgs.jre8'';
       description = lib.mdDoc ''
         The JRE package to use. Check the release notes to ensure it is supported.
       '';
     };
 
-    services.unifi.unifiPackage = mkOption {
-      type = types.package;
+    services.unifi.unifiPackage = lib.mkOption {
+      type = lib.types.package;
       default = pkgs.unifi5;
-      defaultText = literalExpression "pkgs.unifi5";
+      defaultText = lib.literalExpression "pkgs.unifi5";
       description = lib.mdDoc ''
         The unifi package to use.
       '';
     };
 
-    services.unifi.mongodbPackage = mkOption {
-      type = types.package;
+    services.unifi.mongodbPackage = lib.mkOption {
+      type = lib.types.package;
       default = pkgs.mongodb-4_4;
-      defaultText = literalExpression "pkgs.mongodb";
+      defaultText = lib.literalExpression "pkgs.mongodb";
       description = lib.mdDoc ''
         The mongodb package to use. Please note: unifi7 officially only supports mongodb up until 3.6 but works with 4.4.
       '';
     };
 
-    services.unifi.openFirewall = mkOption {
-      type = types.bool;
+    services.unifi.openFirewall = lib.mkOption {
+      type = lib.types.bool;
       default = false;
       description = lib.mdDoc ''
         Whether or not to open the minimum required ports on the firewall.
@@ -65,8 +66,8 @@ in
       '';
     };
 
-    services.unifi.initialJavaHeapSize = mkOption {
-      type = types.nullOr types.int;
+    services.unifi.initialJavaHeapSize = lib.mkOption {
+      type = with lib.types; nullOr int;
       default = null;
       example = 1024;
       description = lib.mdDoc ''
@@ -75,8 +76,8 @@ in
       '';
     };
 
-    services.unifi.maximumJavaHeapSize = mkOption {
-      type = types.nullOr types.int;
+    services.unifi.maximumJavaHeapSize = lib.mkOption {
+      type = with lib.types; nullOr int;
       default = null;
       example = 4096;
       description = lib.mdDoc ''
@@ -85,9 +86,18 @@ in
       '';
     };
 
+    services.unifi.extraJvmOptions = lib.mkOption {
+      type = with lib.types; listOf str;
+      default = [ ];
+      example = lib.literalExpression ''["-Xlog:gc"]'';
+      description = lib.mdDoc ''
+        Set extra options to pass to the JVM.
+      '';
+    };
+
   };
 
-  config = mkIf cfg.enable {
+  config = lib.mkIf cfg.enable {
 
     users.users.unifi = {
       isSystemUser = true;
@@ -97,7 +107,7 @@ in
     };
     users.groups.unifi = {};
 
-    networking.firewall = mkIf cfg.openFirewall {
+    networking.firewall = lib.mkIf cfg.openFirewall {
       # https://help.ubnt.com/hc/en-us/articles/218506997
       allowedTCPPorts = [
         8080  # Port for UAP to inform controller.
@@ -123,8 +133,8 @@ in
 
       serviceConfig = {
         Type = "simple";
-        ExecStart = "${(removeSuffix "\n" cmd)} start";
-        ExecStop = "${(removeSuffix "\n" cmd)} stop";
+        ExecStart = "${cmd} start";
+        ExecStop = "${cmd} stop";
         Restart = "on-failure";
         TimeoutSec = "5min";
         User = "unifi";
@@ -166,7 +176,7 @@ in
         StateDirectory = "unifi";
         RuntimeDirectory = "unifi";
         LogsDirectory = "unifi";
-        CacheDirectory= "unifi";
+        CacheDirectory = "unifi";
 
         TemporaryFileSystem = [
           # required as we want to create bind mounts below
@@ -176,7 +186,7 @@ in
         # We must create the binary directories as bind mounts instead of symlinks
         # This is because the controller resolves all symlinks to absolute paths
         # to be used as the working directory.
-        BindPaths =  [
+        BindPaths = [
           "/var/log/unifi:${stateDir}/logs"
           "/run/unifi:${stateDir}/run"
           "${cfg.unifiPackage}/dl:${stateDir}/dl"
@@ -194,7 +204,7 @@ in
 
   };
   imports = [
-    (mkRemovedOptionModule [ "services" "unifi" "dataDir" ] "You should move contents of dataDir to /var/lib/unifi/data" )
-    (mkRenamedOptionModule [ "services" "unifi" "openPorts" ] [ "services" "unifi" "openFirewall" ])
+    (lib.mkRemovedOptionModule [ "services" "unifi" "dataDir" ] "You should move contents of dataDir to /var/lib/unifi/data")
+    (lib.mkRenamedOptionModule [ "services" "unifi" "openPorts" ] [ "services" "unifi" "openFirewall" ])
   ];
 }
diff --git a/nixos/modules/services/printing/cupsd.nix b/nixos/modules/services/printing/cupsd.nix
index 25367f8e61d49..3a2744303474f 100644
--- a/nixos/modules/services/printing/cupsd.nix
+++ b/nixos/modules/services/printing/cupsd.nix
@@ -109,11 +109,10 @@ let
   getGutenprint = pkgs: head (filterGutenprint pkgs);
 
   parsePorts = addresses: let
-    splitAddress = addr: lib.strings.splitString ":" addr;
-    extractPort = addr: builtins.elemAt (builtins.tail (splitAddress addr)) 0;
-    toInt = str: lib.strings.toInt str;
+    splitAddress = addr: strings.splitString ":" addr;
+    extractPort = addr: builtins.foldl' (a: b: b) "" (splitAddress addr);
   in
-    builtins.map (address: toInt (extractPort address)) addresses;
+    builtins.map (address: strings.toInt (extractPort address)) addresses;
 
 in
 
diff --git a/nixos/modules/services/search/kibana.nix b/nixos/modules/services/search/kibana.nix
deleted file mode 100644
index a5e132d5c38db..0000000000000
--- a/nixos/modules/services/search/kibana.nix
+++ /dev/null
@@ -1,213 +0,0 @@
-{ config, lib, options, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.kibana;
-  opt = options.services.kibana;
-
-  ge7 = builtins.compareVersions cfg.package.version "7" >= 0;
-  lt6_6 = builtins.compareVersions cfg.package.version "6.6" < 0;
-
-  cfgFile = pkgs.writeText "kibana.json" (builtins.toJSON (
-    (filterAttrsRecursive (n: v: v != null && v != []) ({
-      server.host = cfg.listenAddress;
-      server.port = cfg.port;
-      server.ssl.certificate = cfg.cert;
-      server.ssl.key = cfg.key;
-
-      kibana.index = cfg.index;
-      kibana.defaultAppId = cfg.defaultAppId;
-
-      elasticsearch.url = cfg.elasticsearch.url;
-      elasticsearch.hosts = cfg.elasticsearch.hosts;
-      elasticsearch.username = cfg.elasticsearch.username;
-      elasticsearch.password = cfg.elasticsearch.password;
-
-      elasticsearch.ssl.certificate = cfg.elasticsearch.cert;
-      elasticsearch.ssl.key = cfg.elasticsearch.key;
-      elasticsearch.ssl.certificateAuthorities = cfg.elasticsearch.certificateAuthorities;
-    } // cfg.extraConf)
-  )));
-
-in {
-  options.services.kibana = {
-    enable = mkEnableOption (lib.mdDoc "kibana service");
-
-    listenAddress = mkOption {
-      description = lib.mdDoc "Kibana listening host";
-      default = "127.0.0.1";
-      type = types.str;
-    };
-
-    port = mkOption {
-      description = lib.mdDoc "Kibana listening port";
-      default = 5601;
-      type = types.port;
-    };
-
-    cert = mkOption {
-      description = lib.mdDoc "Kibana ssl certificate.";
-      default = null;
-      type = types.nullOr types.path;
-    };
-
-    key = mkOption {
-      description = lib.mdDoc "Kibana ssl key.";
-      default = null;
-      type = types.nullOr types.path;
-    };
-
-    index = mkOption {
-      description = lib.mdDoc "Elasticsearch index to use for saving kibana config.";
-      default = ".kibana";
-      type = types.str;
-    };
-
-    defaultAppId = mkOption {
-      description = lib.mdDoc "Elasticsearch default application id.";
-      default = "discover";
-      type = types.str;
-    };
-
-    elasticsearch = {
-      url = mkOption {
-        description = lib.mdDoc ''
-          Elasticsearch url.
-
-          Defaults to `"http://localhost:9200"`.
-
-          Don't set this when using Kibana >= 7.0.0 because it will result in a
-          configuration error. Use {option}`services.kibana.elasticsearch.hosts`
-          instead.
-        '';
-        default = null;
-        type = types.nullOr types.str;
-      };
-
-      hosts = mkOption {
-        description = lib.mdDoc ''
-          The URLs of the Elasticsearch instances to use for all your queries.
-          All nodes listed here must be on the same cluster.
-
-          Defaults to `[ "http://localhost:9200" ]`.
-
-          This option is only valid when using kibana >= 6.6.
-        '';
-        default = null;
-        type = types.nullOr (types.listOf types.str);
-      };
-
-      username = mkOption {
-        description = lib.mdDoc "Username for elasticsearch basic auth.";
-        default = null;
-        type = types.nullOr types.str;
-      };
-
-      password = mkOption {
-        description = lib.mdDoc "Password for elasticsearch basic auth.";
-        default = null;
-        type = types.nullOr types.str;
-      };
-
-      ca = mkOption {
-        description = lib.mdDoc ''
-          CA file to auth against elasticsearch.
-
-          It's recommended to use the {option}`certificateAuthorities` option
-          when using kibana-5.4 or newer.
-        '';
-        default = null;
-        type = types.nullOr types.path;
-      };
-
-      certificateAuthorities = mkOption {
-        description = lib.mdDoc ''
-          CA files to auth against elasticsearch.
-
-          Please use the {option}`ca` option when using kibana \< 5.4
-          because those old versions don't support setting multiple CA's.
-
-          This defaults to the singleton list [ca] when the {option}`ca` option is defined.
-        '';
-        default = lib.optional (cfg.elasticsearch.ca != null) ca;
-        defaultText = literalExpression ''
-          lib.optional (config.${opt.elasticsearch.ca} != null) ca
-        '';
-        type = types.listOf types.path;
-      };
-
-      cert = mkOption {
-        description = lib.mdDoc "Certificate file to auth against elasticsearch.";
-        default = null;
-        type = types.nullOr types.path;
-      };
-
-      key = mkOption {
-        description = lib.mdDoc "Key file to auth against elasticsearch.";
-        default = null;
-        type = types.nullOr types.path;
-      };
-    };
-
-    package = mkOption {
-      description = lib.mdDoc "Kibana package to use";
-      default = pkgs.kibana;
-      defaultText = literalExpression "pkgs.kibana";
-      type = types.package;
-    };
-
-    dataDir = mkOption {
-      description = lib.mdDoc "Kibana data directory";
-      default = "/var/lib/kibana";
-      type = types.path;
-    };
-
-    extraConf = mkOption {
-      description = lib.mdDoc "Kibana extra configuration";
-      default = {};
-      type = types.attrs;
-    };
-  };
-
-  config = mkIf (cfg.enable) {
-    assertions = [
-      {
-        assertion = ge7 -> cfg.elasticsearch.url == null;
-        message =
-          "The option services.kibana.elasticsearch.url has been removed when using kibana >= 7.0.0. " +
-          "Please use option services.kibana.elasticsearch.hosts instead.";
-      }
-      {
-        assertion = lt6_6 -> cfg.elasticsearch.hosts == null;
-        message =
-          "The option services.kibana.elasticsearch.hosts is only valid for kibana >= 6.6.";
-      }
-    ];
-    systemd.services.kibana = {
-      description = "Kibana Service";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" "elasticsearch.service" ];
-      environment = { BABEL_CACHE_PATH = "${cfg.dataDir}/.babelcache.json"; };
-      serviceConfig = {
-        ExecStart =
-          "${cfg.package}/bin/kibana" +
-          " --config ${cfgFile}" +
-          " --path.data ${cfg.dataDir}";
-        User = "kibana";
-        WorkingDirectory = cfg.dataDir;
-      };
-    };
-
-    environment.systemPackages = [ cfg.package ];
-
-    users.users.kibana = {
-      isSystemUser = true;
-      description = "Kibana service user";
-      home = cfg.dataDir;
-      createHome = true;
-      group = "kibana";
-    };
-    users.groups.kibana = {};
-  };
-}
diff --git a/nixos/modules/services/search/opensearch.nix b/nixos/modules/services/search/opensearch.nix
index 9a50e79631380..ae79d5545fd76 100644
--- a/nixos/modules/services/search/opensearch.nix
+++ b/nixos/modules/services/search/opensearch.nix
@@ -72,6 +72,18 @@ in
             The port to listen on for transport traffic.
           '';
         };
+
+        options."plugins.security.disabled" = lib.mkOption {
+          type = lib.types.bool;
+          default = true;
+          description = lib.mdDoc ''
+            Whether to enable the security plugin,
+            `plugins.security.ssl.transport.keystore_filepath` or
+            `plugins.security.ssl.transport.server.pemcert_filepath` and
+            `plugins.security.ssl.transport.client.pemcert_filepath`
+            must be set for this plugin to be enabled.
+          '';
+        };
       };
 
       default = {};
@@ -186,6 +198,13 @@ in
               shopt -s inherit_errexit
 
               # Install plugins
+
+              # remove plugins directory if it is empty.
+              if [ -z "$(ls -A ${cfg.dataDir}/plugins)" ]; then
+                rm -r "${cfg.dataDir}/plugins"
+              fi
+
+              ln -sfT "${cfg.package}/plugins" "${cfg.dataDir}/plugins"
               ln -sfT ${cfg.package}/lib ${cfg.dataDir}/lib
               ln -sfT ${cfg.package}/modules ${cfg.dataDir}/modules
 
diff --git a/nixos/modules/services/security/clamav.nix b/nixos/modules/services/security/clamav.nix
index 34897a9ac7db8..c3893f4b09b20 100644
--- a/nixos/modules/services/security/clamav.nix
+++ b/nixos/modules/services/security/clamav.nix
@@ -15,6 +15,9 @@ let
 
   clamdConfigFile = pkgs.writeText "clamd.conf" (toKeyValue cfg.daemon.settings);
   freshclamConfigFile = pkgs.writeText "freshclam.conf" (toKeyValue cfg.updater.settings);
+  fangfrischConfigFile = pkgs.writeText "fangfrisch.conf" ''
+    ${lib.generators.toINI {} cfg.fangfrisch.settings}
+  '';
 in
 {
   imports = [
@@ -66,6 +69,36 @@ in
           '';
         };
       };
+      fangfrisch = {
+        enable = mkEnableOption (lib.mdDoc "ClamAV fangfrisch updater");
+
+        interval = mkOption {
+          type = types.str;
+          default = "hourly";
+          description = lib.mdDoc ''
+            How often freshclam is invoked. See systemd.time(7) for more
+            information about the format.
+          '';
+        };
+
+        settings = mkOption {
+          type = lib.types.submodule {
+            freeformType = with types; attrsOf (attrsOf (oneOf [ str int bool ]));
+          };
+          default = { };
+          example = {
+            securiteinfo = {
+              enabled = "yes";
+              customer_id = "your customer_id";
+            };
+          };
+          description = lib.mdDoc ''
+            fangfrisch configuration. Refer to <https://rseichter.github.io/fangfrisch/#_configuration>,
+            for details on supported values.
+            Note that by default urlhaus and sanesecurity are enabled.
+          '';
+        };
+      };
     };
   };
 
@@ -98,6 +131,15 @@ in
       DatabaseMirror = [ "database.clamav.net" ];
     };
 
+    services.clamav.fangfrisch.settings = {
+      DEFAULT.db_url = mkDefault "sqlite:////var/lib/clamav/fangfrisch_db.sqlite";
+      DEFAULT.local_directory = mkDefault stateDir;
+      DEFAULT.log_level = mkDefault "INFO";
+      urlhaus.enabled = mkDefault "yes";
+      urlhaus.max_size = mkDefault "2MB";
+      sanesecurity.enabled = mkDefault "yes";
+    };
+
     environment.etc."clamav/freshclam.conf".source = freshclamConfigFile;
     environment.etc."clamav/clamd.conf".source = clamdConfigFile;
 
@@ -107,14 +149,13 @@ in
       wantedBy = [ "multi-user.target" ];
       restartTriggers = [ clamdConfigFile ];
 
-      preStart = ''
-        mkdir -m 0755 -p ${runDir}
-        chown ${clamavUser}:${clamavGroup} ${runDir}
-      '';
-
       serviceConfig = {
         ExecStart = "${pkg}/bin/clamd";
         ExecReload = "${pkgs.coreutils}/bin/kill -USR2 $MAINPID";
+        User = clamavUser;
+        Group = clamavGroup;
+        StateDirectory = "clamav";
+        RuntimeDirectory = "clamav";
         PrivateTmp = "yes";
         PrivateDevices = "yes";
         PrivateNetwork = "yes";
@@ -134,15 +175,63 @@ in
       description = "ClamAV virus database updater (freshclam)";
       restartTriggers = [ freshclamConfigFile ];
       after = [ "network-online.target" ];
-      preStart = ''
-        mkdir -m 0755 -p ${stateDir}
-        chown ${clamavUser}:${clamavGroup} ${stateDir}
-      '';
 
       serviceConfig = {
         Type = "oneshot";
         ExecStart = "${pkg}/bin/freshclam";
         SuccessExitStatus = "1"; # if databases are up to date
+        StateDirectory = "clamav";
+        RuntimeDirectory = "clamav";
+        User = clamavUser;
+        Group = clamavGroup;
+        PrivateTmp = "yes";
+        PrivateDevices = "yes";
+      };
+    };
+
+    systemd.services.clamav-fangfrisch-init = mkIf cfg.fangfrisch.enable {
+      wantedBy = [ "multi-user.target" ];
+      # if the sqlite file can be found assume the database has already been initialised
+      script = ''
+        db_url="${cfg.fangfrisch.settings.DEFAULT.db_url}"
+        db_path="''${db_url#sqlite:///}"
+
+        if [ ! -f "$db_path" ]; then
+          ${pkgs.fangfrisch}/bin/fangfrisch --conf ${fangfrischConfigFile} initdb
+        fi
+      '';
+      serviceConfig = {
+        Type = "oneshot";
+        StateDirectory = "clamav";
+        RuntimeDirectory = "clamav";
+        User = clamavUser;
+        Group = clamavGroup;
+        PrivateTmp = "yes";
+        PrivateDevices = "yes";
+      };
+    };
+
+    systemd.timers.clamav-fangfrisch = mkIf cfg.fangfrisch.enable {
+      description = "Timer for ClamAV virus database updater (fangfrisch)";
+      wantedBy = [ "timers.target" ];
+      timerConfig = {
+        OnCalendar = cfg.fangfrisch.interval;
+        Unit = "clamav-fangfrisch.service";
+      };
+    };
+
+    systemd.services.clamav-fangfrisch = mkIf cfg.fangfrisch.enable {
+      description = "ClamAV virus database updater (fangfrisch)";
+      restartTriggers = [ fangfrischConfigFile ];
+      after = [ "network-online.target" "clamav-fangfrisch-init.service" ];
+
+      serviceConfig = {
+        Type = "oneshot";
+        ExecStart = "${pkgs.fangfrisch}/bin/fangfrisch --conf ${fangfrischConfigFile} refresh";
+        StateDirectory = "clamav";
+        RuntimeDirectory = "clamav";
+        User = clamavUser;
+        Group = clamavGroup;
         PrivateTmp = "yes";
         PrivateDevices = "yes";
       };
diff --git a/nixos/modules/services/security/hockeypuck.nix b/nixos/modules/services/security/hockeypuck.nix
index 127134bc5dba5..56c13d791920c 100644
--- a/nixos/modules/services/security/hockeypuck.nix
+++ b/nixos/modules/services/security/hockeypuck.nix
@@ -55,7 +55,7 @@ in {
             ensureDatabases = [ "hockeypuck" ];
             ensureUsers = [{
               name = "hockeypuck";
-              ensurePermissions."DATABASE hockeypuck" = "ALL PRIVILEGES";
+              ensureDBOwnership = true;
             }];
           };
         ```
diff --git a/nixos/modules/services/torrent/flexget.nix b/nixos/modules/services/torrent/flexget.nix
index 5cd7ae6ad7db3..58a4b70014977 100644
--- a/nixos/modules/services/torrent/flexget.nix
+++ b/nixos/modules/services/torrent/flexget.nix
@@ -64,7 +64,6 @@ in {
         path = [ pkg ];
         serviceConfig = {
           User = cfg.user;
-          Environment = "TZ=${config.time.timeZone}";
           ExecStartPre = "${pkgs.coreutils}/bin/install -m644 ${ymlFile} ${configFile}";
           ExecStart = "${pkg}/bin/flexget -c ${configFile} daemon start";
           ExecStop = "${pkg}/bin/flexget -c ${configFile} daemon stop";
diff --git a/nixos/modules/services/torrent/transmission.nix b/nixos/modules/services/torrent/transmission.nix
index b98cb5283a1a6..5efb9334ea03e 100644
--- a/nixos/modules/services/torrent/transmission.nix
+++ b/nixos/modules/services/torrent/transmission.nix
@@ -148,7 +148,7 @@ in
             type = types.bool;
             default = true;
             description = lib.mdDoc ''
-              Whether to enable [Micro Transport Protocol (µTP)](http://en.wikipedia.org/wiki/Micro_Transport_Protocol).
+              Whether to enable [Micro Transport Protocol (µTP)](https://en.wikipedia.org/wiki/Micro_Transport_Protocol).
             '';
           };
           options.watch-dir = mkOption {
diff --git a/nixos/modules/services/video/unifi-video.nix b/nixos/modules/services/video/unifi-video.nix
index cb438a08150f7..5c93f60cbd79e 100644
--- a/nixos/modules/services/video/unifi-video.nix
+++ b/nixos/modules/services/video/unifi-video.nix
@@ -32,7 +32,7 @@ let
     name = "mongo.conf";
     executable = false;
     text = ''
-      # for documentation of all options, see http://docs.mongodb.org/manual/reference/configuration-options/
+      # for documentation of all options, see https://www.mongodb.com/docs/manual/reference/configuration-options/
 
       storage:
          dbPath: ${cfg.dataDir}/db
@@ -63,7 +63,7 @@ let
     executable = false;
     text = ''
       # for documentation of all options, see:
-      #   http://docs.mongodb.org/manual/reference/configuration-options/
+      #   https://www.mongodb.com/docs/manual/reference/configuration-options/
 
       storage:
          dbPath: ${cfg.dataDir}/db-wt
diff --git a/nixos/modules/services/web-apps/akkoma.nix b/nixos/modules/services/web-apps/akkoma.nix
index 5f9bbbd663744..8a8ed49115fdd 100644
--- a/nixos/modules/services/web-apps/akkoma.nix
+++ b/nixos/modules/services/web-apps/akkoma.nix
@@ -909,7 +909,7 @@ in {
   };
 
   config = mkIf cfg.enable {
-    warnings = optionals (!config.security.sudo.enable) [''
+    warnings = optionals (with config.security; (!sudo.enable) && (!sudo-rs.enable)) [''
       The pleroma_ctl wrapper enabled by the installWrapper option relies on
       sudo, which appears to have been disabled through security.sudo.enable.
     ''];
diff --git a/nixos/modules/services/web-apps/code-server.nix b/nixos/modules/services/web-apps/code-server.nix
deleted file mode 100644
index 11601f6c30449..0000000000000
--- a/nixos/modules/services/web-apps/code-server.nix
+++ /dev/null
@@ -1,259 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-let
-  cfg = config.services.code-server;
-  defaultUser = "code-server";
-  defaultGroup = defaultUser;
-in {
-  options = {
-    services.code-server = {
-      enable = lib.mkEnableOption (lib.mdDoc "code-server");
-
-      package = lib.mkPackageOptionMD pkgs "code-server" {
-        example = ''
-          pkgs.vscode-with-extensions.override {
-            vscode = pkgs.code-server;
-            vscodeExtensions = with pkgs.vscode-extensions; [
-              bbenoist.nix
-              dracula-theme.theme-dracula
-            ];
-          }
-        '';
-      };
-
-      extraPackages = lib.mkOption {
-        default = [ ];
-        description = lib.mdDoc ''
-          Additional packages to add to the code-server {env}`PATH`.
-        '';
-        example = lib.literalExpression "[ pkgs.go ]";
-        type = lib.types.listOf lib.types.package;
-      };
-
-      extraEnvironment = lib.mkOption {
-        type = lib.types.attrsOf lib.types.str;
-        description = lib.mdDoc ''
-          Additional environment variables to pass to code-server.
-        '';
-        default = { };
-        example = { PKG_CONFIG_PATH = "/run/current-system/sw/lib/pkgconfig"; };
-      };
-
-      extraArguments = lib.mkOption {
-        default = [ ];
-        description = lib.mdDoc ''
-          Additional arguments to pass to code-server.
-        '';
-        example = lib.literalExpression ''[ "--log=info" ]'';
-        type = lib.types.listOf lib.types.str;
-      };
-
-      host = lib.mkOption {
-        default = "localhost";
-        description = lib.mdDoc ''
-          The host name or IP address the server should listen to.
-        '';
-        type = lib.types.str;
-      };
-
-      port = lib.mkOption {
-        default = 4444;
-        description = lib.mdDoc ''
-          The port the server should listen to.
-        '';
-        type = lib.types.port;
-      };
-
-      auth = lib.mkOption {
-        default = "password";
-        description = lib.mdDoc ''
-          The type of authentication to use.
-        '';
-        type = lib.types.enum [ "none" "password" ];
-      };
-
-      hashedPassword = lib.mkOption {
-        default = "";
-        description = lib.mdDoc ''
-          Create the password with: `echo -n 'thisismypassword' | npx argon2-cli -e`.
-        '';
-        type = lib.types.str;
-      };
-
-      user = lib.mkOption {
-        default = defaultUser;
-        example = "yourUser";
-        description = lib.mdDoc ''
-          The user to run code-server as.
-          By default, a user named `${defaultUser}` will be created.
-        '';
-        type = lib.types.str;
-      };
-
-      group = lib.mkOption {
-        default = defaultGroup;
-        example = "yourGroup";
-        description = lib.mdDoc ''
-          The group to run code-server under.
-          By default, a group named `${defaultGroup}` will be created.
-        '';
-        type = lib.types.str;
-      };
-
-      extraGroups = lib.mkOption {
-        default = [ ];
-        description = lib.mdDoc ''
-          An array of additional groups for the `${defaultUser}` user.
-        '';
-        example = [ "docker" ];
-        type = lib.types.listOf lib.types.str;
-      };
-
-      socket = lib.mkOption {
-        default = null;
-        example = "/run/code-server/socket";
-        description = lib.mdDoc ''
-          Path to a socket (bind-addr will be ignored).
-        '';
-        type = lib.types.nullOr lib.types.str;
-      };
-
-      socketMode = lib.mkOption {
-        default = null;
-        description = lib.mdDoc ''
-           File mode of the socket.
-        '';
-        type = lib.types.nullOr lib.types.str;
-      };
-
-      userDataDir = lib.mkOption {
-        default = null;
-        description = lib.mdDoc ''
-          Path to the user data directory.
-        '';
-        type = lib.types.nullOr lib.types.str;
-      };
-
-      extensionsDir = lib.mkOption {
-        default = null;
-        description = lib.mdDoc ''
-          Path to the extensions directory.
-        '';
-        type = lib.types.nullOr lib.types.str;
-      };
-
-      proxyDomain = lib.mkOption {
-        default = null;
-        example = "code-server.lan";
-        description = lib.mdDoc ''
-          Domain used for proxying ports.
-        '';
-        type = lib.types.nullOr lib.types.str;
-      };
-
-      disableTelemetry = lib.mkOption {
-        default = false;
-        example = true;
-        description = lib.mdDoc ''
-          Disable telemetry.
-        '';
-        type = lib.types.bool;
-      };
-
-      disableUpdateCheck = lib.mkOption {
-        default = false;
-        example = true;
-        description = lib.mdDoc ''
-          Disable update check.
-          Without this flag, code-server checks every 6 hours against the latest github release and
-          then notifies you once every week that a new release is available.
-        '';
-        type = lib.types.bool;
-      };
-
-      disableFileDownloads = lib.mkOption {
-        default = false;
-        example = true;
-        description = lib.mdDoc ''
-          Disable file downloads from Code.
-        '';
-        type = lib.types.bool;
-      };
-
-      disableWorkspaceTrust = lib.mkOption {
-        default = false;
-        example = true;
-        description = lib.mdDoc ''
-          Disable Workspace Trust feature.
-        '';
-        type = lib.types.bool;
-      };
-
-      disableGettingStartedOverride = lib.mkOption {
-        default = false;
-        example = true;
-        description = lib.mdDoc ''
-          Disable the coder/coder override in the Help: Getting Started page.
-        '';
-        type = lib.types.bool;
-      };
-
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    systemd.services.code-server = {
-      description = "Code server";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "network-online.target" ];
-      path = cfg.extraPackages;
-      environment = {
-        HASHED_PASSWORD = cfg.hashedPassword;
-      } // cfg.extraEnvironment;
-      serviceConfig = {
-        ExecStart = ''
-          ${lib.getExe cfg.package} \
-            --auth=${cfg.auth} \
-            --bind-addr=${cfg.host}:${toString cfg.port} \
-          '' + lib.optionalString (cfg.socket != null) ''
-            --socket=${cfg.socket} \
-          '' + lib.optionalString (cfg.userDataDir != null) ''
-            --user-data-dir=${cfg.userDataDir} \
-          '' + lib.optionalString (cfg.extensionsDir != null) ''
-            --extensions-dir=${cfg.extensionsDir} \
-          '' + lib.optionalString (cfg.disableTelemetry == true) ''
-            --disable-telemetry \
-          '' + lib.optionalString (cfg.disableUpdateCheck == true) ''
-            --disable-update-check \
-          '' + lib.optionalString (cfg.disableFileDownloads == true) ''
-            --disable-file-downloads \
-          '' + lib.optionalString (cfg.disableWorkspaceTrust == true) ''
-            --disable-workspace-trust \
-          '' + lib.optionalString (cfg.disableGettingStartedOverride == true) ''
-            --disable-getting-started-override \
-          '' + lib.escapeShellArgs cfg.extraArguments;
-        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
-        RuntimeDirectory = cfg.user;
-        User = cfg.user;
-        Group = cfg.group;
-        Restart = "on-failure";
-      };
-    };
-
-    users.users."${cfg.user}" = lib.mkMerge [
-      (lib.mkIf (cfg.user == defaultUser) {
-        isNormalUser = true;
-        description = "code-server user";
-        inherit (cfg) group;
-      })
-      {
-        packages = cfg.extraPackages;
-        inherit (cfg) extraGroups;
-      }
-    ];
-
-    users.groups."${defaultGroup}" = lib.mkIf (cfg.group == defaultGroup) { };
-  };
-
-  meta.maintainers = [ lib.maintainers.stackshadow ];
-}
diff --git a/nixos/modules/services/web-apps/coder.nix b/nixos/modules/services/web-apps/coder.nix
index 469a29bc3aa8c..f65211308c402 100644
--- a/nixos/modules/services/web-apps/coder.nix
+++ b/nixos/modules/services/web-apps/coder.nix
@@ -149,8 +149,8 @@ in {
 
   config = mkIf cfg.enable {
     assertions = [
-      { assertion = cfg.database.createLocally -> cfg.database.username == name;
-        message = "services.coder.database.username must be set to ${user} if services.coder.database.createLocally is set true";
+      { assertion = cfg.database.createLocally -> cfg.database.username == name && cfg.database.database == cfg.database.username;
+        message = "services.coder.database.username must be set to ${name} if services.coder.database.createLocally is set true";
       }
     ];
 
@@ -193,10 +193,8 @@ in {
         cfg.database.database
       ];
       ensureUsers = [{
-        name = cfg.database.username;
-        ensurePermissions = {
-          "DATABASE \"${cfg.database.database}\"" = "ALL PRIVILEGES";
-        };
+        name = cfg.user;
+        ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/web-apps/discourse.nix b/nixos/modules/services/web-apps/discourse.nix
index f80eb6b4c7f02..da1dba7d940ba 100644
--- a/nixos/modules/services/web-apps/discourse.nix
+++ b/nixos/modules/services/web-apps/discourse.nix
@@ -407,7 +407,7 @@ in
             type = with lib.types; nullOr (enum ["plain" "login" "cram_md5"]);
             default = null;
             description = lib.mdDoc ''
-              Authentication type to use, see http://api.rubyonrails.org/classes/ActionMailer/Base.html
+              Authentication type to use, see https://api.rubyonrails.org/classes/ActionMailer/Base.html
             '';
           };
 
@@ -423,7 +423,7 @@ in
             type = lib.types.str;
             default = "peer";
             description = lib.mdDoc ''
-              How OpenSSL checks the certificate, see http://api.rubyonrails.org/classes/ActionMailer/Base.html
+              How OpenSSL checks the certificate, see https://api.rubyonrails.org/classes/ActionMailer/Base.html
             '';
           };
 
diff --git a/nixos/modules/services/web-apps/gotosocial.nix b/nixos/modules/services/web-apps/gotosocial.nix
index f7ae018d5b7cc..9c21719a57590 100644
--- a/nixos/modules/services/web-apps/gotosocial.nix
+++ b/nixos/modules/services/web-apps/gotosocial.nix
@@ -128,9 +128,7 @@ in
       ensureUsers = [
         {
           name = "gotosocial";
-          ensurePermissions = {
-            "DATABASE gotosocial" = "ALL PRIVILEGES";
-          };
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/web-apps/invidious.nix b/nixos/modules/services/web-apps/invidious.nix
index 5603ef7392e82..e4fbc6fd93689 100644
--- a/nixos/modules/services/web-apps/invidious.nix
+++ b/nixos/modules/services/web-apps/invidious.nix
@@ -109,15 +109,17 @@ let
     # Default to using the local database if we create it
     services.invidious.database.host = lib.mkDefault null;
 
+
+    # TODO(raitobezarius to maintainers of invidious): I strongly advise to clean up the kemal specific
+    # thing for 24.05 and use `ensureDBOwnership`.
+    # See https://github.com/NixOS/nixpkgs/issues/216989
+    systemd.services.postgresql.postStart = lib.mkAfter ''
+      $PSQL -tAc 'ALTER DATABASE "${cfg.settings.db.dbname}" OWNER TO "${cfg.settings.db.user}";'
+    '';
     services.postgresql = {
       enable = true;
+      ensureUsers = lib.singleton { name = cfg.settings.db.user; ensureDBOwnership = false; };
       ensureDatabases = lib.singleton cfg.settings.db.dbname;
-      ensureUsers = lib.singleton {
-        name = cfg.settings.db.user;
-        ensurePermissions = {
-          "DATABASE ${cfg.settings.db.dbname}" = "ALL PRIVILEGES";
-        };
-      };
       # This is only needed because the unix user invidious isn't the same as
       # the database user. This tells postgres to map one to the other.
       identMap = ''
@@ -136,6 +138,7 @@ let
       documentation = [ "https://docs.invidious.io/Database-Information-and-Maintenance.md" ];
       startAt = lib.mkDefault "weekly";
       path = [ config.services.postgresql.package ];
+      after = [ "postgresql.service" ];
       script = ''
         psql ${cfg.settings.db.dbname} ${cfg.settings.db.user} -c "DELETE FROM nonces * WHERE expire < current_timestamp"
         psql ${cfg.settings.db.dbname} ${cfg.settings.db.user} -c "TRUNCATE TABLE videos"
diff --git a/nixos/modules/services/web-apps/invoiceplane.nix b/nixos/modules/services/web-apps/invoiceplane.nix
index f419b75cf70fe..429520470a0d4 100644
--- a/nixos/modules/services/web-apps/invoiceplane.nix
+++ b/nixos/modules/services/web-apps/invoiceplane.nix
@@ -28,7 +28,19 @@ let
     REMOVE_INDEXPHP=true
   '';
 
-  extraConfig = hostName: cfg: pkgs.writeText "extraConfig.php" ''
+  mkPhpValue = v:
+    if isString v then escapeShellArg v
+    # NOTE: If any value contains a , (comma) this will not get escaped
+    else if isList v && any lib.strings.isCoercibleToString v then escapeShellArg (concatMapStringsSep "," toString v)
+    else if isInt v then toString v
+    else if isBool v then boolToString v
+    else abort "The Invoiceplane config value ${lib.generators.toPretty {} v} can not be encoded."
+  ;
+
+  extraConfig = hostName: cfg: let
+    settings = mapAttrsToList (k: v: "${k}=${mkPhpValue v}") cfg.settings;
+  in pkgs.writeText "extraConfig.php" ''
+    ${concatStringsSep "\n" settings}
     ${toString cfg.extraConfig}
   '';
 
@@ -182,11 +194,31 @@ let
             InvoicePlane configuration. Refer to
             <https://github.com/InvoicePlane/InvoicePlane/blob/master/ipconfig.php.example>
             for details on supported values.
+
+            **Note**: Please pass structured settings via
+            `services.invoiceplane.sites.${name}.settings` instead, this option
+            will get deprecated in the future.
           '';
         };
 
-        cron = {
+        settings = mkOption {
+          type = types.attrsOf types.anything;
+          default = {};
+          description = lib.mdDoc ''
+            Structural InvoicePlane configuration. Refer to
+            <https://github.com/InvoicePlane/InvoicePlane/blob/master/ipconfig.php.example>
+            for details and supported values.
+          '';
+          example = literalExpression ''
+            {
+              SETUP_COMPLETED = true;
+              DISABLE_SETUP = true;
+              IP_URL = "https://invoice.example.com";
+            }
+          '';
+        };
 
+        cron = {
           enable = mkOption {
             type = types.bool;
             default = false;
@@ -197,12 +229,10 @@ let
               on how to configure it.
             '';
           };
-
           key = mkOption {
             type = types.str;
             description = lib.mdDoc "Cron key taken from the administration page.";
           };
-
         };
 
       };
@@ -239,8 +269,14 @@ in
   # implementation
   config = mkIf (eachSite != {}) (mkMerge [{
 
-    assertions = flatten (mapAttrsToList (hostName: cfg:
-      [{ assertion = cfg.database.createLocally -> cfg.database.user == user;
+    warnings = flatten (mapAttrsToList (hostName: cfg: [
+      (optional (cfg.extraConfig != null) ''
+        services.invoiceplane.sites."${hostName}".extraConfig will be deprecated in future releases, please use the settings option now.
+      '')
+    ]) eachSite);
+
+    assertions = flatten (mapAttrsToList (hostName: cfg: [
+      { assertion = cfg.database.createLocally -> cfg.database.user == user;
         message = ''services.invoiceplane.sites."${hostName}".database.user must be ${user} if the database is to be automatically provisioned'';
       }
       { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
diff --git a/nixos/modules/services/web-apps/jitsi-meet.nix b/nixos/modules/services/web-apps/jitsi-meet.nix
index 21416be358773..c0f9d785eea21 100644
--- a/nixos/modules/services/web-apps/jitsi-meet.nix
+++ b/nixos/modules/services/web-apps/jitsi-meet.nix
@@ -169,6 +169,15 @@ in
         off if you want to configure it manually.
       '';
     };
+
+    excalidraw.enable = mkEnableOption (lib.mdDoc "Excalidraw collaboration backend for Jitsi");
+    excalidraw.port = mkOption {
+      type = types.port;
+      default = 3002;
+      description = lib.mdDoc ''The port which the Excalidraw backend for Jitsi should listen to.'';
+    };
+
+    secureDomain.enable = mkEnableOption (lib.mdDoc "Authenticated room creation");
   };
 
   config = mkIf cfg.enable {
@@ -192,41 +201,118 @@ in
           roomLocking = false;
           roomDefaultPublicJids = true;
           extraConfig = ''
+            restrict_room_creation = true
+            storage = "memory"
+            admins = { "focus@auth.${cfg.hostName}" }
+          '';
+        }
+        {
+          domain = "breakout.${cfg.hostName}";
+          name = "Jitsi Meet Breakout MUC";
+          roomLocking = false;
+          roomDefaultPublicJids = true;
+          extraConfig = ''
+            restrict_room_creation = true
             storage = "memory"
+            admins = { "focus@auth.${cfg.hostName}" }
           '';
         }
         {
-          domain = "internal.${cfg.hostName}";
+          domain = "internal.auth.${cfg.hostName}";
           name = "Jitsi Meet Videobridge MUC";
+          roomLocking = false;
+          roomDefaultPublicJids = true;
           extraConfig = ''
             storage = "memory"
             admins = { "focus@auth.${cfg.hostName}", "jvb@auth.${cfg.hostName}" }
           '';
           #-- muc_room_cache_size = 1000
         }
+        {
+          domain = "lobby.${cfg.hostName}";
+          name = "Jitsi Meet Lobby MUC";
+          roomLocking = false;
+          roomDefaultPublicJids = true;
+          extraConfig = ''
+            restrict_room_creation = true
+            storage = "memory"
+          '';
+        }
+      ];
+      extraModules = [
+        "pubsub"
+        "smacks"
+        "speakerstats"
+        "external_services"
+        "conference_duration"
+        "end_conference"
+        "muc_lobby_rooms"
+        "muc_breakout_rooms"
+        "av_moderation"
+        "muc_hide_all"
+        "muc_meeting_id"
+        "muc_domain_mapper"
+        "muc_rate_limit"
+        "limits_exception"
+        "persistent_lobby"
+        "room_metadata"
       ];
-      extraModules = [ "pubsub" "smacks" ];
       extraPluginPaths = [ "${pkgs.jitsi-meet-prosody}/share/prosody-plugins" ];
-      extraConfig = lib.mkMerge [ (mkAfter ''
-        Component "focus.${cfg.hostName}" "client_proxy"
-          target_address = "focus@auth.${cfg.hostName}"
+      extraConfig = lib.mkMerge [
+        (mkAfter ''
+          Component "focus.${cfg.hostName}" "client_proxy"
+            target_address = "focus@auth.${cfg.hostName}"
+
+          Component "speakerstats.${cfg.hostName}" "speakerstats_component"
+            muc_component = "conference.${cfg.hostName}"
+
+          Component "conferenceduration.${cfg.hostName}" "conference_duration_component"
+            muc_component = "conference.${cfg.hostName}"
+
+          Component "endconference.${cfg.hostName}" "end_conference"
+            muc_component = "conference.${cfg.hostName}"
+
+          Component "avmoderation.${cfg.hostName}" "av_moderation_component"
+            muc_component = "conference.${cfg.hostName}"
+
+          Component "metadata.${cfg.hostName}" "room_metadata_component"
+            muc_component = "conference.${cfg.hostName}"
+            breakout_rooms_component = "breakout.${cfg.hostName}"
         '')
         (mkBefore ''
+          muc_mapper_domain_base = "${cfg.hostName}"
+
           cross_domain_websocket = true;
           consider_websocket_secure = true;
+
+          unlimited_jids = {
+            "focus@auth.${cfg.hostName}",
+            "jvb@auth.${cfg.hostName}"
+          }
         '')
       ];
       virtualHosts.${cfg.hostName} = {
         enabled = true;
         domain = cfg.hostName;
         extraConfig = ''
-          authentication = "anonymous"
+          authentication = ${if cfg.secureDomain.enable then "\"internal_hashed\"" else "\"jitsi-anonymous\""}
           c2s_require_encryption = false
           admins = { "focus@auth.${cfg.hostName}" }
           smacks_max_unacked_stanzas = 5
           smacks_hibernation_time = 60
           smacks_max_hibernated_sessions = 1
           smacks_max_old_sessions = 1
+
+          av_moderation_component = "avmoderation.${cfg.hostName}"
+          speakerstats_component = "speakerstats.${cfg.hostName}"
+          conference_duration_component = "conferenceduration.${cfg.hostName}"
+          end_conference_component = "endconference.${cfg.hostName}"
+
+          c2s_require_encryption = false
+          lobby_muc = "lobby.${cfg.hostName}"
+          breakout_rooms_muc = "breakout.${cfg.hostName}"
+          room_metadata_component = "metadata.${cfg.hostName}"
+          main_muc = "conference.${cfg.hostName}"
         '';
         ssl = {
           cert = "/var/lib/jitsi-meet/jitsi-meet.crt";
@@ -237,7 +323,7 @@ in
         enabled = true;
         domain = "auth.${cfg.hostName}";
         extraConfig = ''
-          authentication = "internal_plain"
+          authentication = "internal_hashed"
         '';
         ssl = {
           cert = "/var/lib/jitsi-meet/jitsi-meet.crt";
@@ -252,6 +338,14 @@ in
           c2s_require_encryption = false
         '';
       };
+      virtualHosts."guest.${cfg.hostName}" = {
+        enabled = true;
+        domain = "guest.${cfg.hostName}";
+        extraConfig = ''
+          authentication = "anonymous"
+          c2s_require_encryption = false
+        '';
+      };
     };
     systemd.services.prosody = mkIf cfg.prosody.enable {
       preStart = let
@@ -270,7 +364,7 @@ in
       reloadIfChanged = true;
     };
 
-    users.groups.jitsi-meet = {};
+    users.groups.jitsi-meet = { };
     systemd.tmpfiles.rules = [
       "d '/var/lib/jitsi-meet' 0750 root jitsi-meet - -"
     ];
@@ -317,6 +411,20 @@ in
       '';
     };
 
+    systemd.services.jitsi-excalidraw = mkIf cfg.excalidraw.enable {
+      description = "Excalidraw collaboration backend for Jitsi";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      environment.PORT = toString cfg.excalidraw.port;
+
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = "${pkgs.jitsi-excalidraw}/bin/jitsi-excalidraw-backend";
+        Restart = "on-failure";
+        Group = "jitsi-meet";
+      };
+    };
+
     services.nginx = mkIf cfg.nginx.enable {
       enable = mkDefault true;
       virtualHosts.${cfg.hostName} = {
@@ -345,12 +453,23 @@ in
         locations."=/external_api.js" = mkDefault {
           alias = "${pkgs.jitsi-meet}/libs/external_api.min.js";
         };
+        locations."=/_api/room-info" = {
+          proxyPass = "http://localhost:5280/room-info";
+          extraConfig = ''
+            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+            proxy_set_header Host $host;
+          '';
+        };
         locations."=/config.js" = mkDefault {
           alias = overrideJs "${pkgs.jitsi-meet}/config.js" "config" (recursiveUpdate defaultCfg cfg.config) cfg.extraConfig;
         };
         locations."=/interface_config.js" = mkDefault {
           alias = overrideJs "${pkgs.jitsi-meet}/interface_config.js" "interfaceConfig" cfg.interfaceConfig "";
         };
+        locations."/socket.io/" = mkIf cfg.excalidraw.enable {
+          proxyPass = "http://127.0.0.1:${toString cfg.excalidraw.port}";
+          proxyWebsockets = true;
+        };
       };
     };
 
@@ -359,7 +478,7 @@ in
       virtualHosts.${cfg.hostName} = {
         extraConfig =
         let
-          templatedJitsiMeet = pkgs.runCommand "templated-jitsi-meet" {} ''
+          templatedJitsiMeet = pkgs.runCommand "templated-jitsi-meet" { } ''
             cp -R ${pkgs.jitsi-meet}/* .
             for file in *.html **/*.html ; do
               ${pkgs.sd}/bin/sd '<!--#include virtual="(.*)" -->' '{{ include "$1" }}' $file
@@ -390,13 +509,24 @@ in
       };
     };
 
+    services.jitsi-meet.config = recursiveUpdate
+      (mkIf cfg.excalidraw.enable {
+        whiteboard = {
+          enabled = true;
+          collabServerBaseUrl = "https://${cfg.hostName}";
+        };
+      })
+      (mkIf cfg.secureDomain.enable {
+        hosts.anonymousdomain = "guest.${cfg.hostName}";
+      });
+
     services.jitsi-videobridge = mkIf cfg.videobridge.enable {
       enable = true;
       xmppConfigs."localhost" = {
         userName = "jvb";
         domain = "auth.${cfg.hostName}";
         passwordFile = "/var/lib/jitsi-meet/videobridge-secret";
-        mucJids = "jvbbrewery@internal.${cfg.hostName}";
+        mucJids = "jvbbrewery@internal.auth.${cfg.hostName}";
         disableCertificateVerification = true;
       };
     };
@@ -409,17 +539,27 @@ in
       userName = "focus";
       userPasswordFile = "/var/lib/jitsi-meet/jicofo-user-secret";
       componentPasswordFile = "/var/lib/jitsi-meet/jicofo-component-secret";
-      bridgeMuc = "jvbbrewery@internal.${cfg.hostName}";
+      bridgeMuc = "jvbbrewery@internal.auth.${cfg.hostName}";
       config = mkMerge [{
         jicofo.xmpp.service.disable-certificate-verification = true;
         jicofo.xmpp.client.disable-certificate-verification = true;
-      #} (lib.mkIf cfg.jibri.enable {
-       } (lib.mkIf (config.services.jibri.enable || cfg.jibri.enable) {
-         jicofo.jibri = {
-           brewery-jid = "JibriBrewery@internal.${cfg.hostName}";
-           pending-timeout = "90";
-         };
-      })];
+      }
+        (lib.mkIf (config.services.jibri.enable || cfg.jibri.enable) {
+          jicofo.jibri = {
+            brewery-jid = "JibriBrewery@internal.auth.${cfg.hostName}";
+            pending-timeout = "90";
+          };
+        })
+        (lib.mkIf cfg.secureDomain.enable {
+          jicofo = {
+            authentication = {
+              enabled = "true";
+              type = "XMPP";
+              login-url = cfg.hostName;
+            };
+            xmpp.client.client-proxy = "focus.${cfg.hostName}";
+          };
+        })];
     };
 
     services.jibri = mkIf cfg.jibri.enable {
@@ -430,7 +570,7 @@ in
         xmppDomain = cfg.hostName;
 
         control.muc = {
-          domain = "internal.${cfg.hostName}";
+          domain = "internal.auth.${cfg.hostName}";
           roomName = "JibriBrewery";
           nickname = "jibri";
         };
diff --git a/nixos/modules/services/web-apps/lemmy.nix b/nixos/modules/services/web-apps/lemmy.nix
index 20d9dcb7c2661..32389f7a59dd8 100644
--- a/nixos/modules/services/web-apps/lemmy.nix
+++ b/nixos/modules/services/web-apps/lemmy.nix
@@ -146,7 +146,7 @@ in
         ensureDatabases = [ cfg.settings.database.database ];
         ensureUsers = [{
           name = cfg.settings.database.user;
-          ensurePermissions."DATABASE ${cfg.settings.database.database}" = "ALL PRIVILEGES";
+          ensureDBOwnership = true;
         }];
       };
 
diff --git a/nixos/modules/services/web-apps/mastodon.nix b/nixos/modules/services/web-apps/mastodon.nix
index 2aab97438b7d6..8686506b1c282 100644
--- a/nixos/modules/services/web-apps/mastodon.nix
+++ b/nixos/modules/services/web-apps/mastodon.nix
@@ -17,9 +17,6 @@ let
     WEB_CONCURRENCY = toString cfg.webProcesses;
     MAX_THREADS = toString cfg.webThreads;
 
-    # mastodon-streaming concurrency.
-    STREAMING_CLUSTER_NUM = toString cfg.streamingProcesses;
-
     DB_USER = cfg.database.user;
 
     REDIS_HOST = cfg.redis.host;
@@ -33,13 +30,15 @@ let
     PAPERCLIP_ROOT_PATH = "/var/lib/mastodon/public-system";
     PAPERCLIP_ROOT_URL = "/system";
     ES_ENABLED = if (cfg.elasticsearch.host != null) then "true" else "false";
-    ES_HOST = cfg.elasticsearch.host;
-    ES_PORT = toString(cfg.elasticsearch.port);
 
     TRUSTED_PROXY_IP = cfg.trustedProxy;
   }
   // lib.optionalAttrs (cfg.database.host != "/run/postgresql" && cfg.database.port != null) { DB_PORT = toString cfg.database.port; }
   // lib.optionalAttrs cfg.smtp.authenticate { SMTP_LOGIN  = cfg.smtp.user; }
+  // lib.optionalAttrs (cfg.elasticsearch.host != null) { ES_HOST = cfg.elasticsearch.host; }
+  // lib.optionalAttrs (cfg.elasticsearch.host != null) { ES_PORT = toString(cfg.elasticsearch.port); }
+  // lib.optionalAttrs (cfg.elasticsearch.host != null) { ES_PRESET = cfg.elasticsearch.preset; }
+  // lib.optionalAttrs (cfg.elasticsearch.user != null) { ES_USER = cfg.elasticsearch.user; }
   // cfg.extraConfig;
 
   systemCallsList = [ "@cpu-emulation" "@debug" "@keyring" "@ipc" "@mount" "@obsolete" "@privileged" "@setuid" ];
@@ -141,8 +140,44 @@ let
     })
   ) cfg.sidekiqProcesses;
 
+  streamingUnits = builtins.listToAttrs
+      (map (i: {
+        name = "mastodon-streaming-${toString i}";
+        value = {
+          after = [ "network.target" "mastodon-init-dirs.service" ]
+            ++ lib.optional databaseActuallyCreateLocally "postgresql.service"
+            ++ lib.optional cfg.automaticMigrations "mastodon-init-db.service";
+          requires = [ "mastodon-init-dirs.service" ]
+            ++ lib.optional databaseActuallyCreateLocally "postgresql.service"
+            ++ lib.optional cfg.automaticMigrations "mastodon-init-db.service";
+          wantedBy = [ "mastodon.target" "mastodon-streaming.target" ];
+          description = "Mastodon streaming ${toString i}";
+          environment = env // { SOCKET = "/run/mastodon-streaming/streaming-${toString i}.socket"; };
+          serviceConfig = {
+            ExecStart = "${cfg.package}/run-streaming.sh";
+            Restart = "always";
+            RestartSec = 20;
+            EnvironmentFile = [ "/var/lib/mastodon/.secrets_env" ] ++ cfg.extraEnvFiles;
+            WorkingDirectory = cfg.package;
+            # Runtime directory and mode
+            RuntimeDirectory = "mastodon-streaming";
+            RuntimeDirectoryMode = "0750";
+            # System Call Filtering
+            SystemCallFilter = [ ("~" + lib.concatStringsSep " " (systemCallsList ++ [ "@memlock" "@resources" ])) "pipe" "pipe2" ];
+          } // cfgService;
+        };
+      })
+      (lib.range 1 cfg.streamingProcesses));
+
 in {
 
+  imports = [
+    (lib.mkRemovedOptionModule
+      [ "services" "mastodon" "streamingPort" ]
+      "Mastodon currently doesn't support streaming via TCP ports. Please open a PR if you need this."
+    )
+  ];
+
   options = {
     services.mastodon = {
       enable = lib.mkEnableOption (lib.mdDoc "Mastodon, a federated social network server");
@@ -191,18 +226,13 @@ in {
         default = "mastodon";
       };
 
-      streamingPort = lib.mkOption {
-        description = lib.mdDoc "TCP port used by the mastodon-streaming service.";
-        type = lib.types.port;
-        default = 55000;
-      };
       streamingProcesses = lib.mkOption {
         description = lib.mdDoc ''
-          Processes used by the mastodon-streaming service.
-          Defaults to the number of CPU cores minus one.
+          Number of processes used by the mastodon-streaming service.
+          Recommended is the amount of your CPU cores minus one.
         '';
-        type = lib.types.nullOr lib.types.int;
-        default = null;
+        type = lib.types.ints.positive;
+        example = 3;
       };
 
       webPort = lib.mkOption {
@@ -485,6 +515,31 @@ in {
           type = lib.types.port;
           default = 9200;
         };
+
+        preset = lib.mkOption {
+          description = lib.mdDoc ''
+            It controls the ElasticSearch indices configuration (number of shards and replica).
+          '';
+          type = lib.types.enum [ "single_node_cluster" "small_cluster" "large_cluster" ];
+          default = "single_node_cluster";
+          example = "large_cluster";
+        };
+
+        user = lib.mkOption {
+          description = lib.mdDoc "Used for optionally authenticating with Elasticsearch.";
+          type = lib.types.nullOr lib.types.str;
+          default = null;
+          example = "elasticsearch-mastodon";
+        };
+
+        passwordFile = lib.mkOption {
+          description = lib.mdDoc ''
+            Path to file containing password for optionally authenticating with Elasticsearch.
+          '';
+          type = lib.types.nullOr lib.types.path;
+          default = null;
+          example = "/var/lib/mastodon/secrets/elasticsearch-password";
+        };
       };
 
       package = lib.mkOption {
@@ -557,7 +612,7 @@ in {
   config = lib.mkIf cfg.enable (lib.mkMerge [{
     assertions = [
       {
-        assertion = databaseActuallyCreateLocally -> (cfg.user == cfg.database.user);
+        assertion = databaseActuallyCreateLocally -> (cfg.user == cfg.database.user && cfg.database.user == cfg.database.name);
         message = ''
           For local automatic database provisioning (services.mastodon.database.createLocally == true) with peer
             authentication (services.mastodon.database.host == "/run/postgresql") to work services.mastodon.user
@@ -603,6 +658,12 @@ in {
       after = [ "network.target" ];
     };
 
+    systemd.targets.mastodon-streaming = {
+      description = "Target for all Mastodon streaming services";
+      wantedBy = [ "multi-user.target" "mastodon.target" ];
+      after = [ "network.target" ];
+    };
+
     systemd.services.mastodon-init-dirs = {
       script = ''
         umask 077
@@ -631,6 +692,8 @@ in {
         DB_PASS="$(cat ${cfg.database.passwordFile})"
       '' + lib.optionalString cfg.smtp.authenticate ''
         SMTP_PASSWORD="$(cat ${cfg.smtp.passwordFile})"
+      '' + lib.optionalString (cfg.elasticsearch.passwordFile != null) ''
+        ES_PASS="$(cat ${cfg.elasticsearch.passwordFile})"
       '' + ''
         EOF
       '';
@@ -688,33 +751,6 @@ in {
         ++ lib.optional databaseActuallyCreateLocally "postgresql.service";
     };
 
-    systemd.services.mastodon-streaming = {
-      after = [ "network.target" "mastodon-init-dirs.service" ]
-        ++ lib.optional databaseActuallyCreateLocally "postgresql.service"
-        ++ lib.optional cfg.automaticMigrations "mastodon-init-db.service";
-      requires = [ "mastodon-init-dirs.service" ]
-        ++ lib.optional databaseActuallyCreateLocally "postgresql.service"
-        ++ lib.optional cfg.automaticMigrations "mastodon-init-db.service";
-      wantedBy = [ "mastodon.target" ];
-      description = "Mastodon streaming";
-      environment = env // (if cfg.enableUnixSocket
-        then { SOCKET = "/run/mastodon-streaming/streaming.socket"; }
-        else { PORT = toString(cfg.streamingPort); }
-      );
-      serviceConfig = {
-        ExecStart = "${cfg.package}/run-streaming.sh";
-        Restart = "always";
-        RestartSec = 20;
-        EnvironmentFile = [ "/var/lib/mastodon/.secrets_env" ] ++ cfg.extraEnvFiles;
-        WorkingDirectory = cfg.package;
-        # Runtime directory and mode
-        RuntimeDirectory = "mastodon-streaming";
-        RuntimeDirectoryMode = "0750";
-        # System Call Filtering
-        SystemCallFilter = [ ("~" + lib.concatStringsSep " " (systemCallsList ++ [ "@memlock" "@resources" ])) "pipe" "pipe2" ];
-      } // cfgService;
-    };
-
     systemd.services.mastodon-web = {
       after = [ "network.target" "mastodon-init-dirs.service" ]
         ++ lib.optional databaseActuallyCreateLocally "postgresql.service"
@@ -780,10 +816,20 @@ in {
         };
 
         locations."/api/v1/streaming/" = {
-          proxyPass = (if cfg.enableUnixSocket then "http://unix:/run/mastodon-streaming/streaming.socket" else "http://127.0.0.1:${toString(cfg.streamingPort)}/");
+          proxyPass = "http://mastodon-streaming";
           proxyWebsockets = true;
         };
       };
+      upstreams.mastodon-streaming = {
+        extraConfig = ''
+          least_conn;
+        '';
+        servers = builtins.listToAttrs
+          (map (i: {
+            name = "unix:/run/mastodon-streaming/streaming-${toString i}.socket";
+            value = { };
+          }) (lib.range 1 cfg.streamingProcesses));
+      };
     };
 
     services.postfix = lib.mkIf (cfg.smtp.createLocally && cfg.smtp.host == "127.0.0.1") {
@@ -799,8 +845,8 @@ in {
       enable = true;
       ensureUsers = [
         {
-          name = cfg.database.user;
-          ensurePermissions."DATABASE ${cfg.database.name}" = "ALL PRIVILEGES";
+          name = cfg.database.name;
+          ensureDBOwnership = true;
         }
       ];
       ensureDatabases = [ cfg.database.name ];
@@ -819,7 +865,7 @@ in {
 
     users.groups.${cfg.group}.members = lib.optional cfg.configureNginx config.services.nginx.user;
   }
-  { systemd.services = sidekiqUnits; }
+  { systemd.services = lib.mkMerge [ sidekiqUnits streamingUnits ]; }
   ]);
 
   meta.maintainers = with lib.maintainers; [ happy-river erictapen ];
diff --git a/nixos/modules/services/web-apps/mediawiki.nix b/nixos/modules/services/web-apps/mediawiki.nix
index 8b494b7c12083..ce7bcd94b3f01 100644
--- a/nixos/modules/services/web-apps/mediawiki.nix
+++ b/nixos/modules/services/web-apps/mediawiki.nix
@@ -454,7 +454,7 @@ in
       { assertion = cfg.database.createLocally -> (cfg.database.type == "mysql" || cfg.database.type == "postgres");
         message = "services.mediawiki.createLocally is currently only supported for database type 'mysql' and 'postgres'";
       }
-      { assertion = cfg.database.createLocally -> cfg.database.user == user;
+      { assertion = cfg.database.createLocally -> cfg.database.user == user && cfg.database.name == cfg.database.user;
         message = "services.mediawiki.database.user must be set to ${user} if services.mediawiki.database.createLocally is set true";
       }
       { assertion = cfg.database.createLocally -> cfg.database.socket != null;
@@ -486,7 +486,7 @@ in
       ensureDatabases = [ cfg.database.name ];
       ensureUsers = [{
         name = cfg.database.user;
-        ensurePermissions = { "DATABASE \"${cfg.database.name}\"" = "ALL PRIVILEGES"; };
+        ensureDBOwnership = true;
       }];
     };
 
diff --git a/nixos/modules/services/web-apps/miniflux.nix b/nixos/modules/services/web-apps/miniflux.nix
index 3374c746ad3dd..5c8c93c13c434 100644
--- a/nixos/modules/services/web-apps/miniflux.nix
+++ b/nixos/modules/services/web-apps/miniflux.nix
@@ -6,13 +6,10 @@ let
 
   defaultAddress = "localhost:8080";
 
-  dbUser = "miniflux";
-  dbName = "miniflux";
-
   pgbin = "${config.services.postgresql.package}/bin";
   preStart = pkgs.writeScript "miniflux-pre-start" ''
     #!${pkgs.runtimeShell}
-    ${pgbin}/psql "${dbName}" -c "CREATE EXTENSION IF NOT EXISTS hstore"
+    ${pgbin}/psql "miniflux" -c "CREATE EXTENSION IF NOT EXISTS hstore"
   '';
 in
 
@@ -62,7 +59,7 @@ in
 
     services.miniflux.config =  {
       LISTEN_ADDR = mkDefault defaultAddress;
-      DATABASE_URL = "user=${dbUser} host=/run/postgresql dbname=${dbName}";
+      DATABASE_URL = "user=miniflux host=/run/postgresql dbname=miniflux";
       RUN_MIGRATIONS = "1";
       CREATE_ADMIN = "1";
     };
@@ -70,12 +67,10 @@ in
     services.postgresql = {
       enable = true;
       ensureUsers = [ {
-        name = dbUser;
-        ensurePermissions = {
-          "DATABASE ${dbName}" = "ALL PRIVILEGES";
-        };
+        name = "miniflux";
+        ensureDBOwnership = true;
       } ];
-      ensureDatabases = [ dbName ];
+      ensureDatabases = [ "miniflux" ];
     };
 
     systemd.services.miniflux-dbsetup = {
@@ -97,7 +92,7 @@ in
 
       serviceConfig = {
         ExecStart = "${cfg.package}/bin/miniflux";
-        User = dbUser;
+        User = "miniflux";
         DynamicUser = true;
         RuntimeDirectory = "miniflux";
         RuntimeDirectoryMode = "0700";
diff --git a/nixos/modules/services/web-apps/mobilizon.nix b/nixos/modules/services/web-apps/mobilizon.nix
index 343c5cead2b15..bb4319b51a2fa 100644
--- a/nixos/modules/services/web-apps/mobilizon.nix
+++ b/nixos/modules/services/web-apps/mobilizon.nix
@@ -347,12 +347,18 @@ in
 
       # Taken from here:
       # https://framagit.org/framasoft/mobilizon/-/blob/1.1.0/priv/templates/setup_db.eex
+      # TODO(to maintainers of mobilizon): the owner database alteration is necessary
+      # as PostgreSQL 15 changed their behaviors w.r.t. to privileges.
+      # See https://github.com/NixOS/nixpkgs/issues/216989 to get rid
+      # of that workaround.
       script =
         ''
           psql "${repoSettings.database}" -c "\
             CREATE EXTENSION IF NOT EXISTS postgis; \
             CREATE EXTENSION IF NOT EXISTS pg_trgm; \
             CREATE EXTENSION IF NOT EXISTS unaccent;"
+          psql -tAc 'ALTER DATABASE "${repoSettings.database}" OWNER TO "${dbUser}";'
+
         '';
 
       serviceConfig = {
@@ -372,9 +378,10 @@ in
       ensureUsers = [
         {
           name = dbUser;
-          ensurePermissions = {
-            "DATABASE \"${repoSettings.database}\"" = "ALL PRIVILEGES";
-          };
+          # Given that `dbUser` is potentially arbitrarily custom, we will perform
+          # manual fixups in mobilizon-postgres.
+          # TODO(to maintainers of mobilizon): Feel free to simplify your setup by using `ensureDBOwnership`.
+          ensureDBOwnership = false;
         }
       ];
       extraPlugins = with postgresql.pkgs; [ postgis ];
diff --git a/nixos/modules/services/web-apps/moodle.nix b/nixos/modules/services/web-apps/moodle.nix
index b617e9a593795..04ae6bd7f1754 100644
--- a/nixos/modules/services/web-apps/moodle.nix
+++ b/nixos/modules/services/web-apps/moodle.nix
@@ -194,7 +194,7 @@ in
   config = mkIf cfg.enable {
 
     assertions = [
-      { assertion = cfg.database.createLocally -> cfg.database.user == user;
+      { assertion = cfg.database.createLocally -> cfg.database.user == user && cfg.database.user == cfg.database.name;
         message = "services.moodle.database.user must be set to ${user} if services.moodle.database.createLocally is set true";
       }
       { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
@@ -220,7 +220,7 @@ in
       ensureDatabases = [ cfg.database.name ];
       ensureUsers = [
         { name = cfg.database.user;
-          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/web-apps/netbox.nix b/nixos/modules/services/web-apps/netbox.nix
index 8ba1852848e5b..3b9434e3d3456 100644
--- a/nixos/modules/services/web-apps/netbox.nix
+++ b/nixos/modules/services/web-apps/netbox.nix
@@ -257,9 +257,7 @@ in {
       ensureUsers = [
         {
           name = "netbox";
-          ensurePermissions = {
-            "DATABASE netbox" = "ALL PRIVILEGES";
-          };
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/web-apps/nextcloud.md b/nixos/modules/services/web-apps/nextcloud.md
index a25bed30e47fd..ecc7f380592ae 100644
--- a/nixos/modules/services/web-apps/nextcloud.md
+++ b/nixos/modules/services/web-apps/nextcloud.md
@@ -49,7 +49,7 @@ used by the imperative installer and all values are written to an additional fil
 to ensure that changes can be applied by changing the module's options.
 
 In case the application serves multiple domains (those are checked with
-[`$_SERVER['HTTP_HOST']`](http://php.net/manual/en/reserved.variables.server.php))
+[`$_SERVER['HTTP_HOST']`](https://www.php.net/manual/en/reserved.variables.server.php))
 it's needed to add them to
 [`services.nextcloud.config.extraTrustedDomains`](#opt-services.nextcloud.config.extraTrustedDomains).
 
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
index f9713cac47e94..f1ac3770d403c 100644
--- a/nixos/modules/services/web-apps/nextcloud.nix
+++ b/nixos/modules/services/web-apps/nextcloud.nix
@@ -1042,7 +1042,7 @@ in {
         ensureDatabases = [ cfg.config.dbname ];
         ensureUsers = [{
           name = cfg.config.dbuser;
-          ensurePermissions = { "DATABASE ${cfg.config.dbname}" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }];
       };
 
diff --git a/nixos/modules/services/web-apps/onlyoffice.nix b/nixos/modules/services/web-apps/onlyoffice.nix
index 3494f2fa21f09..f958566b91f09 100644
--- a/nixos/modules/services/web-apps/onlyoffice.nix
+++ b/nixos/modules/services/web-apps/onlyoffice.nix
@@ -198,7 +198,7 @@ in
         ensureDatabases = [ "onlyoffice" ];
         ensureUsers = [{
           name = "onlyoffice";
-          ensurePermissions = { "DATABASE \"onlyoffice\"" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }];
       };
     };
diff --git a/nixos/modules/services/web-apps/outline.nix b/nixos/modules/services/web-apps/outline.nix
index 0e3bd07c1fc14..d97b45d624187 100644
--- a/nixos/modules/services/web-apps/outline.nix
+++ b/nixos/modules/services/web-apps/outline.nix
@@ -581,7 +581,7 @@ in
       enable = true;
       ensureUsers = [{
         name = "outline";
-        ensurePermissions."DATABASE outline" = "ALL PRIVILEGES";
+        ensureDBOwnership = true;
       }];
       ensureDatabases = [ "outline" ];
     };
diff --git a/nixos/modules/services/web-apps/peering-manager.nix b/nixos/modules/services/web-apps/peering-manager.nix
index 7012df6dffbfd..d6f6077268d46 100644
--- a/nixos/modules/services/web-apps/peering-manager.nix
+++ b/nixos/modules/services/web-apps/peering-manager.nix
@@ -186,9 +186,7 @@ in {
       ensureUsers = [
         {
           name = "peering-manager";
-          ensurePermissions = {
-            "DATABASE \"peering-manager\"" = "ALL PRIVILEGES";
-          };
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/web-apps/peertube.nix b/nixos/modules/services/web-apps/peertube.nix
index a22467611410b..39c02c81c4231 100644
--- a/nixos/modules/services/web-apps/peertube.nix
+++ b/nixos/modules/services/web-apps/peertube.nix
@@ -100,19 +100,19 @@ in {
     listenHttp = lib.mkOption {
       type = lib.types.port;
       default = 9000;
-      description = lib.mdDoc "listen port for HTTP server.";
+      description = lib.mdDoc "The port that the local PeerTube web server will listen on.";
     };
 
     listenWeb = lib.mkOption {
       type = lib.types.port;
       default = 9000;
-      description = lib.mdDoc "listen port for WEB server.";
+      description = lib.mdDoc "The public-facing port that PeerTube will be accessible at (likely 80 or 443 if running behind a reverse proxy). Clients will try to access PeerTube at this port.";
     };
 
     enableWebHttps = lib.mkOption {
       type = lib.types.bool;
       default = false;
-      description = lib.mdDoc "Enable or disable HTTPS protocol.";
+      description = lib.mdDoc "Whether clients will access your PeerTube instance with HTTPS. Does NOT configure the PeerTube webserver itself to listen for incoming HTTPS connections.";
     };
 
     dataDirs = lib.mkOption {
@@ -279,7 +279,7 @@ in {
       type = lib.types.package;
       default = pkgs.peertube;
       defaultText = lib.literalExpression "pkgs.peertube";
-      description = lib.mdDoc "Peertube package to use.";
+      description = lib.mdDoc "PeerTube package to use.";
     };
   };
 
diff --git a/nixos/modules/services/web-apps/pixelfed.nix b/nixos/modules/services/web-apps/pixelfed.nix
index 159fb52476aab..b0a25dcce9efe 100644
--- a/nixos/modules/services/web-apps/pixelfed.nix
+++ b/nixos/modules/services/web-apps/pixelfed.nix
@@ -271,7 +271,6 @@ in {
         ensureDatabases = [ cfg.database.name ];
         ensureUsers = [{
           name = user;
-          ensurePermissions = { };
         }];
       };
 
diff --git a/nixos/modules/services/web-apps/plantuml-server.nix b/nixos/modules/services/web-apps/plantuml-server.nix
index 5ebee48c3e0b5..1fa69814c6c9f 100644
--- a/nixos/modules/services/web-apps/plantuml-server.nix
+++ b/nixos/modules/services/web-apps/plantuml-server.nix
@@ -1,123 +1,110 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
+  inherit (lib)
+    literalExpression
+    mdDoc
+    mkEnableOption
+    mkIf
+    mkOption
+    mkPackageOptionMD
+    mkRemovedOptionModule
+    types
+    ;
 
   cfg = config.services.plantuml-server;
 
 in
 
 {
+  imports = [
+    (mkRemovedOptionModule [ "services" "plantuml-server" "allowPlantumlInclude" ] "This option has been removed from PlantUML.")
+  ];
+
   options = {
     services.plantuml-server = {
-      enable = mkEnableOption (lib.mdDoc "PlantUML server");
+      enable = mkEnableOption (mdDoc "PlantUML server");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.plantuml-server;
-        defaultText = literalExpression "pkgs.plantuml-server";
-        description = lib.mdDoc "PlantUML server package to use";
-      };
+      package = mkPackageOptionMD pkgs "plantuml-server" { };
 
       packages = {
-        jdk = mkOption {
-          type = types.package;
-          default = pkgs.jdk;
-          defaultText = literalExpression "pkgs.jdk";
-          description = lib.mdDoc "JDK package to use for the server";
-        };
-        jetty = mkOption {
-          type = types.package;
-          default = pkgs.jetty;
-          defaultText = literalExpression "pkgs.jetty";
-          description = lib.mdDoc "Jetty package to use for the server";
+        jdk = mkPackageOptionMD pkgs "jdk" { };
+        jetty = mkPackageOptionMD pkgs "jetty" {
+          default = "jetty_11";
+          extraDescription = ''
+            At the time of writing (v1.2023.12), PlantUML Server does not support
+            Jetty versions higher than 12.x.
+
+            Jetty 12.x has introduced major breaking changes, see
+            <https://github.com/jetty/jetty.project/releases/tag/jetty-12.0.0> and
+            <https://eclipse.dev/jetty/documentation/jetty-12/programming-guide/index.html#pg-migration-11-to-12>
+          '';
         };
       };
 
       user = mkOption {
         type = types.str;
         default = "plantuml";
-        description = lib.mdDoc "User which runs PlantUML server.";
+        description = mdDoc "User which runs PlantUML server.";
       };
 
       group = mkOption {
         type = types.str;
         default = "plantuml";
-        description = lib.mdDoc "Group which runs PlantUML server.";
+        description = mdDoc "Group which runs PlantUML server.";
       };
 
       home = mkOption {
-        type = types.str;
+        type = types.path;
         default = "/var/lib/plantuml";
-        description = lib.mdDoc "Home directory of the PlantUML server instance.";
+        description = mdDoc "Home directory of the PlantUML server instance.";
       };
 
       listenHost = mkOption {
         type = types.str;
         default = "127.0.0.1";
-        description = lib.mdDoc "Host to listen on.";
+        description = mdDoc "Host to listen on.";
       };
 
       listenPort = mkOption {
         type = types.int;
         default = 8080;
-        description = lib.mdDoc "Port to listen on.";
+        description = mdDoc "Port to listen on.";
       };
 
       plantumlLimitSize = mkOption {
         type = types.int;
         default = 4096;
-        description = lib.mdDoc "Limits image width and height.";
+        description = mdDoc "Limits image width and height.";
       };
 
-      graphvizPackage = mkOption {
-        type = types.package;
-        default = pkgs.graphviz;
-        defaultText = literalExpression "pkgs.graphviz";
-        description = lib.mdDoc "Package containing the dot executable.";
-      };
+      graphvizPackage = mkPackageOptionMD pkgs "graphviz" { };
 
       plantumlStats = mkOption {
         type = types.bool;
         default = false;
-        description = lib.mdDoc "Set it to on to enable statistics report (https://plantuml.com/statistics-report).";
+        description = mdDoc "Set it to on to enable statistics report (https://plantuml.com/statistics-report).";
       };
 
       httpAuthorization = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = lib.mdDoc "When calling the proxy endpoint, the value of HTTP_AUTHORIZATION will be used to set the HTTP Authorization header.";
-      };
-
-      allowPlantumlInclude = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc "Enables !include processing which can read files from the server into diagrams. Files are read relative to the current working directory.";
+        description = mdDoc "When calling the proxy endpoint, the value of HTTP_AUTHORIZATION will be used to set the HTTP Authorization header.";
       };
     };
   };
 
   config = mkIf cfg.enable {
-    users.users.${cfg.user} = {
-      isSystemUser = true;
-      group = cfg.group;
-      home = cfg.home;
-      createHome = true;
-    };
-
-    users.groups.${cfg.group} = {};
-
     systemd.services.plantuml-server = {
       description = "PlantUML server";
       wantedBy = [ "multi-user.target" ];
       path = [ cfg.home ];
+
       environment = {
         PLANTUML_LIMIT_SIZE = builtins.toString cfg.plantumlLimitSize;
         GRAPHVIZ_DOT = "${cfg.graphvizPackage}/bin/dot";
         PLANTUML_STATS = if cfg.plantumlStats then "on" else "off";
         HTTP_AUTHORIZATION = cfg.httpAuthorization;
-        ALLOW_PLANTUML_INCLUDE = if cfg.allowPlantumlInclude then "true" else "false";
       };
       script = ''
       ${cfg.packages.jdk}/bin/java \
@@ -128,13 +115,40 @@ in
           jetty.http.host=${cfg.listenHost} \
           jetty.http.port=${builtins.toString cfg.listenPort}
       '';
+
       serviceConfig = {
         User = cfg.user;
         Group = cfg.group;
+        StateDirectory = mkIf (cfg.home == "/var/lib/plantuml") "plantuml";
+        StateDirectoryMode = mkIf (cfg.home == "/var/lib/plantuml") "0750";
+
+        # Hardening
+        AmbientCapabilities = [ "" ];
+        CapabilityBoundingSet = [ "" ];
+        DynamicUser = true;
+        LockPersonality = true;
+        NoNewPrivileges = true;
+        PrivateDevices = true;
+        PrivateNetwork = false;
         PrivateTmp = true;
+        PrivateUsers = true;
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectSystem = "strict";
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [ "@system-service" ];
       };
     };
   };
 
-  meta.maintainers = with lib.maintainers; [ truh ];
+  meta.maintainers = with lib.maintainers; [ truh anthonyroussel ];
 }
diff --git a/nixos/modules/services/web-apps/plausible.nix b/nixos/modules/services/web-apps/plausible.nix
index 576b54a7edf24..300a0f892ef77 100644
--- a/nixos/modules/services/web-apps/plausible.nix
+++ b/nixos/modules/services/web-apps/plausible.nix
@@ -11,13 +11,6 @@ in {
 
     package = mkPackageOptionMD pkgs "plausible" { };
 
-    releaseCookiePath = mkOption {
-      type = with types; either str path;
-      description = lib.mdDoc ''
-        The path to the file with release cookie. (used for remote connection to the running node).
-      '';
-    };
-
     adminUser = {
       name = mkOption {
         default = "admin";
@@ -92,6 +85,13 @@ in {
           framework docs](https://hexdocs.pm/phoenix/Mix.Tasks.Phx.Gen.Secret.html#content).
         '';
       };
+      listenAddress = mkOption {
+        default = "127.0.0.1";
+        type = types.str;
+        description = lib.mdDoc ''
+          The IP address on which the server is listening.
+        '';
+      };
       port = mkOption {
         default = 8000;
         type = types.port;
@@ -162,6 +162,10 @@ in {
     };
   };
 
+  imports = [
+    (mkRemovedOptionModule [ "services" "plausible" "releaseCookiePath" ] "Plausible uses no distributed Erlang features, so this option is no longer necessary and was removed")
+  ];
+
   config = mkIf cfg.enable {
     assertions = [
       { assertion = cfg.adminUser.activate -> cfg.database.postgres.setup;
@@ -180,8 +184,6 @@ in {
       enable = true;
     };
 
-    services.epmd.enable = true;
-
     environment.systemPackages = [ cfg.package ];
 
     systemd.services = mkMerge [
@@ -209,6 +211,32 @@ in {
             # Configuration options from
             # https://plausible.io/docs/self-hosting-configuration
             PORT = toString cfg.server.port;
+            LISTEN_IP = cfg.server.listenAddress;
+
+            # Note [plausible-needs-no-erlang-distributed-features]:
+            # Plausible does not use, and does not plan to use, any of
+            # Erlang's distributed features, see:
+            #     https://github.com/plausible/analytics/pull/1190#issuecomment-1018820934
+            # Thus, disable distribution for improved simplicity and security:
+            #
+            # When distribution is enabled,
+            # Elixir spwans the Erlang VM, which will listen by default on all
+            # interfaces for messages between Erlang nodes (capable of
+            # remote code execution); it can be protected by a cookie; see
+            # https://erlang.org/doc/reference_manual/distributed.html#security).
+            #
+            # It would be possible to restrict the interface to one of our choice
+            # (e.g. localhost or a VPN IP) similar to how we do it with `listenAddress`
+            # for the Plausible web server; if distribution is ever needed in the future,
+            # https://github.com/NixOS/nixpkgs/pull/130297 shows how to do it.
+            #
+            # But since Plausible does not use this feature in any way,
+            # we just disable it.
+            RELEASE_DISTRIBUTION = "none";
+            # Additional safeguard, in case `RELEASE_DISTRIBUTION=none` ever
+            # stops disabling the start of EPMD.
+            ERL_EPMD_ADDRESS = "127.0.0.1";
+
             DISABLE_REGISTRATION = if isBool cfg.server.disableRegistration then boolToString cfg.server.disableRegistration else cfg.server.disableRegistration;
 
             RELEASE_TMP = "/var/lib/plausible/tmp";
@@ -238,7 +266,10 @@ in {
           path = [ cfg.package ]
             ++ optional cfg.database.postgres.setup config.services.postgresql.package;
           script = ''
-            export RELEASE_COOKIE="$(< $CREDENTIALS_DIRECTORY/RELEASE_COOKIE )"
+            # Elixir does not start up if `RELEASE_COOKIE` is not set,
+            # even though we set `RELEASE_DISTRIBUTION=none` so the cookie should be unused.
+            # Thus, make a random one, which should then be ignored.
+            export RELEASE_COOKIE=$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 20)
             export ADMIN_USER_PWD="$(< $CREDENTIALS_DIRECTORY/ADMIN_USER_PWD )"
             export SECRET_KEY_BASE="$(< $CREDENTIALS_DIRECTORY/SECRET_KEY_BASE )"
 
@@ -265,7 +296,6 @@ in {
             LoadCredential = [
               "ADMIN_USER_PWD:${cfg.adminUser.passwordFile}"
               "SECRET_KEY_BASE:${cfg.server.secretKeybaseFile}"
-              "RELEASE_COOKIE:${cfg.releaseCookiePath}"
             ] ++ lib.optionals (cfg.mail.smtp.passwordFile != null) [ "SMTP_USER_PWD:${cfg.mail.smtp.passwordFile}"];
           };
         };
diff --git a/nixos/modules/services/web-apps/tt-rss.nix b/nixos/modules/services/web-apps/tt-rss.nix
index 592ab253f7daf..a8fb37d2c5ecd 100644
--- a/nixos/modules/services/web-apps/tt-rss.nix
+++ b/nixos/modules/services/web-apps/tt-rss.nix
@@ -430,7 +430,7 @@ let
           background processes while not running tt-rss, this method is generally
           viable to keep your feeds up to date.
           Still, there are more robust (and recommended) updating methods
-          available, you can read about them here: http://tt-rss.org/wiki/UpdatingFeeds
+          available, you can read about them here: <https://tt-rss.org/wiki/UpdatingFeeds>
         '';
       };
 
@@ -529,6 +529,15 @@ let
         assertion = cfg.database.password != null -> cfg.database.passwordFile == null;
         message = "Cannot set both password and passwordFile";
       }
+      {
+        assertion = cfg.database.createLocally -> cfg.database.name == cfg.user && cfg.database.user == cfg.user;
+        message = ''
+          When creating a database via NixOS, the db user and db name must be equal!
+          If you already have an existing DB+user and this assertion is new, you can safely set
+          `services.tt-rss.database.createLocally` to `false` because removal of `ensureUsers`
+          and `ensureDatabases` doesn't have any effect.
+        '';
+      }
     ];
 
     services.phpfpm.pools = mkIf (cfg.pool == "${poolName}") {
@@ -632,8 +641,8 @@ let
       enable = mkDefault true;
       ensureDatabases = [ cfg.database.name ];
       ensureUsers = [
-        { name = cfg.user;
-          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+        { name = cfg.database.user;
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/web-servers/caddy/default.nix b/nixos/modules/services/web-servers/caddy/default.nix
index ce74e243a1818..cc89553fbb756 100644
--- a/nixos/modules/services/web-servers/caddy/default.nix
+++ b/nixos/modules/services/web-servers/caddy/default.nix
@@ -376,7 +376,9 @@ in
         ReadWriteDirectories = cfg.dataDir;
         StateDirectory = mkIf (cfg.dataDir == "/var/lib/caddy") [ "caddy" ];
         LogsDirectory = mkIf (cfg.logDir == "/var/log/caddy") [ "caddy" ];
-        Restart = "on-abnormal";
+        Restart = "on-failure";
+        RestartPreventExitStatus = 1;
+        RestartSec = "5s";
 
         # TODO: attempt to upstream these options
         NoNewPrivileges = true;
diff --git a/nixos/modules/services/web-servers/hydron.nix b/nixos/modules/services/web-servers/hydron.nix
index 4434965b217a0..9d30fdc0caab0 100644
--- a/nixos/modules/services/web-servers/hydron.nix
+++ b/nixos/modules/services/web-servers/hydron.nix
@@ -93,7 +93,7 @@ in with lib; {
       ensureDatabases = [ "hydron" ];
       ensureUsers = [
         { name = "hydron";
-          ensurePermissions = { "DATABASE hydron" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/web-servers/jboss/builder.sh b/nixos/modules/services/web-servers/jboss/builder.sh
index ac573089cd5a7..8c49b87db0604 100644
--- a/nixos/modules/services/web-servers/jboss/builder.sh
+++ b/nixos/modules/services/web-servers/jboss/builder.sh
@@ -1,6 +1,6 @@
 set -e
 
-if [ -e .attrs.sh ]; then source .attrs.sh; fi
+if [ -e "$NIX_ATTRS_SH_FILE" ]; then . "$NIX_ATTRS_SH_FILE"; elif [ -f .attrs.sh ]; then . .attrs.sh; fi
 source $stdenv/setup
 
 mkdir -p $out/bin
diff --git a/nixos/modules/services/web-servers/lighttpd/default.nix b/nixos/modules/services/web-servers/lighttpd/default.nix
index 729a633a36cc2..eaa113c0d52ce 100644
--- a/nixos/modules/services/web-servers/lighttpd/default.nix
+++ b/nixos/modules/services/web-servers/lighttpd/default.nix
@@ -10,7 +10,7 @@ let
 
   # List of known lighttpd modules, ordered by how the lighttpd documentation
   # recommends them being imported:
-  # http://redmine.lighttpd.net/projects/1/wiki/Server_modulesDetails
+  # https://redmine.lighttpd.net/projects/1/wiki/Server_modulesDetails
   #
   # Some modules are always imported and should not appear in the config:
   # disallowedModules = [ "mod_indexfile" "mod_dirlisting" "mod_staticfile" ];
@@ -84,8 +84,8 @@ let
       # server.modules += () entries in each sub-service extraConfig snippet,
       # read this:
       #
-      #   http://redmine.lighttpd.net/projects/1/wiki/Server_modulesDetails
-      #   http://redmine.lighttpd.net/issues/2337
+      #   https://redmine.lighttpd.net/projects/1/wiki/Server_modulesDetails
+      #   https://redmine.lighttpd.net/issues/2337
       #
       # Basically, lighttpd doesn't want to load (or even silently ignore) a
       # module for a second time, and there is no way to check if a module has
diff --git a/nixos/modules/services/web-servers/mighttpd2.nix b/nixos/modules/services/web-servers/mighttpd2.nix
index 2d887af87c79b..bdd6d8b62aa36 100644
--- a/nixos/modules/services/web-servers/mighttpd2.nix
+++ b/nixos/modules/services/web-servers/mighttpd2.nix
@@ -44,7 +44,7 @@ in {
       type = types.lines;
       description = lib.mdDoc ''
         Verbatim config file to use
-        (see http://www.mew.org/~kazu/proj/mighttpd/en/config.html)
+        (see https://kazu-yamamoto.github.io/mighttpd2/config.html)
       '';
     };
 
@@ -78,7 +78,7 @@ in {
       type = types.lines;
       description = lib.mdDoc ''
         Verbatim routing file to use
-        (see http://www.mew.org/~kazu/proj/mighttpd/en/config.html)
+        (see https://kazu-yamamoto.github.io/mighttpd2/config.html)
       '';
     };
 
diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix
index f2e8585a93654..cf70dc3259456 100644
--- a/nixos/modules/services/web-servers/nginx/default.nix
+++ b/nixos/modules/services/web-servers/nginx/default.nix
@@ -955,7 +955,7 @@ in
         default = {};
         description = lib.mdDoc ''
           Configure a proxy cache path entry.
-          See <http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_path> for documentation.
+          See <https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_path> for documentation.
         '';
       };
 
diff --git a/nixos/modules/services/web-servers/tomcat.nix b/nixos/modules/services/web-servers/tomcat.nix
index 4d2c36287be69..30d6b99fcfda1 100644
--- a/nixos/modules/services/web-servers/tomcat.nix
+++ b/nixos/modules/services/web-servers/tomcat.nix
@@ -1,7 +1,5 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
 
   cfg = config.services.tomcat;
@@ -9,30 +7,20 @@ let
 in
 
 {
-
   meta = {
-    maintainers = with maintainers; [ danbst ];
+    maintainers = with lib.maintainers; [ danbst ];
   };
 
   ###### interface
 
   options = {
-
     services.tomcat = {
-      enable = mkEnableOption (lib.mdDoc "Apache Tomcat");
+      enable = lib.mkEnableOption (lib.mdDoc "Apache Tomcat");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.tomcat9;
-        defaultText = literalExpression "pkgs.tomcat9";
-        example = lib.literalExpression "pkgs.tomcat9";
-        description = lib.mdDoc ''
-          Which tomcat package to use.
-        '';
-      };
+      package = lib.mkPackageOptionMD pkgs "tomcat9" { };
 
-      purifyOnStart = mkOption {
-        type = types.bool;
+      purifyOnStart = lib.mkOption {
+        type = lib.types.bool;
         default = false;
         description = lib.mdDoc ''
           On startup, the `baseDir` directory is populated with various files,
@@ -43,7 +31,7 @@ in
         '';
       };
 
-      baseDir = mkOption {
+      baseDir = lib.mkOption {
         type = lib.types.path;
         default = "/var/tomcat";
         description = lib.mdDoc ''
@@ -53,64 +41,64 @@ in
         '';
       };
 
-      logDirs = mkOption {
-        default = [];
-        type = types.listOf types.path;
+      logDirs = lib.mkOption {
+        default = [ ];
+        type = lib.types.listOf lib.types.path;
         description = lib.mdDoc "Directories to create in baseDir/logs/";
       };
 
-      extraConfigFiles = mkOption {
-        default = [];
-        type = types.listOf types.path;
+      extraConfigFiles = lib.mkOption {
+        default = [ ];
+        type = lib.types.listOf lib.types.path;
         description = lib.mdDoc "Extra configuration files to pull into the tomcat conf directory";
       };
 
-      extraEnvironment = mkOption {
-        type = types.listOf types.str;
-        default = [];
+      extraEnvironment = lib.mkOption {
+        type = lib.types.listOf lib.types.str;
+        default = [ ];
         example = [ "ENVIRONMENT=production" ];
         description = lib.mdDoc "Environment Variables to pass to the tomcat service";
       };
 
-      extraGroups = mkOption {
-        default = [];
-        type = types.listOf types.str;
+      extraGroups = lib.mkOption {
+        default = [ ];
+        type = lib.types.listOf lib.types.str;
         example = [ "users" ];
         description = lib.mdDoc "Defines extra groups to which the tomcat user belongs.";
       };
 
-      user = mkOption {
-        type = types.str;
+      user = lib.mkOption {
+        type = lib.types.str;
         default = "tomcat";
         description = lib.mdDoc "User account under which Apache Tomcat runs.";
       };
 
-      group = mkOption {
-        type = types.str;
+      group = lib.mkOption {
+        type = lib.types.str;
         default = "tomcat";
         description = lib.mdDoc "Group account under which Apache Tomcat runs.";
       };
 
-      javaOpts = mkOption {
-        type = types.either (types.listOf types.str) types.str;
+      javaOpts = lib.mkOption {
+        type = lib.types.either (lib.types.listOf lib.types.str) lib.types.str;
         default = "";
         description = lib.mdDoc "Parameters to pass to the Java Virtual Machine which spawns Apache Tomcat";
       };
 
-      catalinaOpts = mkOption {
-        type = types.either (types.listOf types.str) types.str;
+      catalinaOpts = lib.mkOption {
+        type = lib.types.either (lib.types.listOf lib.types.str) lib.types.str;
         default = "";
         description = lib.mdDoc "Parameters to pass to the Java Virtual Machine which spawns the Catalina servlet container";
       };
 
-      sharedLibs = mkOption {
-        type = types.listOf types.str;
-        default = [];
+      sharedLibs = lib.mkOption {
+        type = lib.types.listOf lib.types.str;
+        default = [ ];
         description = lib.mdDoc "List containing JAR files or directories with JAR files which are libraries shared by the web applications";
       };
 
-      serverXml = mkOption {
-        type = types.lines;
+      serverXml = lib.mkOption {
+        type = lib.types.lines;
         default = "";
         description = lib.mdDoc ''
           Verbatim server.xml configuration.
@@ -118,87 +106,74 @@ in
         '';
       };
 
-      commonLibs = mkOption {
-        type = types.listOf types.str;
-        default = [];
+      commonLibs = lib.mkOption {
+        type = lib.types.listOf lib.types.str;
+        default = [ ];
         description = lib.mdDoc "List containing JAR files or directories with JAR files which are libraries shared by the web applications and the servlet container";
       };
 
-      webapps = mkOption {
-        type = types.listOf types.path;
+      webapps = lib.mkOption {
+        type = lib.types.listOf lib.types.path;
         default = [ tomcat.webapps ];
-        defaultText = literalExpression "[ config.services.tomcat.package.webapps ]";
+        defaultText = lib.literalExpression "[ config.services.tomcat.package.webapps ]";
         description = lib.mdDoc "List containing WAR files or directories with WAR files which are web applications to be deployed on Tomcat";
       };
 
-      virtualHosts = mkOption {
-        type = types.listOf (types.submodule {
+      virtualHosts = lib.mkOption {
+        type = lib.types.listOf (lib.types.submodule {
           options = {
-            name = mkOption {
-              type = types.str;
+            name = lib.mkOption {
+              type = lib.types.str;
               description = lib.mdDoc "name of the virtualhost";
             };
-            aliases = mkOption {
-              type = types.listOf types.str;
+            aliases = lib.mkOption {
+              type = lib.types.listOf lib.types.str;
               description = lib.mdDoc "aliases of the virtualhost";
-              default = [];
+              default = [ ];
             };
-            webapps = mkOption {
-              type = types.listOf types.path;
+            webapps = lib.mkOption {
+              type = lib.types.listOf lib.types.path;
               description = lib.mdDoc ''
                 List containing web application WAR files and/or directories containing
                 web applications and configuration files for the virtual host.
               '';
-              default = [];
+              default = [ ];
             };
           };
         });
-        default = [];
+        default = [ ];
         description = lib.mdDoc "List consisting of a virtual host name and a list of web applications to deploy on each virtual host";
       };
 
-      logPerVirtualHost = mkOption {
-        type = types.bool;
+      logPerVirtualHost = lib.mkOption {
+        type = lib.types.bool;
         default = false;
         description = lib.mdDoc "Whether to enable logging per virtual host.";
       };
 
-      jdk = mkOption {
-        type = types.package;
-        default = pkgs.jdk;
-        defaultText = literalExpression "pkgs.jdk";
-        description = lib.mdDoc "Which JDK to use.";
-      };
+      jdk = lib.mkPackageOptionMD pkgs "jdk" { };
 
       axis2 = {
+        enable = lib.mkEnableOption "Apache Axis2 container";
 
-        enable = mkOption {
-          default = false;
-          type = types.bool;
-          description = lib.mdDoc "Whether to enable an Apache Axis2 container";
-        };
-
-        services = mkOption {
-          default = [];
-          type = types.listOf types.str;
+        services = lib.mkOption {
+          default = [ ];
+          type = lib.types.listOf lib.types.str;
           description = lib.mdDoc "List containing AAR files or directories with AAR files which are web services to be deployed on Axis2";
         };
-
       };
-
     };
-
   };
 
-
   ###### implementation
 
-  config = mkIf config.services.tomcat.enable {
+  config = lib.mkIf config.services.tomcat.enable {
 
     users.groups.tomcat.gid = config.ids.gids.tomcat;
 
     users.users.tomcat =
-      { uid = config.ids.uids.tomcat;
+      {
+        uid = config.ids.uids.tomcat;
         description = "Tomcat user";
         home = "/homeless-shelter";
         group = "tomcat";
@@ -234,7 +209,7 @@ in
           ln -sfn ${tomcat}/conf/$i ${cfg.baseDir}/conf/`basename $i`
         done
 
-        ${optionalString (cfg.extraConfigFiles != []) ''
+        ${lib.optionalString (cfg.extraConfigFiles != []) ''
           for i in ${toString cfg.extraConfigFiles}; do
             ln -sfn $i ${cfg.baseDir}/conf/`basename $i`
           done
@@ -253,32 +228,32 @@ in
             hostElementForVirtualHost = virtualHost: ''
               <Host name="${virtualHost.name}" appBase="virtualhosts/${virtualHost.name}/webapps"
                     unpackWARs="true" autoDeploy="true" xmlValidation="false" xmlNamespaceAware="false">
-            '' + concatStrings (innerElementsForVirtualHost virtualHost) + ''
+            '' + lib.concatStrings (innerElementsForVirtualHost virtualHost) + ''
               </Host>
             '';
             innerElementsForVirtualHost = virtualHost:
               (map (alias: ''
                 <Alias>${alias}</Alias>
               '') virtualHost.aliases)
-              ++ (optional cfg.logPerVirtualHost ''
+              ++ (lib.optional cfg.logPerVirtualHost ''
                 <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs/${virtualHost.name}"
                        prefix="${virtualHost.name}_access_log." pattern="combined" resolveHosts="false"/>
               '');
-            hostElementsString = concatMapStringsSep "\n" hostElementForVirtualHost cfg.virtualHosts;
-            hostElementsSedString = replaceStrings ["\n"] ["\\\n"] hostElementsString;
+            hostElementsString = lib.concatMapStringsSep "\n" hostElementForVirtualHost cfg.virtualHosts;
+            hostElementsSedString = lib.replaceStrings ["\n"] ["\\\n"] hostElementsString;
           in ''
             # Create a modified server.xml which also includes all virtual hosts
-            sed -e "/<Engine name=\"Catalina\" defaultHost=\"localhost\">/a\\"${escapeShellArg hostElementsSedString} \
+            sed -e "/<Engine name=\"Catalina\" defaultHost=\"localhost\">/a\\"${lib.escapeShellArg hostElementsSedString} \
                   ${tomcat}/conf/server.xml > ${cfg.baseDir}/conf/server.xml
           ''
         }
-        ${optionalString (cfg.logDirs != []) ''
+        ${lib.optionalString (cfg.logDirs != []) ''
           for i in ${toString cfg.logDirs}; do
             mkdir -p ${cfg.baseDir}/logs/$i
             chown ${cfg.user}:${cfg.group} ${cfg.baseDir}/logs/$i
           done
         ''}
-        ${optionalString cfg.logPerVirtualHost (toString (map (h: ''
+        ${lib.optionalString cfg.logPerVirtualHost (toString (map (h: ''
           mkdir -p ${cfg.baseDir}/logs/${h.name}
           chown ${cfg.user}:${cfg.group} ${cfg.baseDir}/logs/${h.name}
         '') cfg.virtualHosts))}
@@ -345,7 +320,7 @@ in
 
           # Symlink all the given web applications files or paths into the webapps/ directory
           # of this virtual host
-          for i in "${optionalString (virtualHost ? webapps) (toString virtualHost.webapps)}"; do
+          for i in "${lib.optionalString (virtualHost ? webapps) (toString virtualHost.webapps)}"; do
             if [ -f $i ]; then
               # If the given web application is a file, symlink it into the webapps/ directory
               ln -sfn $i ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps/`basename $i`
@@ -368,7 +343,7 @@ in
           done
         '') cfg.virtualHosts)}
 
-        ${optionalString cfg.axis2.enable ''
+        ${lib.optionalString cfg.axis2.enable ''
           # Copy the Axis2 web application
           cp -av ${pkgs.axis2}/webapps/axis2 ${cfg.baseDir}/webapps
 
@@ -405,10 +380,10 @@ in
       serviceConfig = {
         Type = "forking";
         PermissionsStartOnly = true;
-        PIDFile="/run/tomcat/tomcat.pid";
+        PIDFile = "/run/tomcat/tomcat.pid";
         RuntimeDirectory = "tomcat";
         User = cfg.user;
-        Environment=[
+        Environment = [
           "CATALINA_BASE=${cfg.baseDir}"
           "CATALINA_PID=/run/tomcat/tomcat.pid"
           "JAVA_HOME='${cfg.jdk}'"
diff --git a/nixos/modules/services/x11/desktop-managers/budgie.nix b/nixos/modules/services/x11/desktop-managers/budgie.nix
index a4f8bd5051ecc..de4b2c0e50f5f 100644
--- a/nixos/modules/services/x11/desktop-managers/budgie.nix
+++ b/nixos/modules/services/x11/desktop-managers/budgie.nix
@@ -202,6 +202,7 @@ in {
     xdg.portal.extraPortals = with pkgs; [
       xdg-desktop-portal-gtk # provides a XDG Portals implementation.
     ];
+    xdg.portal.configPackages = mkDefault [ pkgs.budgie.budgie-desktop ];
 
     services.geoclue2.enable = mkDefault true; # for BCC's Privacy > Location Services panel.
     services.upower.enable = config.powerManagement.enable; # for Budgie's Status Indicator and BCC's Power panel.
diff --git a/nixos/modules/services/x11/desktop-managers/cinnamon.nix b/nixos/modules/services/x11/desktop-managers/cinnamon.nix
index f1e4d9304021e..a882bb140d219 100644
--- a/nixos/modules/services/x11/desktop-managers/cinnamon.nix
+++ b/nixos/modules/services/x11/desktop-managers/cinnamon.nix
@@ -200,6 +200,9 @@ in
         })
       ];
 
+      # https://salsa.debian.org/cinnamon-team/cinnamon/-/commit/f87c64f8d35ba406eb11ad442989a0716f6620cf#
+      xdg.portal.config.x-cinnamon.default = mkDefault [ "xapp" "gtk" ];
+
       # Override GSettings schemas
       environment.sessionVariables.NIX_GSETTINGS_OVERRIDES_DIR = "${nixos-gsettings-overrides}/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas";
 
diff --git a/nixos/modules/services/x11/desktop-managers/deepin.nix b/nixos/modules/services/x11/desktop-managers/deepin.nix
index 28d751305892b..f70e316a23094 100644
--- a/nixos/modules/services/x11/desktop-managers/deepin.nix
+++ b/nixos/modules/services/x11/desktop-managers/deepin.nix
@@ -78,6 +78,9 @@ in
         })
       ];
 
+      # https://github.com/NixOS/nixpkgs/pull/247766#issuecomment-1722839259
+      xdg.portal.config.deepin.default = mkDefault [ "gtk" ];
+
       environment.sessionVariables = {
         NIX_GSETTINGS_OVERRIDES_DIR = "${nixos-gsettings-overrides}/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas";
         DDE_POLKIT_AGENT_PLUGINS_DIRS = [ "${pkgs.deepin.dpa-ext-gnomekeyring}/lib/polkit-1-dde/plugins" ];
diff --git a/nixos/modules/services/x11/desktop-managers/enlightenment.nix b/nixos/modules/services/x11/desktop-managers/enlightenment.nix
index d4b2a50cb8af9..28dd408c923c8 100644
--- a/nixos/modules/services/x11/desktop-managers/enlightenment.nix
+++ b/nixos/modules/services/x11/desktop-managers/enlightenment.nix
@@ -63,7 +63,7 @@ in
         # make available for D-BUS user services
         #export XDG_DATA_DIRS=$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}:${config.system.path}/share:${e.efl}/share
 
-        # Update user dirs as described in http://freedesktop.org/wiki/Software/xdg-user-dirs/
+        # Update user dirs as described in https://freedesktop.org/wiki/Software/xdg-user-dirs/
         ${pkgs.xdg-user-dirs}/bin/xdg-user-dirs-update
       fi
     '';
diff --git a/nixos/modules/services/x11/desktop-managers/gnome.md b/nixos/modules/services/x11/desktop-managers/gnome.md
index d9e75bfe6bddd..aa36f66970ec4 100644
--- a/nixos/modules/services/x11/desktop-managers/gnome.md
+++ b/nixos/modules/services/x11/desktop-managers/gnome.md
@@ -145,7 +145,7 @@ services.xserver.desktopManager.gnome = {
 
     # Favorite apps in gnome-shell
     [org.gnome.shell]
-    favorite-apps=['org.gnome.Photos.desktop', 'org.gnome.Nautilus.desktop']
+    favorite-apps=['org.gnome.Console.desktop', 'org.gnome.Nautilus.desktop']
   '';
 
   extraGSettingsOverridePackages = [
diff --git a/nixos/modules/services/x11/desktop-managers/gnome.nix b/nixos/modules/services/x11/desktop-managers/gnome.nix
index 8b5daf83de1df..20eca7746447b 100644
--- a/nixos/modules/services/x11/desktop-managers/gnome.nix
+++ b/nixos/modules/services/x11/desktop-managers/gnome.nix
@@ -19,7 +19,7 @@ let
 
   defaultFavoriteAppsOverride = ''
     [org.gnome.shell]
-    favorite-apps=[ 'org.gnome.Epiphany.desktop', 'org.gnome.Geary.desktop', 'org.gnome.Calendar.desktop', 'org.gnome.Music.desktop', 'org.gnome.Photos.desktop', 'org.gnome.Nautilus.desktop' ]
+    favorite-apps=[ 'org.gnome.Epiphany.desktop', 'org.gnome.Geary.desktop', 'org.gnome.Calendar.desktop', 'org.gnome.Music.desktop', 'org.gnome.Nautilus.desktop' ]
   '';
 
   nixos-background-light = pkgs.nixos-artwork.wallpapers.simple-blue;
@@ -353,6 +353,7 @@ in
           buildPortalsInGnome = false;
         })
       ];
+      xdg.portal.configPackages = mkDefault [ pkgs.gnome.gnome-session ];
 
       networking.networkmanager.enable = mkDefault true;
 
@@ -455,22 +456,20 @@ in
             pkgs.glib # for gsettings program
             pkgs.gnome-menus
             pkgs.gtk3.out # for gtk-launch program
-            pkgs.xdg-user-dirs # Update user dirs as described in http://freedesktop.org/wiki/Software/xdg-user-dirs/
+            pkgs.xdg-user-dirs # Update user dirs as described in https://freedesktop.org/wiki/Software/xdg-user-dirs/
           ];
         in
         mandatoryPackages
         ++ utils.removePackagesByName optionalPackages config.environment.gnome.excludePackages;
     })
 
-    # Adapt from https://gitlab.gnome.org/GNOME/gnome-build-meta/blob/gnome-3-38/elements/core/meta-gnome-core-utilities.bst
+    # Adapt from https://gitlab.gnome.org/GNOME/gnome-build-meta/-/blob/gnome-45/elements/core/meta-gnome-core-utilities.bst
     (mkIf serviceCfg.core-utilities.enable {
       environment.systemPackages =
         with pkgs.gnome;
         utils.removePackagesByName
           ([
             baobab
-            cheese
-            eog
             epiphany
             pkgs.gnome-text-editor
             gnome-calculator
@@ -483,12 +482,13 @@ in
             gnome-logs
             gnome-maps
             gnome-music
-            pkgs.gnome-photos
             gnome-system-monitor
             gnome-weather
+            pkgs.loupe
             nautilus
             pkgs.gnome-connections
             simple-scan
+            pkgs.snapshot
             totem
             yelp
           ] ++ lib.optionals config.services.flatpak.enable [
diff --git a/nixos/modules/services/x11/desktop-managers/lxqt.nix b/nixos/modules/services/x11/desktop-managers/lxqt.nix
index b69da41c9fc9c..50ad72dc7388d 100644
--- a/nixos/modules/services/x11/desktop-managers/lxqt.nix
+++ b/nixos/modules/services/x11/desktop-managers/lxqt.nix
@@ -70,6 +70,9 @@ in
     services.xserver.libinput.enable = mkDefault true;
 
     xdg.portal.lxqt.enable = true;
+
+    # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1050804
+    xdg.portal.config.lxqt.default = mkDefault [ "lxqt" "gtk" ];
   };
 
 }
diff --git a/nixos/modules/services/x11/desktop-managers/mate.nix b/nixos/modules/services/x11/desktop-managers/mate.nix
index c93f120bed7f5..f535a1d298b9f 100644
--- a/nixos/modules/services/x11/desktop-managers/mate.nix
+++ b/nixos/modules/services/x11/desktop-managers/mate.nix
@@ -77,6 +77,8 @@ in
 
     security.pam.services.mate-screensaver.unixAuth = true;
 
+    xdg.portal.configPackages = mkDefault [ pkgs.mate.mate-desktop ];
+
     environment.pathsToLink = [ "/share" ];
   };
 
diff --git a/nixos/modules/services/x11/desktop-managers/pantheon.nix b/nixos/modules/services/x11/desktop-managers/pantheon.nix
index eef7aa14057ea..59bc142eeb7f9 100644
--- a/nixos/modules/services/x11/desktop-managers/pantheon.nix
+++ b/nixos/modules/services/x11/desktop-managers/pantheon.nix
@@ -201,7 +201,7 @@ in
         onboard
         orca # elementary/greeter#668
         sound-theme-freedesktop
-        xdg-user-dirs # Update user dirs as described in http://freedesktop.org/wiki/Software/xdg-user-dirs/
+        xdg-user-dirs # Update user dirs as described in https://freedesktop.org/wiki/Software/xdg-user-dirs/
       ]) ++ (with pkgs.pantheon; [
         # Artwork
         elementary-gtk-theme
@@ -229,9 +229,6 @@ in
 
       xdg.portal.enable = true;
       xdg.portal.extraPortals = [
-        # Some Pantheon apps enforce portal usage, we need this for e.g. notifications.
-        # Currently we have buildPortalsInGnome enabled, if you run into issues related
-        # to https://github.com/flatpak/xdg-desktop-portal/issues/656 please report to us.
         pkgs.xdg-desktop-portal-gtk
       ] ++ (with pkgs.pantheon; [
         elementary-files
@@ -239,6 +236,8 @@ in
         xdg-desktop-portal-pantheon
       ]);
 
+      xdg.portal.configPackages = mkDefault [ pkgs.pantheon.elementary-default-settings ];
+
       # Override GSettings schemas
       environment.sessionVariables.NIX_GSETTINGS_OVERRIDES_DIR = "${nixos-gsettings-desktop-schemas}/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas";
 
diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix
index 361dbe879a184..a177299bb32b2 100644
--- a/nixos/modules/services/x11/desktop-managers/plasma5.nix
+++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix
@@ -372,6 +372,7 @@ in
 
       xdg.portal.enable = true;
       xdg.portal.extraPortals = [ plasma5.xdg-desktop-portal-kde ];
+      xdg.portal.configPackages = mkDefault [ plasma5.xdg-desktop-portal-kde ];
       # xdg-desktop-portal-kde expects PipeWire to be running.
       # This does not, by default, replace PulseAudio.
       services.pipewire.enable = mkDefault true;
diff --git a/nixos/modules/services/x11/desktop-managers/xfce.nix b/nixos/modules/services/x11/desktop-managers/xfce.nix
index 191b3690c02f3..e28486bcc12d8 100644
--- a/nixos/modules/services/x11/desktop-managers/xfce.nix
+++ b/nixos/modules/services/x11/desktop-managers/xfce.nix
@@ -178,5 +178,7 @@ in
     ]) excludePackages;
 
     security.pam.services.xfce4-screensaver.unixAuth = cfg.enableScreensaver;
+
+    xdg.portal.configPackages = mkDefault [ pkgs.xfce.xfce4-session ];
   };
 }
diff --git a/nixos/modules/services/x11/display-managers/default.nix b/nixos/modules/services/x11/display-managers/default.nix
index 1f08ded7c96fa..16a7ff1a4bd5e 100644
--- a/nixos/modules/services/x11/display-managers/default.nix
+++ b/nixos/modules/services/x11/display-managers/default.nix
@@ -96,7 +96,7 @@ let
       )}
 
       # Speed up application start by 50-150ms according to
-      # http://kdemonkey.blogspot.nl/2008/04/magic-trick.html
+      # https://kdemonkey.blogspot.com/2008/04/magic-trick.html
       compose_cache="''${XCOMPOSECACHE:-$HOME/.compose-cache}"
       mkdir -p "$compose_cache"
       # To avoid accidentally deleting a wrongly set up XCOMPOSECACHE directory,
diff --git a/nixos/modules/services/x11/display-managers/xpra.nix b/nixos/modules/services/x11/display-managers/xpra.nix
index cb78f52d9b689..0861530f21e84 100644
--- a/nixos/modules/services/x11/display-managers/xpra.nix
+++ b/nixos/modules/services/x11/display-managers/xpra.nix
@@ -60,11 +60,11 @@ in
       VertRefresh 1.0 - 200.0
       #To add your own modes here, use a modeline calculator, like:
       # cvt:
-      # http://www.x.org/archive/X11R7.5/doc/man/man1/cvt.1.html
+      # https://www.x.org/archive/X11R7.5/doc/man/man1/cvt.1.html
       # xtiming:
-      # http://xtiming.sourceforge.net/cgi-bin/xtiming.pl
+      # https://xtiming.sourceforge.net/cgi-bin/xtiming.pl
       # gtf:
-      # http://gtf.sourceforge.net/
+      # https://gtf.sourceforge.net/
       #This can be used to get a specific DPI, but only for the default resolution:
       #DisplaySize 508 317
       #NOTE: the highest modes will not work without increasing the VideoRam
diff --git a/nixos/modules/services/x11/imwheel.nix b/nixos/modules/services/x11/imwheel.nix
index 133e64c65cdd7..bd2bcb7bcd06c 100644
--- a/nixos/modules/services/x11/imwheel.nix
+++ b/nixos/modules/services/x11/imwheel.nix
@@ -37,7 +37,7 @@ in
             Window class translation rules.
             /etc/X11/imwheelrc is generated based on this config
             which means this config is global for all users.
-            See [official man pages](http://imwheel.sourceforge.net/imwheel.1.html)
+            See [official man pages](https://imwheel.sourceforge.net/imwheel.1.html)
             for more information.
           '';
         };
diff --git a/nixos/modules/services/x11/xscreensaver.nix b/nixos/modules/services/x11/xscreensaver.nix
new file mode 100644
index 0000000000000..dc269b892ebcf
--- /dev/null
+++ b/nixos/modules/services/x11/xscreensaver.nix
@@ -0,0 +1,40 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.xscreensaver;
+in
+{
+  options.services.xscreensaver = {
+    enable = lib.mkEnableOption "xscreensaver user service";
+
+    package = lib.mkOption {
+      type = lib.types.package;
+      default = pkgs.xscreensaver;
+      defaultText = lib.literalExpression "pkgs.xscreensaver";
+      description = "Which xscreensaver package to use.";
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    # Make xscreensaver-auth setuid root so that it can (try to) prevent the OOM
+    # killer from unlocking the screen.
+    security.wrappers.xscreensaver-auth = {
+      setuid = true;
+      owner = "root";
+      group = "root";
+      source = "${pkgs.xscreensaver}/libexec/xscreensaver/xscreensaver-auth";
+    };
+
+    systemd.user.services.xscreensaver = {
+      enable = true;
+      description = "XScreenSaver";
+      after = [ "graphical-session-pre.target" ];
+      partOf = [ "graphical-session.target" ];
+      wantedBy = [ "graphical-session.target" ];
+      path = [ cfg.package ];
+      serviceConfig.ExecStart = "${cfg.package}/bin/xscreensaver -no-splash";
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ vancluever AndersonTorres ];
+}
diff --git a/nixos/modules/system/boot/kernel.nix b/nixos/modules/system/boot/kernel.nix
index 6b07686efcba2..a46331ccd431d 100644
--- a/nixos/modules/system/boot/kernel.nix
+++ b/nixos/modules/system/boot/kernel.nix
@@ -96,8 +96,8 @@ in
                                         # (required, but can be null if only config changes
                                         # are needed)
 
-          extraStructuredConfig = {     # attrset of extra configuration parameters
-            FOO = lib.kernel.yes;       # (without the CONFIG_ prefix, optional)
+          extraStructuredConfig = {     # attrset of extra configuration parameters without the CONFIG_ prefix
+            FOO = lib.kernel.yes;       # (optional)
           };                            # values should generally be lib.kernel.yes,
                                         # lib.kernel.no or lib.kernel.module
 
@@ -105,8 +105,9 @@ in
             foo = true;                 # (may be checked by other NixOS modules, optional)
           };
 
-          extraConfig = "CONFIG_FOO y"; # extra configuration options in string form
-                                        # (deprecated, use extraStructuredConfig instead, optional)
+          extraConfig = "FOO y";        # extra configuration options in string form without the CONFIG_ prefix
+                                        # (optional, multiple lines allowed to specify multiple options)
+                                        # (deprecated, use extraStructuredConfig instead)
         }
         ```
 
diff --git a/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix b/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
index 5ef3c5cd52a80..13df609071168 100644
--- a/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
+++ b/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
@@ -25,7 +25,7 @@ in
           under `/boot/extlinux.conf`.  For instance,
           U-Boot's generic distro boot support uses this file format.
 
-          See [U-boot's documentation](http://git.denx.de/?p=u-boot.git;a=blob;f=doc/README.distro;hb=refs/heads/master)
+          See [U-boot's documentation](https://u-boot.readthedocs.io/en/latest/develop/distro.html)
           for more information.
         '';
       };
diff --git a/nixos/modules/system/boot/loader/grub/grub.nix b/nixos/modules/system/boot/loader/grub/grub.nix
index 468f701ae5bc7..7097e1d83dca9 100644
--- a/nixos/modules/system/boot/loader/grub/grub.nix
+++ b/nixos/modules/system/boot/loader/grub/grub.nix
@@ -339,7 +339,7 @@ in
           See the
           [
           GRUB source code
-          ](http://git.savannah.gnu.org/cgit/grub.git/tree/grub-core/commands/nativedisk.c?h=grub-2.04#n326)
+          ](https://git.savannah.gnu.org/cgit/grub.git/tree/grub-core/commands/nativedisk.c?h=grub-2.04#n326)
           for which disk modules are available.
 
           The list elements are passed directly as `argv`
diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
index 310584e398bc1..e2e7ffe59dcd2 100644
--- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
@@ -11,7 +11,23 @@ import shutil
 import subprocess
 import sys
 import warnings
-from typing import NamedTuple
+import json
+from typing import NamedTuple, Dict, List
+from dataclasses import dataclass
+
+
+@dataclass
+class BootSpec:
+    init: str
+    initrd: str
+    initrdSecrets: str
+    kernel: str
+    kernelParams: List[str]
+    label: str
+    system: str
+    toplevel: str
+    specialisations: Dict[str, "BootSpec"]
+
 
 
 libc = ctypes.CDLL("libc.so.6")
@@ -71,12 +87,31 @@ def write_loader_conf(profile: str | None, generation: int, specialisation: str
     os.rename("@efiSysMountPoint@/loader/loader.conf.tmp", "@efiSysMountPoint@/loader/loader.conf")
 
 
-def profile_path(profile: str | None, generation: int, specialisation: str | None, name: str) -> str:
-    return os.path.realpath("%s/%s" % (system_dir(profile, generation, specialisation), name))
+def get_bootspec(profile: str | None, generation: int) -> BootSpec:
+    system_directory = system_dir(profile, generation, None)
+    boot_json_path = os.path.realpath("%s/%s" % (system_directory, "boot.json"))
+    if os.path.isfile(boot_json_path):
+        boot_json_f = open(boot_json_path, 'r')
+        bootspec_json = json.load(boot_json_f)
+    else:
+        boot_json_str = subprocess.check_output([
+        "@bootspecTools@/bin/synthesize",
+        "--version",
+        "1",
+        system_directory,
+        "/dev/stdout"],
+        universal_newlines=True)
+        bootspec_json = json.loads(boot_json_str)
+    return bootspec_from_json(bootspec_json)
+
+def bootspec_from_json(bootspec_json: Dict) -> BootSpec:
+    specialisations = bootspec_json['org.nixos.specialisation.v1']
+    specialisations = {k: bootspec_from_json(v) for k, v in specialisations.items()}
+    return BootSpec(**bootspec_json['org.nixos.bootspec.v1'], specialisations=specialisations)
 
 
-def copy_from_profile(profile: str | None, generation: int, specialisation: str | None, name: str, dry_run: bool = False) -> str:
-    store_file_path = profile_path(profile, generation, specialisation, name)
+def copy_from_file(file: str, dry_run: bool = False) -> str:
+    store_file_path = os.path.realpath(file)
     suffix = os.path.basename(store_file_path)
     store_dir = os.path.basename(os.path.dirname(store_file_path))
     efi_file_path = "/efi/nixos/%s-%s.efi" % (store_dir, suffix)
@@ -84,40 +119,19 @@ def copy_from_profile(profile: str | None, generation: int, specialisation: str
         copy_if_not_exists(store_file_path, "@efiSysMountPoint@%s" % (efi_file_path))
     return efi_file_path
 
-
-def describe_generation(profile: str | None, generation: int, specialisation: str | None) -> str:
-    try:
-        with open(profile_path(profile, generation, specialisation, "nixos-version")) as f:
-            nixos_version = f.read()
-    except IOError:
-        nixos_version = "Unknown"
-
-    kernel_dir = os.path.dirname(profile_path(profile, generation, specialisation, "kernel"))
-    module_dir = glob.glob("%s/lib/modules/*" % kernel_dir)[0]
-    kernel_version = os.path.basename(module_dir)
-
-    build_time = int(os.path.getctime(system_dir(profile, generation, specialisation)))
-    build_date = datetime.datetime.fromtimestamp(build_time).strftime('%F')
-
-    description = "@distroName@ {}, Linux Kernel {}, Built on {}".format(
-        nixos_version, kernel_version, build_date
-    )
-
-    return description
-
-
 def write_entry(profile: str | None, generation: int, specialisation: str | None,
-                machine_id: str, current: bool) -> None:
-    kernel = copy_from_profile(profile, generation, specialisation, "kernel")
-    initrd = copy_from_profile(profile, generation, specialisation, "initrd")
+                machine_id: str, bootspec: BootSpec, current: bool) -> None:
+    if specialisation:
+        bootspec = bootspec.specialisations[specialisation]
+    kernel = copy_from_file(bootspec.kernel)
+    initrd = copy_from_file(bootspec.initrd)
 
     title = "@distroName@{profile}{specialisation}".format(
         profile=" [" + profile + "]" if profile else "",
         specialisation=" (%s)" % specialisation if specialisation else "")
 
     try:
-        append_initrd_secrets = profile_path(profile, generation, specialisation, "append-initrd-secrets")
-        subprocess.check_call([append_initrd_secrets, "@efiSysMountPoint@%s" % (initrd)])
+        subprocess.check_call([bootspec.initrdSecrets, "@efiSysMountPoint@%s" % (initrd)])
     except FileNotFoundError:
         pass
     except subprocess.CalledProcessError:
@@ -132,17 +146,19 @@ def write_entry(profile: str | None, generation: int, specialisation: str | None
     entry_file = "@efiSysMountPoint@/loader/entries/%s" % (
         generation_conf_filename(profile, generation, specialisation))
     tmp_path = "%s.tmp" % (entry_file)
-    kernel_params = "init=%s " % profile_path(profile, generation, specialisation, "init")
+    kernel_params = "init=%s " % bootspec.init
+
+    kernel_params = kernel_params + " ".join(bootspec.kernelParams)
+    build_time = int(os.path.getctime(system_dir(profile, generation, specialisation)))
+    build_date = datetime.datetime.fromtimestamp(build_time).strftime('%F')
 
-    with open(profile_path(profile, generation, specialisation, "kernel-params")) as params_file:
-        kernel_params = kernel_params + params_file.read()
     with open(tmp_path, 'w') as f:
         f.write(BOOT_ENTRY.format(title=title,
                     generation=generation,
                     kernel=kernel,
                     initrd=initrd,
                     kernel_params=kernel_params,
-                    description=describe_generation(profile, generation, specialisation)))
+                    description=f"{bootspec.label}, built on {build_date}"))
         if machine_id is not None:
             f.write("machine-id %s\n" % machine_id)
         f.flush()
@@ -173,21 +189,14 @@ def get_generations(profile: str | None = None) -> list[SystemIdentifier]:
     return configurations[-configurationLimit:]
 
 
-def get_specialisations(profile: str | None, generation: int, _: str | None) -> list[SystemIdentifier]:
-    specialisations_dir = os.path.join(
-            system_dir(profile, generation, None), "specialisation")
-    if not os.path.exists(specialisations_dir):
-        return []
-    return [SystemIdentifier(profile, generation, spec) for spec in os.listdir(specialisations_dir)]
-
-
 def remove_old_entries(gens: list[SystemIdentifier]) -> None:
     rex_profile = re.compile(r"^@efiSysMountPoint@/loader/entries/nixos-(.*)-generation-.*\.conf$")
     rex_generation = re.compile(r"^@efiSysMountPoint@/loader/entries/nixos.*-generation-([0-9]+)(-specialisation-.*)?\.conf$")
     known_paths = []
     for gen in gens:
-        known_paths.append(copy_from_profile(*gen, "kernel", True))
-        known_paths.append(copy_from_profile(*gen, "initrd", True))
+        bootspec = get_bootspec(gen.profile, gen.generation)
+        known_paths.append(copy_from_file(bootspec.kernel, True))
+        known_paths.append(copy_from_file(bootspec.initrd, True))
     for path in glob.iglob("@efiSysMountPoint@/loader/entries/nixos*-generation-[1-9]*.conf"):
         if rex_profile.match(path):
             prof = rex_profile.sub(r"\1", path)
@@ -268,7 +277,7 @@ def install_bootloader(args: argparse.Namespace) -> None:
 
         if installed_version < available_version:
             print("updating systemd-boot from %s to %s" % (installed_version, available_version))
-            subprocess.check_call(["@systemd@/bin/bootctl", "--esp-path=@efiSysMountPoint@", "update"])
+            subprocess.check_call(["@systemd@/bin/bootctl", "--esp-path=@efiSysMountPoint@"] + bootctl_flags + ["update"])
 
     os.makedirs("@efiSysMountPoint@/efi/nixos", exist_ok=True)
     os.makedirs("@efiSysMountPoint@/loader/entries", exist_ok=True)
@@ -279,10 +288,11 @@ def install_bootloader(args: argparse.Namespace) -> None:
     remove_old_entries(gens)
     for gen in gens:
         try:
-            is_default = os.path.dirname(profile_path(*gen, "init")) == args.default_config
-            write_entry(*gen, machine_id, current=is_default)
-            for specialisation in get_specialisations(*gen):
-                write_entry(*specialisation, machine_id, current=is_default)
+            bootspec = get_bootspec(gen.profile, gen.generation)
+            is_default = os.path.dirname(bootspec.init) == args.default_config
+            write_entry(*gen, machine_id, bootspec, current=is_default)
+            for specialisation in bootspec.specialisations.keys():
+                write_entry(gen.profile, gen.generation, specialisation, machine_id, bootspec, current=is_default)
             if is_default:
                 write_loader_conf(*gen)
         except OSError as e:
diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
index 1086ab80b14f1..9d55c21077d13 100644
--- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
@@ -16,6 +16,8 @@ let
 
     systemd = config.systemd.package;
 
+    bootspecTools = pkgs.bootspec;
+
     nix = config.nix.package.out;
 
     timeout = optionalString (config.boot.loader.timeout != null) config.boot.loader.timeout;
diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix
index 06c329e006b84..ca560d63f3bdc 100644
--- a/nixos/modules/system/boot/luksroot.nix
+++ b/nixos/modules/system/boot/luksroot.nix
@@ -537,7 +537,7 @@ in
       description = lib.mdDoc ''
         Unless enabled, encryption keys can be easily recovered by an attacker with physical
         access to any machine with PCMCIA, ExpressCard, ThunderBolt or FireWire port.
-        More information is available at <http://en.wikipedia.org/wiki/DMA_attack>.
+        More information is available at <https://en.wikipedia.org/wiki/DMA_attack>.
 
         This option blacklists FireWire drivers, but doesn't remove them. You can manually
         load the drivers if you need to use a FireWire device, but don't forget to unload them!
diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix
index b7ced5b0d3466..33261021480f1 100644
--- a/nixos/modules/system/boot/networkd.nix
+++ b/nixos/modules/system/boot/networkd.nix
@@ -122,6 +122,16 @@ let
         (assertValueOneOf "PacketInfo" boolValues)
         (assertValueOneOf "VNetHeader" boolValues)
       ];
+
+      # See https://www.freedesktop.org/software/systemd/man/latest/systemd.netdev.html#%5BIPVTAP%5D%20Section%20Options
+      ipVlanVtapChecks = [
+        (assertOnlyFields [
+          "Mode"
+          "Flags"
+        ])
+        (assertValueOneOf "Mode" ["L2" "L3" "L3S" ])
+        (assertValueOneOf "Flags" ["private" "vepa" "bridge" ])
+      ];
     in {
 
       sectionNetdev = checkUnitConfig "Netdev" [
@@ -146,6 +156,7 @@ let
           "ip6gretap"
           "ipip"
           "ipvlan"
+          "ipvtap"
           "macvlan"
           "macvtap"
           "sit"
@@ -191,6 +202,10 @@ let
         (assertValueOneOf "ReorderHeader" boolValues)
       ];
 
+      sectionIPVLAN = checkUnitConfig "IPVLAN" ipVlanVtapChecks;
+
+      sectionIPVTAP = checkUnitConfig "IPVTAP" ipVlanVtapChecks;
+
       sectionMACVLAN = checkUnitConfig "MACVLAN" [
         (assertOnlyFields [
           "Mode"
@@ -615,6 +630,7 @@ let
           "VRF"
           "VLAN"
           "IPVLAN"
+          "IPVTAP"
           "MACVLAN"
           "MACVTAP"
           "VXLAN"
@@ -1277,6 +1293,7 @@ let
           "FirewallMark"
           "Wash"
           "SplitGSO"
+          "AckFilter"
         ])
         (assertValueOneOf "AutoRateIngress" boolValues)
         (assertInt "OverheadBytes")
@@ -1309,6 +1326,7 @@ let
         (assertRange "FirewallMark" 1 4294967295)
         (assertValueOneOf "Wash" boolValues)
         (assertValueOneOf "SplitGSO" boolValues)
+        (assertValueOneOf "AckFilter" (boolValues ++ ["aggressive"]))
       ];
 
       sectionControlledDelay = checkUnitConfig "ControlledDelay" [
@@ -1623,6 +1641,26 @@ let
       '';
     };
 
+    ipvlanConfig = mkOption {
+      default = {};
+      example = { Mode = "L2"; Flags = "private"; };
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionIPVLAN;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the `[IPVLAN]` section of the unit.
+        See {manpage}`systemd.netdev(5)` for details.
+      '';
+    };
+
+    ipvtapConfig = mkOption {
+      default = {};
+      example = { Mode = "L3"; Flags = "vepa"; };
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionIPVTAP;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the `[IPVTAP]` section of the unit.
+        See {manpage}`systemd.netdev(5)` for details.
+      '';
+    };
+
     macvlanConfig = mkOption {
       default = {};
       example = { Mode = "private"; };
diff --git a/nixos/modules/system/boot/stage-1-init.sh b/nixos/modules/system/boot/stage-1-init.sh
index bac354b4724bb..086e5d65da2f2 100644
--- a/nixos/modules/system/boot/stage-1-init.sh
+++ b/nixos/modules/system/boot/stage-1-init.sh
@@ -495,6 +495,8 @@ if test -e /sys/power/resume -a -e /sys/power/disk; then
     fi
 fi
 
+@postResumeCommands@
+
 # If we have a path to an iso file, find the iso and link it to /dev/root
 if [ -n "$isoPath" ]; then
   mkdir -p /findiso
diff --git a/nixos/modules/system/boot/stage-1.nix b/nixos/modules/system/boot/stage-1.nix
index f139902cdc85b..e990aeea7a14b 100644
--- a/nixos/modules/system/boot/stage-1.nix
+++ b/nixos/modules/system/boot/stage-1.nix
@@ -284,7 +284,7 @@ let
       # in the NixOS installation CD, so use ID_CDROM_MEDIA in the
       # corresponding udev rules for now.  This was the behaviour in
       # udev <= 154.  See also
-      #   http://www.spinics.net/lists/hotplug/msg03935.html
+      #   https://www.spinics.net/lists/hotplug/msg03935.html
       substituteInPlace $out/60-persistent-storage.rules \
         --replace ID_CDROM_MEDIA_TRACK_COUNT_DATA ID_CDROM_MEDIA
     ''; # */
@@ -316,7 +316,7 @@ let
     inherit (config.system.build) earlyMountScript;
 
     inherit (config.boot.initrd) checkJournalingFS verbose
-      preLVMCommands preDeviceCommands postDeviceCommands postMountCommands preFailCommands kernelModules;
+      preLVMCommands preDeviceCommands postDeviceCommands postResumeCommands postMountCommands preFailCommands kernelModules;
 
     resumeDevices = map (sd: if sd ? device then sd.device else "/dev/disk/by-label/${sd.label}")
                     (filter (sd: hasPrefix "/dev/" sd.device && !sd.randomEncryption.enable
@@ -435,7 +435,7 @@ let
          }
 
         # mindepth 1 so that we don't change the mode of /
-        (cd "$tmp" && find . -mindepth 1 -print0 | sort -z | bsdtar --uid 0 --gid 0 -cnf - -T - | bsdtar --null -cf - --format=newc @-) | \
+        (cd "$tmp" && find . -mindepth 1 | xargs touch -amt 197001010000 && find . -mindepth 1 -print0 | sort -z | bsdtar --uid 0 --gid 0 -cnf - -T - | bsdtar --null -cf - --format=newc @-) | \
           ${compressorExe} ${lib.escapeShellArgs initialRamdisk.compressorArgs} >> "$1"
       '';
 
@@ -527,6 +527,14 @@ in
       '';
     };
 
+    boot.initrd.postResumeCommands = mkOption {
+      default = "";
+      type = types.lines;
+      description = lib.mdDoc ''
+        Shell commands to be executed immediately after attempting to resume.
+      '';
+    };
+
     boot.initrd.postMountCommands = mkOption {
       default = "";
       type = types.lines;
diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix
index 68a8c1f37ed5a..87333999313e4 100644
--- a/nixos/modules/system/boot/systemd.nix
+++ b/nixos/modules/system/boot/systemd.nix
@@ -177,189 +177,190 @@ in
 {
   ###### interface
 
-  options = {
+  options.systemd = {
 
-    systemd.package = mkOption {
-      default = pkgs.systemd;
-      defaultText = literalExpression "pkgs.systemd";
-      type = types.package;
-      description = lib.mdDoc "The systemd package.";
-    };
+    package = mkPackageOption pkgs "systemd" {};
 
-    systemd.units = mkOption {
-      description = lib.mdDoc "Definition of systemd units.";
+    units = mkOption {
+      description = "Definition of systemd units; see {manpage}`systemd.unit(5)`.";
       default = {};
       type = systemdUtils.types.units;
     };
 
-    systemd.packages = mkOption {
+    packages = mkOption {
       default = [];
       type = types.listOf types.package;
       example = literalExpression "[ pkgs.systemd-cryptsetup-generator ]";
-      description = lib.mdDoc "Packages providing systemd units and hooks.";
+      description = "Packages providing systemd units and hooks.";
     };
 
-    systemd.targets = mkOption {
+    targets = mkOption {
       default = {};
       type = systemdUtils.types.targets;
-      description = lib.mdDoc "Definition of systemd target units.";
+      description = "Definition of systemd target units; see {manpage}`systemd.target(5)`";
     };
 
-    systemd.services = mkOption {
+    services = mkOption {
       default = {};
       type = systemdUtils.types.services;
-      description = lib.mdDoc "Definition of systemd service units.";
+      description = "Definition of systemd service units; see {manpage}`systemd.service(5)`.";
     };
 
-    systemd.sockets = mkOption {
+    sockets = mkOption {
       default = {};
       type = systemdUtils.types.sockets;
-      description = lib.mdDoc "Definition of systemd socket units.";
+      description = "Definition of systemd socket units; see {manpage}`systemd.socket(5)`.";
     };
 
-    systemd.timers = mkOption {
+    timers = mkOption {
       default = {};
       type = systemdUtils.types.timers;
-      description = lib.mdDoc "Definition of systemd timer units.";
+      description = "Definition of systemd timer units; see {manpage}`systemd.timer(5)`.";
     };
 
-    systemd.paths = mkOption {
+    paths = mkOption {
       default = {};
       type = systemdUtils.types.paths;
-      description = lib.mdDoc "Definition of systemd path units.";
+      description = "Definition of systemd path units; see {manpage}`systemd.path(5)`.";
     };
 
-    systemd.mounts = mkOption {
+    mounts = mkOption {
       default = [];
       type = systemdUtils.types.mounts;
-      description = lib.mdDoc ''
-        Definition of systemd mount units.
-        This is a list instead of an attrSet, because systemd mandates the names to be derived from
-        the 'where' attribute.
+      description = ''
+        Definition of systemd mount units; see {manpage}`systemd.mount(5)`.
+
+        This is a list instead of an attrSet, because systemd mandates
+        the names to be derived from the `where` attribute.
       '';
     };
 
-    systemd.automounts = mkOption {
+    automounts = mkOption {
       default = [];
       type = systemdUtils.types.automounts;
-      description = lib.mdDoc ''
-        Definition of systemd automount units.
-        This is a list instead of an attrSet, because systemd mandates the names to be derived from
-        the 'where' attribute.
+      description = ''
+        Definition of systemd automount units; see {manpage}`systemd.automount(5)`.
+
+        This is a list instead of an attrSet, because systemd mandates
+        the names to be derived from the `where` attribute.
       '';
     };
 
-    systemd.slices = mkOption {
+    slices = mkOption {
       default = {};
       type = systemdUtils.types.slices;
-      description = lib.mdDoc "Definition of slice configurations.";
+      description = "Definition of slice configurations; see {manpage}`systemd.slice(5)`.";
     };
 
-    systemd.generators = mkOption {
+    generators = mkOption {
       type = types.attrsOf types.path;
       default = {};
       example = { systemd-gpt-auto-generator = "/dev/null"; };
-      description = lib.mdDoc ''
-        Definition of systemd generators.
+      description = ''
+        Definition of systemd generators; see {manpage}`systemd.generator(5)`.
+
         For each `NAME = VALUE` pair of the attrSet, a link is generated from
         `/etc/systemd/system-generators/NAME` to `VALUE`.
       '';
     };
 
-    systemd.shutdown = mkOption {
+    shutdown = mkOption {
       type = types.attrsOf types.path;
       default = {};
-      description = lib.mdDoc ''
+      description = ''
         Definition of systemd shutdown executables.
         For each `NAME = VALUE` pair of the attrSet, a link is generated from
         `/etc/systemd/system-shutdown/NAME` to `VALUE`.
       '';
     };
 
-    systemd.defaultUnit = mkOption {
+    defaultUnit = mkOption {
       default = "multi-user.target";
       type = types.str;
-      description = lib.mdDoc "Default unit started when the system boots.";
+      description = ''
+        Default unit started when the system boots; see {manpage}`systemd.special(7)`.
+      '';
     };
 
-    systemd.ctrlAltDelUnit = mkOption {
+    ctrlAltDelUnit = mkOption {
       default = "reboot.target";
       type = types.str;
       example = "poweroff.target";
-      description = lib.mdDoc ''
-        Target that should be started when Ctrl-Alt-Delete is pressed.
+      description = ''
+        Target that should be started when Ctrl-Alt-Delete is pressed;
+        see {manpage}`systemd.special(7)`.
       '';
     };
 
-    systemd.globalEnvironment = mkOption {
+    globalEnvironment = mkOption {
       type = with types; attrsOf (nullOr (oneOf [ str path package ]));
       default = {};
       example = { TZ = "CET"; };
-      description = lib.mdDoc ''
+      description = ''
         Environment variables passed to *all* systemd units.
       '';
     };
 
-    systemd.managerEnvironment = mkOption {
+    managerEnvironment = mkOption {
       type = with types; attrsOf (nullOr (oneOf [ str path package ]));
       default = {};
       example = { SYSTEMD_LOG_LEVEL = "debug"; };
-      description = lib.mdDoc ''
+      description = ''
         Environment variables of PID 1. These variables are
         *not* passed to started units.
       '';
     };
 
-    systemd.enableCgroupAccounting = mkOption {
+    enableCgroupAccounting = mkOption {
       default = true;
       type = types.bool;
-      description = lib.mdDoc ''
-        Whether to enable cgroup accounting.
+      description = ''
+        Whether to enable cgroup accounting; see {manpage}`cgroups(7)`.
       '';
     };
 
-    systemd.enableUnifiedCgroupHierarchy = mkOption {
+    enableUnifiedCgroupHierarchy = mkOption {
       default = true;
       type = types.bool;
-      description = lib.mdDoc ''
-        Whether to enable the unified cgroup hierarchy (cgroupsv2).
+      description = ''
+        Whether to enable the unified cgroup hierarchy (cgroupsv2); see {manpage}`cgroups(7)`.
       '';
     };
 
-    systemd.extraConfig = mkOption {
+    extraConfig = mkOption {
       default = "";
       type = types.lines;
       example = "DefaultLimitCORE=infinity";
-      description = lib.mdDoc ''
-        Extra config options for systemd. See systemd-system.conf(5) man page
+      description = ''
+        Extra config options for systemd. See {manpage}`systemd-system.conf(5)` man page
         for available options.
       '';
     };
 
-    systemd.sleep.extraConfig = mkOption {
+    sleep.extraConfig = mkOption {
       default = "";
       type = types.lines;
       example = "HibernateDelaySec=1h";
-      description = lib.mdDoc ''
+      description = ''
         Extra config options for systemd sleep state logic.
-        See sleep.conf.d(5) man page for available options.
+        See {manpage}`sleep.conf.d(5)` man page for available options.
       '';
     };
 
-    systemd.additionalUpstreamSystemUnits = mkOption {
+    additionalUpstreamSystemUnits = mkOption {
       default = [ ];
       type = types.listOf types.str;
       example = [ "debug-shell.service" "systemd-quotacheck.service" ];
-      description = lib.mdDoc ''
+      description = ''
         Additional units shipped with systemd that shall be enabled.
       '';
     };
 
-    systemd.suppressedSystemUnits = mkOption {
+    suppressedSystemUnits = mkOption {
       default = [ ];
       type = types.listOf types.str;
       example = [ "systemd-backlight@.service" ];
-      description = lib.mdDoc ''
+      description = ''
         A list of units to skip when generating system systemd configuration directory. This has
         priority over upstream units, {option}`systemd.units`, and
         {option}`systemd.additionalUpstreamSystemUnits`. The main purpose of this is to
@@ -368,49 +369,56 @@ in
       '';
     };
 
-    systemd.watchdog.device = mkOption {
+    watchdog.device = mkOption {
       type = types.nullOr types.path;
       default = null;
       example = "/dev/watchdog";
-      description = lib.mdDoc ''
+      description = ''
         The path to a hardware watchdog device which will be managed by systemd.
-        If not specified, systemd will default to /dev/watchdog.
+        If not specified, systemd will default to `/dev/watchdog`.
       '';
     };
 
-    systemd.watchdog.runtimeTime = mkOption {
+    watchdog.runtimeTime = mkOption {
       type = types.nullOr types.str;
       default = null;
       example = "30s";
-      description = lib.mdDoc ''
+      description = ''
         The amount of time which can elapse before a watchdog hardware device
-        will automatically reboot the system. Valid time units include "ms",
-        "s", "min", "h", "d", and "w".
+        will automatically reboot the system.
+
+        Valid time units include "ms", "s", "min", "h", "d", and "w";
+        see {manpage}`systemd.time(7)`.
       '';
     };
 
-    systemd.watchdog.rebootTime = mkOption {
+    watchdog.rebootTime = mkOption {
       type = types.nullOr types.str;
       default = null;
       example = "10m";
-      description = lib.mdDoc ''
+      description = ''
         The amount of time which can elapse after a reboot has been triggered
         before a watchdog hardware device will automatically reboot the system.
-        Valid time units include "ms", "s", "min", "h", "d", and "w". If left
-        `null`, systemd will use its default of `10min`; see also {command}`man
-        5 systemd-system.conf`.
+        If left `null`, systemd will use its default of 10 minutes;
+        see {manpage}`systemd-system.conf(5)`.
+
+        Valid time units include "ms", "s", "min", "h", "d", and "w";
+        see also {manpage}`systemd.time(7)`.
       '';
     };
 
-    systemd.watchdog.kexecTime = mkOption {
+    watchdog.kexecTime = mkOption {
       type = types.nullOr types.str;
       default = null;
       example = "10m";
-      description = lib.mdDoc ''
-        The amount of time which can elapse when kexec is being executed before
+      description = ''
+        The amount of time which can elapse when `kexec` is being executed before
         a watchdog hardware device will automatically reboot the system. This
-        option should only be enabled if reloadTime is also enabled. Valid
-        time units include "ms", "s", "min", "h", "d", and "w".
+        option should only be enabled if `reloadTime` is also enabled;
+        see {manpage}`kexec(8)`.
+
+        Valid time units include "ms", "s", "min", "h", "d", and "w";
+        see also {manpage}`systemd.time(7)`.
       '';
     };
   };
@@ -493,32 +501,32 @@ in
       "systemd/system.conf".text = ''
         [Manager]
         ManagerEnvironment=${lib.concatStringsSep " " (lib.mapAttrsToList (n: v: "${n}=${lib.escapeShellArg v}") cfg.managerEnvironment)}
-        ${optionalString config.systemd.enableCgroupAccounting ''
+        ${optionalString cfg.enableCgroupAccounting ''
           DefaultCPUAccounting=yes
           DefaultIOAccounting=yes
           DefaultBlockIOAccounting=yes
           DefaultIPAccounting=yes
         ''}
         DefaultLimitCORE=infinity
-        ${optionalString (config.systemd.watchdog.device != null) ''
-          WatchdogDevice=${config.systemd.watchdog.device}
+        ${optionalString (cfg.watchdog.device != null) ''
+          WatchdogDevice=${cfg.watchdog.device}
         ''}
-        ${optionalString (config.systemd.watchdog.runtimeTime != null) ''
-          RuntimeWatchdogSec=${config.systemd.watchdog.runtimeTime}
+        ${optionalString (cfg.watchdog.runtimeTime != null) ''
+          RuntimeWatchdogSec=${cfg.watchdog.runtimeTime}
         ''}
-        ${optionalString (config.systemd.watchdog.rebootTime != null) ''
-          RebootWatchdogSec=${config.systemd.watchdog.rebootTime}
+        ${optionalString (cfg.watchdog.rebootTime != null) ''
+          RebootWatchdogSec=${cfg.watchdog.rebootTime}
         ''}
-        ${optionalString (config.systemd.watchdog.kexecTime != null) ''
-          KExecWatchdogSec=${config.systemd.watchdog.kexecTime}
+        ${optionalString (cfg.watchdog.kexecTime != null) ''
+          KExecWatchdogSec=${cfg.watchdog.kexecTime}
         ''}
 
-        ${config.systemd.extraConfig}
+        ${cfg.extraConfig}
       '';
 
       "systemd/sleep.conf".text = ''
         [Sleep]
-        ${config.systemd.sleep.extraConfig}
+        ${cfg.sleep.extraConfig}
       '';
 
       "systemd/system-generators" = { source = hooks "generators" cfg.generators; };
diff --git a/nixos/modules/system/boot/systemd/initrd.nix b/nixos/modules/system/boot/systemd/initrd.nix
index be40b8e969a17..0e7d59b32075b 100644
--- a/nixos/modules/system/boot/systemd/initrd.nix
+++ b/nixos/modules/system/boot/systemd/initrd.nix
@@ -358,6 +358,7 @@ in {
       [ "preDeviceCommands" ]
       [ "preLVMCommands" ]
       [ "postDeviceCommands" ]
+      [ "postResumeCommands" ]
       [ "postMountCommands" ]
       [ "extraUdevRulesCommands" ]
       [ "extraUtilsCommands" ]
@@ -369,7 +370,7 @@ in {
 
     boot.initrd.availableKernelModules = [
       # systemd needs this for some features
-      "autofs4"
+      "autofs"
       # systemd-cryptenroll
     ] ++ lib.optional cfg.enableTpm2 "tpm-tis"
     ++ lib.optional (cfg.enableTpm2 && !(pkgs.stdenv.hostPlatform.isRiscV64 || pkgs.stdenv.hostPlatform.isArmv7)) "tpm-crb";
diff --git a/nixos/modules/tasks/filesystems/bcachefs.nix b/nixos/modules/tasks/filesystems/bcachefs.nix
index 4eadec239e67d..f28fd5cde9c19 100644
--- a/nixos/modules/tasks/filesystems/bcachefs.nix
+++ b/nixos/modules/tasks/filesystems/bcachefs.nix
@@ -1,28 +1,43 @@
 { config, lib, pkgs, utils, ... }:
 
-with lib;
-
 let
 
-  bootFs = filterAttrs (n: fs: (fs.fsType == "bcachefs") && (utils.fsNeededForBoot fs)) config.fileSystems;
-
-  mountCommand = pkgs.runCommand "mount.bcachefs" {} ''
-    mkdir -p $out/bin
-    cat > $out/bin/mount.bcachefs <<EOF
-    #!/bin/sh
-    exec "/bin/bcachefs" mount "\$@"
-    EOF
-    chmod +x $out/bin/mount.bcachefs
-  '';
+  bootFs = lib.filterAttrs (n: fs: (fs.fsType == "bcachefs") && (utils.fsNeededForBoot fs)) config.fileSystems;
 
   commonFunctions = ''
     prompt() {
         local name="$1"
         printf "enter passphrase for $name: "
     }
+
     tryUnlock() {
         local name="$1"
         local path="$2"
+        local success=false
+        local target
+        local uuid=$(echo -n $path | sed -e 's,UUID=\(.*\),\1,g')
+
+        printf "waiting for device to appear $path"
+        for try in $(seq 10); do
+          if [ -e $path ]; then
+              target=$(readlink -f $path)
+              success=true
+              break
+          else
+              target=$(blkid --uuid $uuid)
+              if [ $? == 0 ]; then
+                 success=true
+                 break
+              fi
+          fi
+          echo -n "."
+          sleep 1
+        done
+        printf "\n"
+        if [ $success == true ]; then
+            path=$target
+        fi
+
         if bcachefs unlock -c $path > /dev/null 2> /dev/null; then    # test for encryption
             prompt $name
             until bcachefs unlock $path 2> /dev/null; do              # repeat until successfully unlocked
@@ -30,6 +45,8 @@ let
                 prompt $name
             done
             printf "unlocking successful.\n"
+        else
+            echo "Cannot unlock device $uuid with path $path" >&2
         fi
     }
   '';
@@ -38,7 +55,7 @@ let
   # remove this adaptation when bcachefs implements mounting by filesystem uuid
   # also, implement automatic waiting for the constituent devices when that happens
   # bcachefs does not support mounting devices with colons in the path, ergo we don't (see #49671)
-  firstDevice = fs: head (splitString ":" fs.device);
+  firstDevice = fs: lib.head (lib.splitString ":" fs.device);
 
   openCommand = name: fs: ''
     tryUnlock ${name} ${firstDevice fs}
@@ -72,41 +89,61 @@ let
     };
   };
 
+  assertions = [
+    {
+      assertion = let
+        kernel = config.boot.kernelPackages.kernel;
+      in (
+        kernel.kernelAtLeast "6.7" || (
+          lib.elem (kernel.structuredExtraConfig.BCACHEFS_FS or null) [
+            lib.kernel.module
+            lib.kernel.yes
+            (lib.kernel.option lib.kernel.yes)
+          ]
+        )
+      );
+
+      message = "Linux 6.7-rc1 at minimum or a custom linux kernel with bcachefs support is required";
+    }
+  ];
 in
 
 {
-  config = mkIf (elem "bcachefs" config.boot.supportedFilesystems) (mkMerge [
+  config = lib.mkIf (lib.elem "bcachefs" config.boot.supportedFilesystems) (lib.mkMerge [
     {
-      # We do not want to include bachefs in the fsPackages for systemd-initrd
-      # because we provide the unwrapped version of mount.bcachefs
-      # through the extraBin option, which will make it available for use.
-      system.fsPackages = lib.optional (!config.boot.initrd.systemd.enable) pkgs.bcachefs-tools;
-      environment.systemPackages = lib.optional (config.boot.initrd.systemd.enable) pkgs.bcachefs-tools;
-
-      # use kernel package with bcachefs support until it's in mainline
-      boot.kernelPackages = pkgs.linuxPackages_testing_bcachefs;
+      inherit assertions;
+      # needed for systemd-remount-fs
+      system.fsPackages = [ pkgs.bcachefs-tools ];
+
+      # FIXME: Replace this with `linuxPackages_testing` after NixOS 23.11 is released
+      # FIXME: Replace this with `linuxPackages_latest` when 6.7 is released, remove this line when the LTS version is at least 6.7
+      boot.kernelPackages = lib.mkDefault (
+        # FIXME: Remove warning after NixOS 23.11 is released
+        lib.warn "Please upgrade to Linux 6.7-rc1 or later: 'linuxPackages_testing_bcachefs' is deprecated. Use 'boot.kernelPackages = pkgs.linuxPackages_testing;' to silence this warning"
+        pkgs.linuxPackages_testing_bcachefs
+      );
 
       systemd.services = lib.mapAttrs' (mkUnits "") (lib.filterAttrs (n: fs: (fs.fsType == "bcachefs") && (!utils.fsNeededForBoot fs)) config.fileSystems);
     }
 
-    (mkIf ((elem "bcachefs" config.boot.initrd.supportedFilesystems) || (bootFs != {})) {
+    (lib.mkIf ((lib.elem "bcachefs" config.boot.initrd.supportedFilesystems) || (bootFs != {})) {
+      inherit assertions;
       # chacha20 and poly1305 are required only for decryption attempts
       boot.initrd.availableKernelModules = [ "bcachefs" "sha256" "chacha20" "poly1305" ];
-
       boot.initrd.systemd.extraBin = {
+        # do we need this? boot/systemd.nix:566 & boot/systemd/initrd.nix:357
         "bcachefs" = "${pkgs.bcachefs-tools}/bin/bcachefs";
-        "mount.bcachefs" = "${mountCommand}/bin/mount.bcachefs";
+        "mount.bcachefs" = "${pkgs.bcachefs-tools}/bin/mount.bcachefs";
       };
-
       boot.initrd.extraUtilsCommands = lib.mkIf (!config.boot.initrd.systemd.enable) ''
         copy_bin_and_libs ${pkgs.bcachefs-tools}/bin/bcachefs
-        copy_bin_and_libs ${mountCommand}/bin/mount.bcachefs
+        copy_bin_and_libs ${pkgs.bcachefs-tools}/bin/mount.bcachefs
       '';
       boot.initrd.extraUtilsCommandsTest = lib.mkIf (!config.boot.initrd.systemd.enable) ''
         $out/bin/bcachefs version
       '';
 
-      boot.initrd.postDeviceCommands = lib.mkIf (!config.boot.initrd.systemd.enable) (commonFunctions + concatStrings (mapAttrsToList openCommand bootFs));
+      boot.initrd.postDeviceCommands = lib.mkIf (!config.boot.initrd.systemd.enable) (commonFunctions + lib.concatStrings (lib.mapAttrsToList openCommand bootFs));
 
       boot.initrd.systemd.services = lib.mapAttrs' (mkUnits "/sysroot") bootFs;
     })
diff --git a/nixos/modules/tasks/filesystems/zfs.nix b/nixos/modules/tasks/filesystems/zfs.nix
index 4b6a5b6c12c14..72bc79f31b68a 100644
--- a/nixos/modules/tasks/filesystems/zfs.nix
+++ b/nixos/modules/tasks/filesystems/zfs.nix
@@ -16,6 +16,7 @@ let
   cfgTrim = config.services.zfs.trim;
   cfgZED = config.services.zfs.zed;
 
+  selectModulePackage = package: config.boot.kernelPackages.${package.kernelModuleAttribute};
   inInitrd = any (fs: fs == "zfs") config.boot.initrd.supportedFilesystems;
   inSystem = any (fs: fs == "zfs") config.boot.supportedFilesystems;
 
@@ -210,11 +211,17 @@ in
   options = {
     boot.zfs = {
       package = mkOption {
-        readOnly = true;
         type = types.package;
-        default = if config.boot.zfs.enableUnstable then pkgs.zfsUnstable else pkgs.zfs;
-        defaultText = literalExpression "if config.boot.zfs.enableUnstable then pkgs.zfsUnstable else pkgs.zfs";
-        description = lib.mdDoc "Configured ZFS userland tools package.";
+        default = if cfgZfs.enableUnstable then pkgs.zfsUnstable else pkgs.zfs;
+        defaultText = literalExpression "if zfsUnstable is enabled then pkgs.zfsUnstable else pkgs.zfs";
+        description = lib.mdDoc "Configured ZFS userland tools package, use `pkgs.zfsUnstable` if you want to track the latest staging ZFS branch.";
+      };
+
+      modulePackage = mkOption {
+        internal = true; # It is supposed to be selected automatically, but can be overridden by expert users.
+        default = selectModulePackage cfgZfs.package;
+        type = types.package;
+        description = lib.mdDoc "Configured ZFS kernel module package.";
       };
 
       enabled = mkOption {
@@ -534,6 +541,10 @@ in
     (mkIf cfgZfs.enabled {
       assertions = [
         {
+          assertion = cfgZfs.modulePackage.version == cfgZfs.package.version;
+          message = "The kernel module and the userspace tooling versions are not matching, this is an unsupported usecase.";
+        }
+        {
           assertion = cfgZED.enableMail -> cfgZfs.package.enableMail;
           message = ''
             To allow ZED to send emails, ZFS needs to be configured to enable
@@ -571,18 +582,14 @@ in
         # https://github.com/NixOS/nixpkgs/issues/106093
         kernelParams = lib.optionals (!config.boot.zfs.allowHibernation) [ "nohibernate" ];
 
-        extraModulePackages = let
-          kernelPkg = if config.boot.zfs.enableUnstable then
-            config.boot.kernelPackages.zfsUnstable
-           else
-            config.boot.kernelPackages.zfs;
-        in [
-          (kernelPkg.override { inherit (cfgZfs) removeLinuxDRM; })
+        extraModulePackages = [
+          (cfgZfs.modulePackage.override { inherit (cfgZfs) removeLinuxDRM; })
         ];
       };
 
       boot.initrd = mkIf inInitrd {
-        kernelModules = [ "zfs" ] ++ optional (!cfgZfs.enableUnstable) "spl";
+        # spl has been removed in ≥ 2.2.0.
+        kernelModules = [ "zfs" ] ++ lib.optional (lib.versionOlder "2.2.0" version) "spl";
         extraUtilsCommands =
           mkIf (!config.boot.initrd.systemd.enable) ''
             copy_bin_and_libs ${cfgZfs.package}/sbin/zfs
diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix
index d976f9951bb55..298add13437a0 100644
--- a/nixos/modules/tasks/network-interfaces.nix
+++ b/nixos/modules/tasks/network-interfaces.nix
@@ -498,7 +498,7 @@ in
         option will result in an evaluation error if the hostname is empty or
         no domain is specified.
 
-        Modules that accept a mere `networing.hostName` but prefer a fully qualified
+        Modules that accept a mere `networking.hostName` but prefer a fully qualified
         domain name may use `networking.fqdnOrHostName` instead.
       '';
     };
diff --git a/nixos/modules/testing/test-instrumentation.nix b/nixos/modules/testing/test-instrumentation.nix
index c91e54f5a4d78..9ee77cd79a9b1 100644
--- a/nixos/modules/testing/test-instrumentation.nix
+++ b/nixos/modules/testing/test-instrumentation.nix
@@ -6,49 +6,124 @@
 with lib;
 
 let
+  cfg = config.testing;
+
   qemu-common = import ../../lib/qemu-common.nix { inherit lib pkgs; };
+
+  backdoorService = {
+    requires = [ "dev-hvc0.device" "dev-${qemu-common.qemuSerialDevice}.device" ];
+    after = [ "dev-hvc0.device" "dev-${qemu-common.qemuSerialDevice}.device" ];
+    script =
+      ''
+        export USER=root
+        export HOME=/root
+        export DISPLAY=:0.0
+
+        if [[ -e /etc/profile ]]; then
+            source /etc/profile
+        fi
+
+        # Don't use a pager when executing backdoor
+        # actions. Because we use a tty, commands like systemctl
+        # or nix-store get confused into thinking they're running
+        # interactively.
+        export PAGER=
+
+        cd /tmp
+        exec < /dev/hvc0 > /dev/hvc0
+        while ! exec 2> /dev/${qemu-common.qemuSerialDevice}; do sleep 0.1; done
+        echo "connecting to host..." >&2
+        stty -F /dev/hvc0 raw -echo # prevent nl -> cr/nl conversion
+        # The following line is essential since it signals to
+        # the test driver that the shell is ready.
+        # See: the connect method in the Machine class.
+        echo "Spawning backdoor root shell..."
+        # Passing the terminal device makes bash run non-interactively.
+        # Otherwise we get errors on the terminal because bash tries to
+        # setup things like job control.
+        # Note: calling bash explicitly here instead of sh makes sure that
+        # we can also run non-NixOS guests during tests.
+        PS1= exec /usr/bin/env bash --norc /dev/hvc0
+      '';
+      serviceConfig.KillSignal = "SIGHUP";
+  };
+
 in
 
 {
 
+  options.testing = {
+
+    initrdBackdoor = lib.mkEnableOption (lib.mdDoc ''
+      enable backdoor.service in initrd. Requires
+      boot.initrd.systemd.enable to be enabled. Boot will pause in
+      stage 1 at initrd.target, and will listen for commands from the
+      Machine python interface, just like stage 2 normally does. This
+      enables commands to be sent to test and debug stage 1. Use
+      machine.switch_root() to leave stage 1 and proceed to stage 2.
+    '');
+
+  };
+
   config = {
 
-    systemd.services.backdoor =
-      { wantedBy = [ "multi-user.target" ];
-        requires = [ "dev-hvc0.device" "dev-${qemu-common.qemuSerialDevice}.device" ];
-        after = [ "dev-hvc0.device" "dev-${qemu-common.qemuSerialDevice}.device" ];
-        script =
-          ''
-            export USER=root
-            export HOME=/root
-            export DISPLAY=:0.0
+    assertions = [
+      {
+        assertion = cfg.initrdBackdoor -> config.boot.initrd.systemd.enable;
+        message = ''
+          testing.initrdBackdoor requires boot.initrd.systemd.enable to be enabled.
+        '';
+      }
+    ];
 
-            source /etc/profile
+    systemd.services.backdoor = lib.mkMerge [
+      backdoorService
+      {
+        wantedBy = [ "multi-user.target" ];
+      }
+    ];
 
-            # Don't use a pager when executing backdoor
-            # actions. Because we use a tty, commands like systemctl
-            # or nix-store get confused into thinking they're running
-            # interactively.
-            export PAGER=
-
-            cd /tmp
-            exec < /dev/hvc0 > /dev/hvc0
-            while ! exec 2> /dev/${qemu-common.qemuSerialDevice}; do sleep 0.1; done
-            echo "connecting to host..." >&2
-            stty -F /dev/hvc0 raw -echo # prevent nl -> cr/nl conversion
-            # The following line is essential since it signals to
-            # the test driver that the shell is ready.
-            # See: the connect method in the Machine class.
-            echo "Spawning backdoor root shell..."
-            # Passing the terminal device makes bash run non-interactively.
-            # Otherwise we get errors on the terminal because bash tries to
-            # setup things like job control.
-            # Note: calling bash explicitly here instead of sh makes sure that
-            # we can also run non-NixOS guests during tests.
-            PS1= exec /usr/bin/env bash --norc /dev/hvc0
-          '';
-        serviceConfig.KillSignal = "SIGHUP";
-      };
+    boot.initrd.systemd = lib.mkMerge [
+      {
+        contents."/etc/systemd/journald.conf".text = ''
+          [Journal]
+          ForwardToConsole=yes
+          MaxLevelConsole=debug
+        '';
+
+        extraConfig = config.systemd.extraConfig;
+      }
+
+      (lib.mkIf cfg.initrdBackdoor {
+        # Implemented in machine.switch_root(). Suppress the unit by
+        # making it a noop without removing it, which would break
+        # initrd-parse-etc.service
+        services.initrd-cleanup.serviceConfig.ExecStart = [
+          # Reset
+          ""
+          # noop
+          "/bin/true"
+        ];
+
+        services.backdoor = lib.mkMerge [
+          backdoorService
+          {
+            # TODO: Both stage 1 and stage 2 should use these same
+            # settings. But a lot of existing tests rely on
+            # backdoor.service having default orderings,
+            # e.g. systemd-boot.update relies on /boot being mounted
+            # as soon as backdoor starts. But it can be useful for
+            # backdoor to start even earlier.
+            wantedBy = [ "sysinit.target" ];
+            unitConfig.DefaultDependencies = false;
+            conflicts = [ "shutdown.target" "initrd-switch-root.target" ];
+            before = [ "shutdown.target" "initrd-switch-root.target" ];
+          }
+        ];
+
+        contents."/usr/bin/env".source = "${pkgs.coreutils}/bin/env";
+      })
+    ];
 
     # Prevent agetty from being instantiated on the serial device, since it
     # interferes with the backdoor (writes to it will randomly fail
@@ -104,12 +179,6 @@ in
         MaxLevelConsole=debug
       '';
 
-    boot.initrd.systemd.contents."/etc/systemd/journald.conf".text = ''
-      [Journal]
-      ForwardToConsole=yes
-      MaxLevelConsole=debug
-    '';
-
     systemd.extraConfig = ''
       # Don't clobber the console with duplicate systemd messages.
       ShowStatus=no
@@ -123,8 +192,6 @@ in
       DefaultDeviceTimeoutSec=300
     '';
 
-    boot.initrd.systemd.extraConfig = config.systemd.extraConfig;
-
     boot.consoleLogLevel = 7;
 
     # Prevent tests from accessing the Internet.
diff --git a/nixos/modules/virtualisation/azure-agent.nix b/nixos/modules/virtualisation/azure-agent.nix
index 6e6021cf80fe3..e712fac17a462 100644
--- a/nixos/modules/virtualisation/azure-agent.nix
+++ b/nixos/modules/virtualisation/azure-agent.nix
@@ -61,7 +61,7 @@ in
 
         # Which provisioning agent to use. Supported values are "auto" (default), "waagent",
         # "cloud-init", or "disabled".
-        Provisioning.Agent=disabled
+        Provisioning.Agent=auto
 
         # Password authentication for root account will be unavailable.
         Provisioning.DeleteRootPassword=n
@@ -241,7 +241,16 @@ in
       after = [ "network-online.target" "sshd.service" ];
       wants = [ "network-online.target" ];
 
-      path = [ pkgs.e2fsprogs pkgs.bash ];
+      path = [
+        pkgs.e2fsprogs
+        pkgs.bash
+
+        # waagent's Microsoft.OSTCExtensions.VMAccessForLinux needs Python 3
+        pkgs.python39
+
+        # waagent's Microsoft.CPlat.Core.RunCommandLinux needs lsof
+        pkgs.lsof
+      ];
       description = "Windows Azure Agent Service";
       unitConfig.ConditionPathExists = "/etc/waagent.conf";
       serviceConfig = {
@@ -250,5 +259,10 @@ in
       };
     };
 
+    # waagent will generate files under /etc/sudoers.d during provisioning
+    security.sudo.extraConfig = ''
+      #includedir /etc/sudoers.d
+    '';
+
   };
 }
diff --git a/nixos/modules/virtualisation/azure-image.nix b/nixos/modules/virtualisation/azure-image.nix
index 39c6cab5980a1..d909680cca1ff 100644
--- a/nixos/modules/virtualisation/azure-image.nix
+++ b/nixos/modules/virtualisation/azure-image.nix
@@ -37,42 +37,5 @@ in
       inherit config lib pkgs;
     };
 
-    # Azure metadata is available as a CD-ROM drive.
-    fileSystems."/metadata".device = "/dev/sr0";
-
-    systemd.services.fetch-ssh-keys = {
-      description = "Fetch host keys and authorized_keys for root user";
-
-      wantedBy = [ "sshd.service" "waagent.service" ];
-      before = [ "sshd.service" "waagent.service" ];
-
-      path  = [ pkgs.coreutils ];
-      script =
-        ''
-          eval "$(cat /metadata/CustomData.bin)"
-          if ! [ -z "$ssh_host_ecdsa_key" ]; then
-            echo "downloaded ssh_host_ecdsa_key"
-            echo "$ssh_host_ecdsa_key" > /etc/ssh/ssh_host_ed25519_key
-            chmod 600 /etc/ssh/ssh_host_ed25519_key
-          fi
-
-          if ! [ -z "$ssh_host_ecdsa_key_pub" ]; then
-            echo "downloaded ssh_host_ecdsa_key_pub"
-            echo "$ssh_host_ecdsa_key_pub" > /etc/ssh/ssh_host_ed25519_key.pub
-            chmod 644 /etc/ssh/ssh_host_ed25519_key.pub
-          fi
-
-          if ! [ -z "$ssh_root_auth_key" ]; then
-            echo "downloaded ssh_root_auth_key"
-            mkdir -m 0700 -p /root/.ssh
-            echo "$ssh_root_auth_key" > /root/.ssh/authorized_keys
-            chmod 600 /root/.ssh/authorized_keys
-          fi
-        '';
-      serviceConfig.Type = "oneshot";
-      serviceConfig.RemainAfterExit = true;
-      serviceConfig.StandardError = "journal+console";
-      serviceConfig.StandardOutput = "journal+console";
-    };
   };
 }
diff --git a/nixos/modules/virtualisation/google-compute-config.nix b/nixos/modules/virtualisation/google-compute-config.nix
index 3c503f027d798..887af26949feb 100644
--- a/nixos/modules/virtualisation/google-compute-config.nix
+++ b/nixos/modules/virtualisation/google-compute-config.nix
@@ -84,6 +84,10 @@ in
     { groups = [ "google-sudoers" ]; commands = [ { command = "ALL"; options = [ "NOPASSWD" ]; } ]; }
   ];
 
+  security.sudo-rs.extraRules = mkIf config.users.mutableUsers [
+    { groups = [ "google-sudoers" ]; commands = [ { command = "ALL"; options = [ "NOPASSWD" ]; } ]; }
+  ];
+
   users.groups.google-sudoers = mkIf config.users.mutableUsers { };
 
   boot.extraModprobeConfig = readFile "${pkgs.google-guest-configs}/etc/modprobe.d/gce-blacklist.conf";
diff --git a/nixos/modules/virtualisation/google-compute-image.nix b/nixos/modules/virtualisation/google-compute-image.nix
index dcdd1b59eef53..e4a18fd81d715 100644
--- a/nixos/modules/virtualisation/google-compute-image.nix
+++ b/nixos/modules/virtualisation/google-compute-image.nix
@@ -56,6 +56,11 @@ in
       efiInstallAsRemovable = true;
     };
 
+    fileSystems."/boot" = mkIf cfg.efi {
+      device = "/dev/disk/by-label/ESP";
+      fsType = "vfat";
+    };
+
     system.build.googleComputeImage = import ../../lib/make-disk-image.nix {
       name = "google-compute-image";
       postVM = ''
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index f44fcfcf54ab6..f8894df631efe 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -117,6 +117,7 @@ in {
   allTerminfo = handleTest ./all-terminfo.nix {};
   alps = handleTest ./alps.nix {};
   amazon-init-shell = handleTest ./amazon-init-shell.nix {};
+  amazon-ssm-agent = handleTest ./amazon-ssm-agent.nix {};
   amd-sev = runTest ./amd-sev.nix;
   anbox = runTest ./anbox.nix;
   anuko-time-tracker = handleTest ./anuko-time-tracker.nix {};
@@ -124,6 +125,7 @@ in {
   apfs = runTest ./apfs.nix;
   appliance-repart-image = runTest ./appliance-repart-image.nix;
   apparmor = handleTest ./apparmor.nix {};
+  archi = handleTest ./archi.nix {};
   atd = handleTest ./atd.nix {};
   atop = handleTest ./atop.nix {};
   atuin = handleTest ./atuin.nix {};
@@ -172,6 +174,7 @@ in {
   cassandra_3_0 = handleTest ./cassandra.nix { testPackage = pkgs.cassandra_3_0; };
   cassandra_3_11 = handleTest ./cassandra.nix { testPackage = pkgs.cassandra_3_11; };
   cassandra_4 = handleTest ./cassandra.nix { testPackage = pkgs.cassandra_4; };
+  centrifugo = runTest ./centrifugo.nix;
   ceph-multi-node = handleTestOn [ "aarch64-linux" "x86_64-linux" ] ./ceph-multi-node.nix {};
   ceph-single-node = handleTestOn [ "aarch64-linux" "x86_64-linux" ] ./ceph-single-node.nix {};
   ceph-single-node-bluestore = handleTestOn [ "aarch64-linux" "x86_64-linux" ] ./ceph-single-node-bluestore.nix {};
@@ -191,7 +194,6 @@ in {
   cntr = handleTestOn ["aarch64-linux" "x86_64-linux"] ./cntr.nix {};
   cockpit = handleTest ./cockpit.nix {};
   cockroachdb = handleTestOn ["x86_64-linux"] ./cockroachdb.nix {};
-  code-server = handleTest ./code-server.nix {};
   coder = handleTest ./coder.nix {};
   collectd = handleTest ./collectd.nix {};
   connman = handleTest ./connman.nix {};
@@ -330,6 +332,7 @@ in {
   gitolite-fcgiwrap = handleTest ./gitolite-fcgiwrap.nix {};
   glusterfs = handleTest ./glusterfs.nix {};
   gnome = handleTest ./gnome.nix {};
+  gnome-extensions = handleTest ./gnome-extensions.nix {};
   gnome-flashback = handleTest ./gnome-flashback.nix {};
   gnome-xorg = handleTest ./gnome-xorg.nix {};
   gnupg = handleTest ./gnupg.nix {};
@@ -616,6 +619,7 @@ in {
   openstack-image-userdata = (handleTestOn ["x86_64-linux"] ./openstack-image.nix {}).userdata or {};
   opentabletdriver = handleTest ./opentabletdriver.nix {};
   opentelemetry-collector = handleTest ./opentelemetry-collector.nix {};
+  ocsinventory-agent = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./ocsinventory-agent.nix {};
   owncast = handleTest ./owncast.nix {};
   outline = handleTest ./outline.nix {};
   image-contents = handleTest ./image-contents.nix {};
@@ -656,6 +660,7 @@ in {
   phylactery = handleTest ./web-apps/phylactery.nix {};
   pict-rs = handleTest ./pict-rs.nix {};
   pinnwand = handleTest ./pinnwand.nix {};
+  plantuml-server = handleTest ./plantuml-server.nix {};
   plasma-bigscreen = handleTest ./plasma-bigscreen.nix {};
   plasma5 = handleTest ./plasma5.nix {};
   plasma5-systemd-start = handleTest ./plasma5-systemd-start.nix {};
@@ -737,6 +742,7 @@ in {
   sddm = handleTest ./sddm.nix {};
   seafile = handleTest ./seafile.nix {};
   searx = handleTest ./searx.nix {};
+  seatd = handleTest ./seatd.nix {};
   service-runner = handleTest ./service-runner.nix {};
   sftpgo = runTest ./sftpgo.nix;
   sfxr-qt = handleTest ./sfxr-qt.nix {};
@@ -748,6 +754,7 @@ in {
   signal-desktop = handleTest ./signal-desktop.nix {};
   simple = handleTest ./simple.nix {};
   sing-box = handleTest ./sing-box.nix {};
+  slimserver = handleTest ./slimserver.nix {};
   slurm = handleTest ./slurm.nix {};
   smokeping = handleTest ./smokeping.nix {};
   snapcast = handleTest ./snapcast.nix {};
@@ -920,6 +927,7 @@ in {
   xmonad-xdg-autostart = handleTest ./xmonad-xdg-autostart.nix {};
   xpadneo = handleTest ./xpadneo.nix {};
   xrdp = handleTest ./xrdp.nix {};
+  xscreensaver = handleTest ./xscreensaver.nix {};
   xss-lock = handleTest ./xss-lock.nix {};
   xterm = handleTest ./xterm.nix {};
   xxh = handleTest ./xxh.nix {};
diff --git a/nixos/tests/amazon-ssm-agent.nix b/nixos/tests/amazon-ssm-agent.nix
new file mode 100644
index 0000000000000..957e9e0e02c5d
--- /dev/null
+++ b/nixos/tests/amazon-ssm-agent.nix
@@ -0,0 +1,17 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "amazon-ssm-agent";
+  meta.maintainers = [ lib.maintainers.anthonyroussel ];
+
+  nodes.machine = { config, pkgs, ... }: {
+    services.amazon-ssm-agent.enable = true;
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_file("/etc/amazon/ssm/seelog.xml")
+    machine.wait_for_file("/etc/amazon/ssm/amazon-ssm-agent.json")
+
+    machine.wait_for_unit("amazon-ssm-agent.service")
+  '';
+})
diff --git a/nixos/tests/archi.nix b/nixos/tests/archi.nix
new file mode 100644
index 0000000000000..59f2e940c0050
--- /dev/null
+++ b/nixos/tests/archi.nix
@@ -0,0 +1,31 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "archi";
+  meta.maintainers = with lib.maintainers; [ paumr ];
+
+  nodes.machine = { pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    environment.systemPackages = with pkgs; [ archi ];
+  };
+
+  enableOCR = true;
+
+  testScript = ''
+    machine.wait_for_x()
+
+    with subtest("createEmptyModel via CLI"):
+         machine.succeed("Archi -application com.archimatetool.commandline.app -consoleLog -nosplash --createEmptyModel --saveModel smoke.archimate")
+         machine.copy_from_vm("smoke.archimate", "")
+
+    with subtest("UI smoketest"):
+         machine.succeed("DISPLAY=:0 Archi --createEmptyModel >&2 &")
+         machine.wait_for_window("Archi")
+
+         # wait till main UI is open
+         machine.wait_for_text("Welcome to Archi")
+
+         machine.screenshot("welcome-screen")
+  '';
+})
diff --git a/nixos/tests/centrifugo.nix b/nixos/tests/centrifugo.nix
new file mode 100644
index 0000000000000..45c2904f5585f
--- /dev/null
+++ b/nixos/tests/centrifugo.nix
@@ -0,0 +1,80 @@
+let
+  redisPort = 6379;
+  centrifugoPort = 8080;
+  nodes = [
+    "centrifugo1"
+    "centrifugo2"
+    "centrifugo3"
+  ];
+in
+{ lib, ... }: {
+  name = "centrifugo";
+  meta.maintainers = [ lib.maintainers.tie ];
+
+  nodes = lib.listToAttrs (lib.imap0
+    (index: name: {
+      inherit name;
+      value = { config, ... }: {
+        services.centrifugo = {
+          enable = true;
+          settings = {
+            inherit name;
+            port = centrifugoPort;
+            # See https://centrifugal.dev/docs/server/engines#redis-sharding
+            engine = "redis";
+            # Connect to local Redis shard via Unix socket.
+            redis_address =
+              let
+                otherNodes = lib.take index nodes ++ lib.drop (index + 1) nodes;
+              in
+              map (name: "${name}:${toString redisPort}") otherNodes ++ [
+                "unix://${config.services.redis.servers.centrifugo.unixSocket}"
+              ];
+            usage_stats_disable = true;
+            api_insecure = true;
+          };
+          extraGroups = [
+            config.services.redis.servers.centrifugo.user
+          ];
+        };
+        services.redis.servers.centrifugo = {
+          enable = true;
+          bind = null; # all interfaces
+          port = redisPort;
+          openFirewall = true;
+          settings.protected-mode = false;
+        };
+      };
+    })
+    nodes);
+
+  testScript = ''
+    import json
+
+    redisPort = ${toString redisPort}
+    centrifugoPort = ${toString centrifugoPort}
+
+    start_all()
+
+    for machine in machines:
+      machine.wait_for_unit("redis-centrifugo.service")
+      machine.wait_for_open_port(redisPort)
+
+    for machine in machines:
+      machine.wait_for_unit("centrifugo.service")
+      machine.wait_for_open_port(centrifugoPort)
+
+    # See https://centrifugal.dev/docs/server/server_api#info
+    def list_nodes(machine):
+      curl = "curl --fail-with-body --silent"
+      body = "{}"
+      resp = json.loads(machine.succeed(f"{curl} -d '{body}' http://localhost:{centrifugoPort}/api/info"))
+      return resp["result"]["nodes"]
+    machineNames = {m.name for m in machines}
+    for machine in machines:
+      nodes = list_nodes(machine)
+      assert len(nodes) == len(machines)
+      nodeNames = {n['name'] for n in nodes}
+      assert machineNames == nodeNames
+  '';
+}
diff --git a/nixos/tests/code-server.nix b/nixos/tests/code-server.nix
deleted file mode 100644
index 7d523dfc617e3..0000000000000
--- a/nixos/tests/code-server.nix
+++ /dev/null
@@ -1,22 +0,0 @@
-import ./make-test-python.nix ({pkgs, lib, ...}:
-{
-  name = "code-server";
-
-  nodes = {
-    machine = {pkgs, ...}: {
-      services.code-server = {
-        enable = true;
-        auth = "none";
-      };
-    };
-  };
-
-  testScript = ''
-    start_all()
-    machine.wait_for_unit("code-server.service")
-    machine.wait_for_open_port(4444)
-    machine.succeed("curl -k --fail http://localhost:4444", timeout=10)
-  '';
-
-  meta.maintainers = [ lib.maintainers.drupol ];
-})
diff --git a/nixos/tests/containers-ip.nix b/nixos/tests/containers-ip.nix
index ecead5c22f756..ecff99a3f0c25 100644
--- a/nixos/tests/containers-ip.nix
+++ b/nixos/tests/containers-ip.nix
@@ -19,10 +19,7 @@ in import ./make-test-python.nix ({ pkgs, lib, ... }: {
 
   nodes.machine =
     { pkgs, ... }: {
-      imports = [ ../modules/installer/cd-dvd/channel.nix ];
-      virtualisation = {
-        writableStore = true;
-      };
+      virtualisation.writableStore = true;
 
       containers.webserver4 = webserverFor "10.231.136.1" "10.231.136.2";
       containers.webserver6 = webserverFor "fc00::2" "fc00::1";
diff --git a/nixos/tests/convos.nix b/nixos/tests/convos.nix
index 8fe5892da9e52..06d8d30fcb148 100644
--- a/nixos/tests/convos.nix
+++ b/nixos/tests/convos.nix
@@ -22,7 +22,6 @@ in
   testScript = ''
     machine.wait_for_unit("convos")
     machine.wait_for_open_port(${toString port})
-    machine.succeed("journalctl -u convos | grep -q 'application available at.*${toString port}'")
     machine.succeed("curl -f http://localhost:${toString port}/")
   '';
 })
diff --git a/nixos/tests/dex-oidc.nix b/nixos/tests/dex-oidc.nix
index 37275a97ef0fb..e54ae18ca9373 100644
--- a/nixos/tests/dex-oidc.nix
+++ b/nixos/tests/dex-oidc.nix
@@ -49,7 +49,7 @@ import ./make-test-python.nix ({ lib, ... }: {
       ensureUsers = [
         {
           name = "dex";
-          ensurePermissions = { "DATABASE dex" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/tests/dnscrypt-wrapper/default.nix b/nixos/tests/dnscrypt-wrapper/default.nix
index 1c05376e097b3..1a794931dc500 100644
--- a/nixos/tests/dnscrypt-wrapper/default.nix
+++ b/nixos/tests/dnscrypt-wrapper/default.nix
@@ -1,5 +1,15 @@
+
 { lib, pkgs, ... }:
 
+let
+  snakeoil = import ../common/acme/server/snakeoil-certs.nix;
+
+  hosts = lib.mkForce
+   { "fd::a" = [ "server" snakeoil.domain ];
+     "fd::b" = [ "client" ];
+   };
+in
+
 {
   name = "dnscrypt-wrapper";
   meta = with pkgs.lib.maintainers; {
@@ -7,59 +17,122 @@
   };
 
   nodes = {
-    server = { lib, ... }:
-      { services.dnscrypt-wrapper = with builtins;
+    server = {
+      networking.hosts = hosts;
+      networking.interfaces.eth1.ipv6.addresses = lib.singleton
+        { address = "fd::a"; prefixLength = 64; };
+
+        services.dnscrypt-wrapper =
           { enable = true;
-            address = "192.168.1.1";
+            address = "[::]";
+            port = 5353;
             keys.expiration = 5; # days
             keys.checkInterval = 2;  # min
             # The keypair was generated by the command:
             # dnscrypt-wrapper --gen-provider-keypair \
             #  --provider-name=2.dnscrypt-cert.server \
-            #  --ext-address=192.168.1.1:5353
-            providerKey.public = toFile "public.key" (readFile ./public.key);
-            providerKey.secret = toFile "secret.key" (readFile ./secret.key);
+            providerKey.public = "${./public.key}";
+            providerKey.secret = "${./secret.key}";
+          };
+
+        # nameserver
+        services.bind.enable = true;
+        services.bind.zones = lib.singleton
+          { name = ".";
+            master = true;
+            file = pkgs.writeText "root.zone" ''
+              $TTL 3600
+              . IN SOA example.org. admin.example.org. ( 1 3h 1h 1w 1d )
+              . IN NS example.org.
+              example.org. IN AAAA 2001:db8::1
+            '';
+          };
+
+        # webserver
+        services.nginx.enable = true;
+        services.nginx.virtualHosts.${snakeoil.domain} =
+          { onlySSL = true;
+            listenAddresses = [ "localhost" ];
+            sslCertificate = snakeoil.${snakeoil.domain}.cert;
+            sslCertificateKey = snakeoil.${snakeoil.domain}.key;
+            locations."/ip".extraConfig = ''
+              default_type text/plain;
+              return 200 "Ciao $remote_addr!\n";
+            '';
           };
-        services.tinydns.enable = true;
-        services.tinydns.data = ''
-          ..:192.168.1.1:a
-          +it.works:1.2.3.4
-        '';
-        networking.firewall.allowedUDPPorts = [ 5353 ];
-        networking.firewall.allowedTCPPorts = [ 5353 ];
-        networking.interfaces.eth1.ipv4.addresses = lib.mkForce
-          [ { address = "192.168.1.1"; prefixLength = 24; } ];
+
+        # demultiplex HTTP and DNS from port 443
+        services.sslh =
+          { enable = true;
+            method = "ev";
+            settings.transparent = true;
+            settings.listen = lib.mkForce
+              [ { host = "server"; port = "443"; is_udp = false; }
+                { host = "server"; port = "443"; is_udp = true; }
+              ];
+            settings.protocols =
+              [ # Send TLS to webserver (TCP)
+                { name = "tls"; host= "localhost"; port= "443"; }
+                # Send DNSCrypt to dnscrypt-wrapper (TCP or UDP)
+                { name = "anyprot"; host = "localhost"; port = "5353"; }
+                { name = "anyprot"; host = "localhost"; port = "5353"; is_udp = true;}
+              ];
+          };
+
+        networking.firewall.allowedTCPPorts = [ 443 ];
+        networking.firewall.allowedUDPPorts = [ 443 ];
       };
 
-    client = { lib, ... }:
-      { services.dnscrypt-proxy2.enable = true;
-        services.dnscrypt-proxy2.upstreamDefaults = false;
-        services.dnscrypt-proxy2.settings = {
-          server_names = [ "server" ];
-          static.server.stamp = "sdns://AQAAAAAAAAAAEDE5Mi4xNjguMS4xOjUzNTMgFEHYOv0SCKSuqR5CDYa7-58cCBuXO2_5uTSVU9wNQF0WMi5kbnNjcnlwdC1jZXJ0LnNlcnZlcg";
+    client = {
+      networking.hosts = hosts;
+      networking.interfaces.eth1.ipv6.addresses = lib.singleton
+        { address = "fd::b"; prefixLength = 64; };
+
+      services.dnscrypt-proxy2.enable = true;
+      services.dnscrypt-proxy2.upstreamDefaults = false;
+      services.dnscrypt-proxy2.settings =
+        { server_names = [ "server" ];
+          listen_addresses = [ "[::1]:53" ];
+          cache = false;
+          # Computed using https://dnscrypt.info/stamps/
+          static.server.stamp =
+            "sdns://AQAAAAAAAAAADzE5Mi4xNjguMS4yOjQ0MyAUQdg6"
+            +"_RIIpK6pHkINhrv7nxwIG5c7b_m5NJVT3A1AXRYyLmRuc2NyeXB0LWNlcnQuc2VydmVy";
         };
-        networking.nameservers = [ "127.0.0.1" ];
-        networking.interfaces.eth1.ipv4.addresses = lib.mkForce
-          [ { address = "192.168.1.2"; prefixLength = 24; } ];
-      };
+      networking.nameservers = [ "::1" ];
+      security.pki.certificateFiles = [ snakeoil.ca.cert ];
+    };
 
   };
 
   testScript = ''
-    start_all()
-
     with subtest("The server can generate the ephemeral keypair"):
         server.wait_for_unit("dnscrypt-wrapper")
         server.wait_for_file("/var/lib/dnscrypt-wrapper/2.dnscrypt-cert.server.key")
         server.wait_for_file("/var/lib/dnscrypt-wrapper/2.dnscrypt-cert.server.crt")
         almost_expiration = server.succeed("date --date '4days 23 hours 56min'").strip()
 
-    with subtest("The client can connect to the server"):
-        server.wait_for_unit("tinydns")
-        client.wait_for_unit("dnscrypt-proxy2")
-        assert "1.2.3.4" in client.wait_until_succeeds(
-            "host it.works"
-        ), "The IP address of 'it.works' does not match 1.2.3.4"
+    with subtest("The DNSCrypt client can connect to the server"):
+        server.wait_for_unit("sslh")
+        client.wait_until_succeeds("journalctl -u dnscrypt-proxy2 --grep '\[server\] OK'")
+
+    with subtest("HTTP client can connect to the server"):
+        server.wait_for_unit("nginx")
+        client.succeed("curl -s --fail https://${snakeoil.domain}/ip | grep -q fd::b")
+
+    with subtest("DNS queries over UDP are working"):
+        server.wait_for_unit("bind")
+        client.wait_for_open_port(53)
+        assert "2001:db8::1" in client.wait_until_succeeds(
+            "host -U example.org"
+        ), "The IP address of 'example.org' does not match 2001:db8::1"
+
+    with subtest("DNS queries over TCP are working"):
+        server.wait_for_unit("bind")
+        client.wait_for_open_port(53)
+        assert "2001:db8::1" in client.wait_until_succeeds(
+            "host -T example.org"
+        ), "The IP address of 'example.org' does not match 2001:db8::1"
 
     with subtest("The server rotates the ephemeral keys"):
         # advance time by a little less than 5 days
@@ -68,7 +141,8 @@
         server.wait_for_file("/var/lib/dnscrypt-wrapper/oldkeys")
 
     with subtest("The client can still connect to the server"):
-        server.wait_for_unit("dnscrypt-wrapper")
-        client.succeed("host it.works")
+        client.systemctl("restart dnscrypt-proxy2")
+        client.wait_until_succeeds("host -T example.org")
+        client.wait_until_succeeds("host -U example.org")
   '';
 }
diff --git a/nixos/tests/elk.nix b/nixos/tests/elk.nix
index 0122bc440361d..900ea6320100f 100644
--- a/nixos/tests/elk.nix
+++ b/nixos/tests/elk.nix
@@ -119,11 +119,6 @@ let
                 package = elk.elasticsearch;
               };
 
-              kibana = {
-                enable = true;
-                package = elk.kibana;
-              };
-
               elasticsearch-curator = {
                 enable = true;
                 actionYAML = ''
@@ -217,13 +212,6 @@ let
           one.wait_until_succeeds("cat /tmp/logstash.out | grep flowers")
           one.wait_until_succeeds("cat /tmp/logstash.out | grep -v dragons")
 
-      with subtest("Kibana is healthy"):
-          one.wait_for_unit("kibana.service")
-          one.wait_until_succeeds(
-              "curl --silent --show-error --fail-with-body 'http://localhost:5601/api/status'"
-              + " | jq -es 'if . == [] then null else .[] | .status.overall.state == \"green\" end'"
-          )
-
       with subtest("Metricbeat is running"):
           one.wait_for_unit("metricbeat.service")
 
@@ -274,7 +262,6 @@ in {
   #   name = "elk-7";
   #   elasticsearch = pkgs.elasticsearch7-oss;
   #   logstash      = pkgs.logstash7-oss;
-  #   kibana        = pkgs.kibana7-oss;
   #   filebeat      = pkgs.filebeat7;
   #   metricbeat    = pkgs.metricbeat7;
   # };
@@ -282,7 +269,6 @@ in {
     ELK-7 = mkElkTest "elk-7" {
       elasticsearch = pkgs.elasticsearch7;
       logstash      = pkgs.logstash7;
-      kibana        = pkgs.kibana7;
       filebeat      = pkgs.filebeat7;
       metricbeat    = pkgs.metricbeat7;
     };
diff --git a/nixos/tests/ferretdb.nix b/nixos/tests/ferretdb.nix
index 9ad7397ade80c..7251198af77dc 100644
--- a/nixos/tests/ferretdb.nix
+++ b/nixos/tests/ferretdb.nix
@@ -39,7 +39,7 @@ with import ../lib/testing-python.nix { inherit system; };
             ensureDatabases = [ "ferretdb" ];
             ensureUsers = [{
               name = "ferretdb";
-              ensurePermissions."DATABASE ferretdb" = "ALL PRIVILEGES";
+              ensureDBOwnership = true;
             }];
           };
 
diff --git a/nixos/tests/freshrss-pgsql.nix b/nixos/tests/freshrss-pgsql.nix
index 055bd51ed43d7..c685f4a8159b8 100644
--- a/nixos/tests/freshrss-pgsql.nix
+++ b/nixos/tests/freshrss-pgsql.nix
@@ -22,9 +22,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
       ensureUsers = [
         {
           name = "freshrss";
-          ensurePermissions = {
-            "DATABASE freshrss" = "ALL PRIVILEGES";
-          };
+          ensureDBOwnership = true;
         }
       ];
       initialScript = pkgs.writeText "postgresql-password" ''
diff --git a/nixos/tests/gnome-extensions.nix b/nixos/tests/gnome-extensions.nix
new file mode 100644
index 0000000000000..2faff9a4a80d5
--- /dev/null
+++ b/nixos/tests/gnome-extensions.nix
@@ -0,0 +1,151 @@
+import ./make-test-python.nix (
+{ pkgs, lib, ...}:
+{
+  name = "gnome-extensions";
+  meta.maintainers = [ lib.maintainers.piegames ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    {
+      imports = [ ./common/user-account.nix ];
+
+      # Install all extensions
+      environment.systemPackages = lib.filter (e: e ? extensionUuid) (lib.attrValues pkgs.gnomeExtensions);
+
+      # Some extensions are broken, but that's kind of the point of a testing VM
+      nixpkgs.config.allowBroken = true;
+      # There are some aliases which throw exceptions; ignore them.
+      # Also prevent duplicate extensions under different names.
+      nixpkgs.config.allowAliases = false;
+
+      # Configure GDM
+      services.xserver.enable = true;
+      services.xserver.displayManager = {
+        gdm = {
+          enable = true;
+          debug = true;
+          wayland = true;
+        };
+        autoLogin = {
+          enable = true;
+          user = "alice";
+        };
+      };
+
+      # Configure Gnome
+      services.xserver.desktopManager.gnome.enable = true;
+      services.xserver.desktopManager.gnome.debug = true;
+
+      systemd.user.services = {
+        "org.gnome.Shell@wayland" = {
+          serviceConfig = {
+            ExecStart = [
+              # Clear the list before overriding it.
+              ""
+              # Eval API is now internal so Shell needs to run in unsafe mode.
+              # TODO: improve test driver so that it supports openqa-like manipulation
+              # that would allow us to drop this mess.
+              "${pkgs.gnome.gnome-shell}/bin/gnome-shell --unsafe-mode"
+            ];
+          };
+        };
+      };
+
+    };
+
+  testScript = { nodes, ... }: let
+    # Keep line widths somewhat manageable
+    user = nodes.machine.users.users.alice;
+    uid = toString user.uid;
+    bus = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${uid}/bus";
+    # Run a command in the appropriate user environment
+    run = command: "su - ${user.name} -c '${bus} ${command}'";
+
+    # Call javascript in gnome shell, returns a tuple (success, output), where
+    # `success` is true if the dbus call was successful and output is what the
+    # javascript evaluates to.
+    eval = command: run "gdbus call --session -d org.gnome.Shell -o /org/gnome/Shell -m org.gnome.Shell.Eval ${command}";
+
+    # False when startup is done
+    startingUp = eval "Main.layoutManager._startingUp";
+
+    # Extensions to keep always enabled together
+    # Those are extensions that are usually always on for many users, and that we expect to work
+    # well together with most others without conflicts
+    alwaysOnExtensions = map (name: pkgs.gnomeExtensions.${name}.extensionUuid) [
+      "applications-menu"
+      "user-themes"
+    ];
+
+    # Extensions to enable and disable individually
+    # Extensions like dash-to-dock and dash-to-panel cannot be enabled at the same time.
+    testExtensions = map (name: pkgs.gnomeExtensions.${name}.extensionUuid) [
+      "appindicator"
+      "dash-to-dock"
+      "dash-to-panel"
+      "ddterm"
+      "emoji-selector"
+      "gsconnect"
+      "system-monitor"
+      "desktop-icons-ng-ding"
+      "workspace-indicator"
+      "vitals"
+    ];
+  in
+  ''
+    with subtest("Login to GNOME with GDM"):
+        # wait for gdm to start
+        machine.wait_for_unit("display-manager.service")
+        # wait for the wayland server
+        machine.wait_for_file("/run/user/${uid}/wayland-0")
+        # wait for alice to be logged in
+        machine.wait_for_unit("default.target", "${user.name}")
+        # check that logging in has given the user ownership of devices
+        assert "alice" in machine.succeed("getfacl -p /dev/snd/timer")
+
+    with subtest("Wait for GNOME Shell"):
+        # correct output should be (true, 'false')
+        machine.wait_until_succeeds(
+            "${startingUp} | grep -q 'true,..false'"
+        )
+
+        # Close the Activities view so that Shell can correctly track the focused window.
+        machine.send_key("esc")
+        # # Disable extension version validation (only use for manual testing)
+        # machine.succeed(
+        #   "${run "gsettings set org.gnome.shell disable-extension-version-validation true"}"
+        # )
+
+    # Assert that some extension is in a specific state
+    def checkState(target, extension):
+        state = machine.succeed(
+            f"${run "gnome-extensions info {extension}"} | grep '^  State: .*$'"
+        )
+        assert target in state, f"{state} instead of {target}"
+
+    def checkExtension(extension, disable):
+        with subtest(f"Enable extension '{extension}'"):
+            # Check that the extension is properly initialized; skip out of date ones
+            state = machine.succeed(
+                f"${run "gnome-extensions info {extension}"} | grep '^  State: .*$'"
+            )
+            if "OUT OF DATE" in state:
+                machine.log(f"Extension {extension} will be skipped because out of date")
+                return
+
+            assert "INITIALIZED" in state, f"{state} instead of INITIALIZED"
+
+            # Enable and optionally disable
+
+            machine.succeed(f"${run "gnome-extensions enable {extension}"}")
+            checkState("ENABLED", extension)
+
+            if disable:
+                machine.succeed(f"${run "gnome-extensions disable {extension}"}")
+                checkState("DISABLED", extension)
+    ''
+    + lib.concatLines (map (e: ''checkExtension("${e}", False)'') alwaysOnExtensions)
+    + lib.concatLines (map (e: ''checkExtension("${e}", True)'') testExtensions)
+  ;
+}
+)
diff --git a/nixos/tests/gnome-xorg.nix b/nixos/tests/gnome-xorg.nix
index 7762fff5c3a25..6ca700edcac38 100644
--- a/nixos/tests/gnome-xorg.nix
+++ b/nixos/tests/gnome-xorg.nix
@@ -5,7 +5,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
   };
 
   nodes.machine = { nodes, ... }: let
-    user = nodes.machine.config.users.users.alice;
+    user = nodes.machine.users.users.alice;
   in
 
     { imports = [ ./common/user-account.nix ];
@@ -43,28 +43,28 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
     };
 
   testScript = { nodes, ... }: let
-    user = nodes.machine.config.users.users.alice;
+    user = nodes.machine.users.users.alice;
     uid = toString user.uid;
     bus = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${uid}/bus";
     xauthority = "/run/user/${uid}/gdm/Xauthority";
     display = "DISPLAY=:0.0";
     env = "${bus} XAUTHORITY=${xauthority} ${display}";
-    gdbus = "${env} gdbus";
-    su = command: "su - ${user.name} -c '${env} ${command}'";
+    # Run a command in the appropriate user environment
+    run = command: "su - ${user.name} -c '${bus} ${command}'";
 
     # Call javascript in gnome shell, returns a tuple (success, output), where
     # `success` is true if the dbus call was successful and output is what the
     # javascript evaluates to.
-    eval = "call --session -d org.gnome.Shell -o /org/gnome/Shell -m org.gnome.Shell.Eval";
+    eval = command: run "gdbus call --session -d org.gnome.Shell -o /org/gnome/Shell -m org.gnome.Shell.Eval ${command}";
 
     # False when startup is done
-    startingUp = su "${gdbus} ${eval} Main.layoutManager._startingUp";
+    startingUp = eval "Main.layoutManager._startingUp";
 
     # Start Console
-    launchConsole = su "${bus} gapplication launch org.gnome.Console";
+    launchConsole = run "gapplication launch org.gnome.Console";
 
     # Hopefully Console's wm class
-    wmClass = su "${gdbus} ${eval} global.display.focus_window.wm_class";
+    wmClass = eval "global.display.focus_window.wm_class";
   in ''
       with subtest("Login to GNOME Xorg with GDM"):
           machine.wait_for_x()
diff --git a/nixos/tests/gnome.nix b/nixos/tests/gnome.nix
index 448a3350240c7..91182790cb248 100644
--- a/nixos/tests/gnome.nix
+++ b/nixos/tests/gnome.nix
@@ -40,25 +40,25 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
 
   testScript = { nodes, ... }: let
     # Keep line widths somewhat manageable
-    user = nodes.machine.config.users.users.alice;
+    user = nodes.machine.users.users.alice;
     uid = toString user.uid;
     bus = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${uid}/bus";
-    gdbus = "${bus} gdbus";
-    su = command: "su - ${user.name} -c '${command}'";
+    # Run a command in the appropriate user environment
+    run = command: "su - ${user.name} -c '${bus} ${command}'";
 
     # Call javascript in gnome shell, returns a tuple (success, output), where
     # `success` is true if the dbus call was successful and output is what the
     # javascript evaluates to.
-    eval = "call --session -d org.gnome.Shell -o /org/gnome/Shell -m org.gnome.Shell.Eval";
+    eval = command: run "gdbus call --session -d org.gnome.Shell -o /org/gnome/Shell -m org.gnome.Shell.Eval ${command}";
 
     # False when startup is done
-    startingUp = su "${gdbus} ${eval} Main.layoutManager._startingUp";
+    startingUp = eval "Main.layoutManager._startingUp";
 
     # Start Console
-    launchConsole = su "${bus} gapplication launch org.gnome.Console";
+    launchConsole = run "gapplication launch org.gnome.Console";
 
     # Hopefully Console's wm class
-    wmClass = su "${gdbus} ${eval} global.display.focus_window.wm_class";
+    wmClass = eval "global.display.focus_window.wm_class";
   in ''
       with subtest("Login to GNOME with GDM"):
           # wait for gdm to start
diff --git a/nixos/tests/grafana/basic.nix b/nixos/tests/grafana/basic.nix
index 8bf4caad7fbfc..dd389bc8a3d1f 100644
--- a/nixos/tests/grafana/basic.nix
+++ b/nixos/tests/grafana/basic.nix
@@ -55,7 +55,7 @@ let
         ensureDatabases = [ "grafana" ];
         ensureUsers = [{
           name = "grafana";
-          ensurePermissions."DATABASE grafana" = "ALL PRIVILEGES";
+          ensureDBOwnership = true;
         }];
       };
       systemd.services.grafana.after = [ "postgresql.service" ];
diff --git a/nixos/tests/hockeypuck.nix b/nixos/tests/hockeypuck.nix
index 2b9dba8720aba..675d6b226ad20 100644
--- a/nixos/tests/hockeypuck.nix
+++ b/nixos/tests/hockeypuck.nix
@@ -35,7 +35,7 @@ in {
       ensureDatabases = [ "hockeypuck" ];
       ensureUsers = [{
         name = "hockeypuck";
-        ensurePermissions."DATABASE hockeypuck" = "ALL PRIVILEGES";
+        ensureDBOwnership = true;
       }];
     };
   };
diff --git a/nixos/tests/home-assistant.nix b/nixos/tests/home-assistant.nix
index b7deb95b2c194..e1588088ba198 100644
--- a/nixos/tests/home-assistant.nix
+++ b/nixos/tests/home-assistant.nix
@@ -9,13 +9,11 @@ in {
   nodes.hass = { pkgs, ... }: {
     services.postgresql = {
       enable = true;
-
-      # FIXME: hack for https://github.com/NixOS/nixpkgs/issues/216989
-      # Should be replaced with ensureUsers again when a solution for that is found
-      initialScript = pkgs.writeText "hass-setup-db.sql" ''
-        CREATE ROLE hass WITH LOGIN;
-        CREATE DATABASE hass WITH OWNER hass;
-      '';
+      ensureDatabases = [ "hass" ];
+      ensureUsers = [{
+        name = "hass";
+        ensureDBOwnership = true;
+      }];
     };
 
     services.home-assistant = {
@@ -43,6 +41,16 @@ in {
         psycopg2
       ];
 
+      # test loading custom components
+      customComponents = with pkgs.home-assistant-custom-components; [
+        prometheus-sensor
+      ];
+
+      # test loading lovelace modules
+      customLovelaceModules = with pkgs.home-assistant-custom-lovelace-modules; [
+        mini-graph-card
+      ];
+
       config = {
         homeassistant = {
           name = "Home";
@@ -114,6 +122,14 @@ in {
       inheritParentConfig = true;
       configuration.services.home-assistant.config.backup = {};
     };
+
+    specialisation.removeCustomThings = {
+      inheritParentConfig = true;
+      configuration.services.home-assistant = {
+        customComponents = lib.mkForce [];
+        customLovelaceModules = lib.mkForce [];
+      };
+    };
   };
 
   testScript = { nodes, ... }: let
@@ -161,6 +177,14 @@ in {
         hass.wait_for_open_port(8123)
         hass.succeed("curl --fail http://localhost:8123/lovelace")
 
+    with subtest("Check that custom components get installed"):
+        hass.succeed("test -f ${configDir}/custom_components/prometheus_sensor/manifest.json")
+        hass.wait_until_succeeds("journalctl -u home-assistant.service | grep -q 'We found a custom integration prometheus_sensor which has not been tested by Home Assistant'")
+
+    with subtest("Check that lovelace modules are referenced and fetchable"):
+        hass.succeed("grep -q 'mini-graph-card-bundle.js' '${configDir}/ui-lovelace.yaml'")
+        hass.succeed("curl --fail http://localhost:8123/local/nixos-lovelace-modules/mini-graph-card-bundle.js")
+
     with subtest("Check that optional dependencies are in the PYTHONPATH"):
         env = get_unit_property("Environment")
         python_path = env.split("PYTHONPATH=")[1].split()[0]
@@ -200,6 +224,13 @@ in {
         for domain in ["backup"]:
             assert f"Setup of domain {domain} took" in journal, f"{domain} setup missing"
 
+    with subtest("Check custom components and custom lovelace modules get removed"):
+        cursor = get_journal_cursor()
+        hass.succeed("${system}/specialisation/removeCustomThings/bin/switch-to-configuration test")
+        hass.fail("grep -q 'mini-graph-card-bundle.js' '${configDir}/ui-lovelace.yaml'")
+        hass.fail("test -f ${configDir}/custom_components/prometheus_sensor/manifest.json")
+        wait_for_homeassistant(cursor)
+
     with subtest("Check that no errors were logged"):
         hass.fail("journalctl -u home-assistant -o cat | grep -q ERROR")
 
diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix
index 1baa4396424fb..e9ec287498509 100644
--- a/nixos/tests/installer.nix
+++ b/nixos/tests/installer.nix
@@ -991,6 +991,68 @@ in {
     '';
   };
 
+  bcachefsLinuxTesting = makeInstallerTest "bcachefs-linux-testing" {
+    extraInstallerConfig = {
+      imports = [ no-zfs-module ];
+
+      boot = {
+        supportedFilesystems = [ "bcachefs" ];
+        kernelPackages = pkgs.linuxPackages_testing;
+      };
+    };
+
+    extraConfig = ''
+      boot.kernelPackages = pkgs.linuxPackages_testing;
+    '';
+
+    createPartitions = ''
+      machine.succeed(
+        "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+        + " mkpart primary ext2 1M 100MB"          # /boot
+        + " mkpart primary linux-swap 100M 1024M"  # swap
+        + " mkpart primary 1024M -1s",             # /
+        "udevadm settle",
+        "mkswap /dev/vda2 -L swap",
+        "swapon -L swap",
+        "mkfs.bcachefs -L root /dev/vda3",
+        "mount -t bcachefs /dev/vda3 /mnt",
+        "mkfs.ext3 -L boot /dev/vda1",
+        "mkdir -p /mnt/boot",
+        "mount /dev/vda1 /mnt/boot",
+      )
+    '';
+  };
+
+  bcachefsUpgradeToLinuxTesting = makeInstallerTest "bcachefs-upgrade-to-linux-testing" {
+    extraInstallerConfig = {
+      imports = [ no-zfs-module ];
+      boot.supportedFilesystems = [ "bcachefs" ];
+      # We don't have network access in the VM, we need this for `nixos-install`
+      system.extraDependencies = [ pkgs.linux_testing ];
+    };
+
+    extraConfig = ''
+      boot.kernelPackages = pkgs.linuxPackages_testing;
+    '';
+
+    createPartitions = ''
+      machine.succeed(
+        "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+        + " mkpart primary ext2 1M 100MB"          # /boot
+        + " mkpart primary linux-swap 100M 1024M"  # swap
+        + " mkpart primary 1024M -1s",             # /
+        "udevadm settle",
+        "mkswap /dev/vda2 -L swap",
+        "swapon -L swap",
+        "mkfs.bcachefs -L root /dev/vda3",
+        "mount -t bcachefs /dev/vda3 /mnt",
+        "mkfs.ext3 -L boot /dev/vda1",
+        "mkdir -p /mnt/boot",
+        "mount /dev/vda1 /mnt/boot",
+      )
+    '';
+  };
+
   # Test using labels to identify volumes in grub
   simpleLabels = makeInstallerTest "simpleLabels" {
     createPartitions = ''
diff --git a/nixos/tests/invidious.nix b/nixos/tests/invidious.nix
index 582d1550fff1a..701e8e5e7a3fc 100644
--- a/nixos/tests/invidious.nix
+++ b/nixos/tests/invidious.nix
@@ -44,8 +44,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
             enable = true;
             initialScript = pkgs.writeText "init-postgres-with-password" ''
               CREATE USER kemal WITH PASSWORD 'correct horse battery staple';
-              CREATE DATABASE invidious;
-              GRANT ALL PRIVILEGES ON DATABASE invidious TO kemal;
+              CREATE DATABASE invidious OWNER kemal;
             '';
           };
       };
diff --git a/nixos/tests/kafka.nix b/nixos/tests/kafka.nix
index 864253fd8c73b..f4f9827ab7b5f 100644
--- a/nixos/tests/kafka.nix
+++ b/nixos/tests/kafka.nix
@@ -6,13 +6,62 @@
 with pkgs.lib;
 
 let
-  makeKafkaTest = name: kafkaPackage: (import ./make-test-python.nix ({
+  makeKafkaTest = name: { kafkaPackage, mode ? "zookeeper" }: (import ./make-test-python.nix ({
     inherit name;
     meta = with pkgs.lib.maintainers; {
       maintainers = [ nequissimus ];
     };
 
     nodes = {
+      kafka = { ... }: {
+        services.apache-kafka = mkMerge [
+          ({
+            enable = true;
+            package = kafkaPackage;
+            settings = {
+              "offsets.topic.replication.factor" = 1;
+              "log.dirs" = [
+                "/var/lib/kafka/logdir1"
+                "/var/lib/kafka/logdir2"
+              ];
+            };
+          })
+          (mkIf (mode == "zookeeper") {
+            settings = {
+              "zookeeper.session.timeout.ms" = 600000;
+              "zookeeper.connect" = [ "zookeeper1:2181" ];
+            };
+          })
+          (mkIf (mode == "kraft") {
+            clusterId = "ak2fIHr4S8WWarOF_ODD0g";
+            formatLogDirs = true;
+            settings = {
+              "node.id" = 1;
+              "process.roles" = [
+                "broker"
+                "controller"
+              ];
+              "listeners" = [
+                "PLAINTEXT://:9092"
+                "CONTROLLER://:9093"
+              ];
+              "listener.security.protocol.map" = [
+                "PLAINTEXT:PLAINTEXT"
+                "CONTROLLER:PLAINTEXT"
+              ];
+              "controller.quorum.voters" = [
+                "1@kafka:9093"
+              ];
+              "controller.listener.names" = [ "CONTROLLER" ];
+            };
+          })
+        ];
+
+        networking.firewall.allowedTCPPorts = [ 9092 9093 ];
+        # i686 tests: qemu-system-i386 can simulate max 2047MB RAM (not 2048)
+        virtualisation.memorySize = 2047;
+      };
+    } // optionalAttrs (mode == "zookeeper") {
       zookeeper1 = { ... }: {
         services.zookeeper = {
           enable = true;
@@ -20,29 +69,16 @@ let
 
         networking.firewall.allowedTCPPorts = [ 2181 ];
       };
-      kafka = { ... }: {
-        services.apache-kafka = {
-          enable = true;
-          extraProperties = ''
-            offsets.topic.replication.factor = 1
-            zookeeper.session.timeout.ms = 600000
-          '';
-          package = kafkaPackage;
-          zookeeper = "zookeeper1:2181";
-        };
-
-        networking.firewall.allowedTCPPorts = [ 9092 ];
-        # i686 tests: qemu-system-i386 can simulate max 2047MB RAM (not 2048)
-        virtualisation.memorySize = 2047;
-      };
     };
 
     testScript = ''
       start_all()
 
+      ${optionalString (mode == "zookeeper") ''
       zookeeper1.wait_for_unit("default.target")
       zookeeper1.wait_for_unit("zookeeper.service")
       zookeeper1.wait_for_open_port(2181)
+      ''}
 
       kafka.wait_for_unit("default.target")
       kafka.wait_for_unit("apache-kafka.service")
@@ -67,12 +103,13 @@ let
   }) { inherit system; });
 
 in with pkgs; {
-  kafka_2_8  = makeKafkaTest "kafka_2_8"  apacheKafka_2_8;
-  kafka_3_0  = makeKafkaTest "kafka_3_0"  apacheKafka_3_0;
-  kafka_3_1  = makeKafkaTest "kafka_3_1"  apacheKafka_3_1;
-  kafka_3_2  = makeKafkaTest "kafka_3_2"  apacheKafka_3_2;
-  kafka_3_3  = makeKafkaTest "kafka_3_3"  apacheKafka_3_3;
-  kafka_3_4  = makeKafkaTest "kafka_3_4"  apacheKafka_3_4;
-  kafka_3_5  = makeKafkaTest "kafka_3_5"  apacheKafka_3_5;
-  kafka  = makeKafkaTest "kafka"  apacheKafka;
+  kafka_2_8 = makeKafkaTest "kafka_2_8" { kafkaPackage = apacheKafka_2_8; };
+  kafka_3_0 = makeKafkaTest "kafka_3_0" { kafkaPackage = apacheKafka_3_0; };
+  kafka_3_1 = makeKafkaTest "kafka_3_1" { kafkaPackage = apacheKafka_3_1; };
+  kafka_3_2 = makeKafkaTest "kafka_3_2" { kafkaPackage = apacheKafka_3_2; };
+  kafka_3_3 = makeKafkaTest "kafka_3_3" { kafkaPackage = apacheKafka_3_3; };
+  kafka_3_4 = makeKafkaTest "kafka_3_4" { kafkaPackage = apacheKafka_3_4; };
+  kafka_3_5 = makeKafkaTest "kafka_3_5" { kafkaPackage = apacheKafka_3_5; };
+  kafka = makeKafkaTest "kafka" { kafkaPackage = apacheKafka; };
+  kafka_kraft = makeKafkaTest "kafka_kraft" { kafkaPackage = apacheKafka; mode = "kraft"; };
 }
diff --git a/nixos/tests/lxd/ui.nix b/nixos/tests/lxd/ui.nix
index 86cb30d8c2b68..ff651725ba705 100644
--- a/nixos/tests/lxd/ui.nix
+++ b/nixos/tests/lxd/ui.nix
@@ -11,9 +11,37 @@ import ../make-test-python.nix ({ pkgs, lib, ... }: {
       lxd.ui.enable = true;
     };
 
-    environment.systemPackages = [ pkgs.curl ];
+    environment.systemPackages =
+      let
+        seleniumScript = pkgs.writers.writePython3Bin "selenium-script"
+          {
+            libraries = with pkgs.python3Packages; [ selenium ];
+          } ''
+          from selenium import webdriver
+          from selenium.webdriver.common.by import By
+          from selenium.webdriver.firefox.options import Options
+          from selenium.webdriver.support.ui import WebDriverWait
+
+          options = Options()
+          options.add_argument("--headless")
+          service = webdriver.FirefoxService(executable_path="${lib.getExe pkgs.geckodriver}")  # noqa: E501
+
+          driver = webdriver.Firefox(options=options, service=service)
+          driver.implicitly_wait(10)
+          driver.get("https://localhost:8443/ui")
+
+          wait = WebDriverWait(driver, 60)
+
+          assert len(driver.find_elements(By.CLASS_NAME, "l-application")) > 0
+          assert len(driver.find_elements(By.CLASS_NAME, "l-navigation__drawer")) > 0
+
+          driver.close()
+        '';
+      in
+      with pkgs; [ curl firefox-unwrapped geckodriver seleniumScript ];
   };
 
+
   testScript = ''
     machine.wait_for_unit("sockets.target")
     machine.wait_for_unit("lxd.service")
@@ -31,5 +59,8 @@ import ../make-test-python.nix ({ pkgs, lib, ... }: {
 
     # Ensure the endpoint returns an HTML page with 'LXD UI' in the title
     machine.succeed("curl -kLs https://localhost:8443/ui | grep '<title>LXD UI</title>'")
+
+    # Ensure the application is actually rendered by the Javascript
+    machine.succeed("PYTHONUNBUFFERED=1 selenium-script")
   '';
 })
diff --git a/nixos/tests/ocsinventory-agent.nix b/nixos/tests/ocsinventory-agent.nix
new file mode 100644
index 0000000000000..67b0c8c911036
--- /dev/null
+++ b/nixos/tests/ocsinventory-agent.nix
@@ -0,0 +1,33 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "ocsinventory-agent";
+
+  nodes.machine = { pkgs, ... }: {
+    services.ocsinventory-agent = {
+      enable = true;
+      settings = {
+        debug = true;
+        local = "/var/lib/ocsinventory-agent/reports";
+        tag = "MY_INVENTORY_TAG";
+      };
+    };
+  };
+
+  testScript = ''
+    path = "/var/lib/ocsinventory-agent/reports"
+
+    # Run the agent to generate the inventory file in offline mode
+    start_all()
+    machine.succeed("mkdir -p {}".format(path))
+    machine.wait_for_unit("ocsinventory-agent.service")
+    machine.wait_until_succeeds("journalctl -u ocsinventory-agent.service | grep 'Inventory saved in'")
+
+    # Fetch the path to the generated inventory file
+    report_file = machine.succeed("find {}/*.ocs -type f | head -n1".format(path))
+
+    with subtest("Check the tag value"):
+      tag = machine.succeed(
+        "${pkgs.libxml2}/bin/xmllint --xpath 'string(/REQUEST/CONTENT/ACCOUNTINFO/KEYVALUE)' {}".format(report_file)
+      ).rstrip()
+      assert tag == "MY_INVENTORY_TAG", f"tag is not valid, was '{tag}'"
+  '';
+})
diff --git a/nixos/tests/paperless.nix b/nixos/tests/paperless.nix
index 22409e8992367..6a51cc522bdc5 100644
--- a/nixos/tests/paperless.nix
+++ b/nixos/tests/paperless.nix
@@ -17,7 +17,7 @@ import ./make-test-python.nix ({ lib, ... }: {
         ensureDatabases = [ "paperless" ];
         ensureUsers = [
           { name = config.services.paperless.user;
-            ensurePermissions = { "DATABASE \"paperless\"" = "ALL PRIVILEGES"; };
+            ensureDBOwnership = true;
           }
         ];
       };
diff --git a/nixos/tests/pgadmin4.nix b/nixos/tests/pgadmin4.nix
index cb8de87c9ee31..3ee7ed19fa1c5 100644
--- a/nixos/tests/pgadmin4.nix
+++ b/nixos/tests/pgadmin4.nix
@@ -19,14 +19,6 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
       authentication = ''
         host    all             all             localhost               trust
       '';
-      ensureUsers = [
-        {
-          name = "postgres";
-          ensurePermissions = {
-            "DATABASE \"postgres\"" = "ALL PRIVILEGES";
-          };
-        }
-      ];
     };
 
     services.pgadmin = {
diff --git a/nixos/tests/pgbouncer.nix b/nixos/tests/pgbouncer.nix
index 1e72327d42005..bb5afd35ee28f 100644
--- a/nixos/tests/pgbouncer.nix
+++ b/nixos/tests/pgbouncer.nix
@@ -17,7 +17,8 @@ in
 
       systemd.services.postgresql = {
         postStart = ''
-            ${pkgs.postgresql}/bin/psql -U postgres -c "ALTER ROLE testuser WITH LOGIN PASSWORD 'testpass'";
+          ${pkgs.postgresql}/bin/psql -U postgres -c "ALTER ROLE testuser WITH LOGIN PASSWORD 'testpass'";
+          ${pkgs.postgresql}/bin/psql -U postgres -c "ALTER DATABASE testdb OWNER TO testuser;";
         '';
       };
 
@@ -28,9 +29,6 @@ in
           ensureUsers = [
           {
             name = "testuser";
-            ensurePermissions = {
-              "DATABASE testdb" = "ALL PRIVILEGES";
-            };
           }];
           authentication = ''
             local testdb testuser scram-sha-256
@@ -40,7 +38,7 @@ in
         pgbouncer = {
           enable = true;
           listenAddress = "localhost";
-          databases = { testdb = "host=/run/postgresql/ port=5432 auth_user=testuser dbname=testdb"; };
+          databases = { test = "host=/run/postgresql/ port=5432 auth_user=testuser dbname=testdb"; };
           authType = "scram-sha-256";
           authFile = testAuthFile;
         };
@@ -55,7 +53,7 @@ in
 
     # Test if we can make a query through PgBouncer
     one.wait_until_succeeds(
-        "psql 'postgres://testuser:testpass@localhost:6432/testdb' -c 'SELECT 1;'"
+        "psql 'postgres://testuser:testpass@localhost:6432/test' -c 'SELECT 1;'"
     )
   '';
 })
diff --git a/nixos/tests/plantuml-server.nix b/nixos/tests/plantuml-server.nix
new file mode 100644
index 0000000000000..460c30919aecf
--- /dev/null
+++ b/nixos/tests/plantuml-server.nix
@@ -0,0 +1,20 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "plantuml-server";
+  meta.maintainers = with lib.maintainers; [ anthonyroussel ];
+
+  nodes.machine = { pkgs, ... }: {
+    environment.systemPackages = [ pkgs.curl ];
+    services.plantuml-server.enable = true;
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("plantuml-server.service")
+    machine.wait_for_open_port(8080)
+
+    with subtest("Generate chart"):
+      chart_id = machine.succeed("curl -sSf http://localhost:8080/plantuml/coder -d 'Alice -> Bob'")
+      machine.succeed("curl -sSf http://localhost:8080/plantuml/txt/{}".format(chart_id))
+  '';
+})
diff --git a/nixos/tests/plausible.nix b/nixos/tests/plausible.nix
index 9afd3db75de8a..9c26c509a5ab5 100644
--- a/nixos/tests/plausible.nix
+++ b/nixos/tests/plausible.nix
@@ -8,9 +8,6 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     virtualisation.memorySize = 4096;
     services.plausible = {
       enable = true;
-      releaseCookiePath = "${pkgs.runCommand "cookie" { } ''
-        ${pkgs.openssl}/bin/openssl rand -base64 64 >"$out"
-      ''}";
       adminUser = {
         email = "admin@example.org";
         passwordFile = "${pkgs.writeText "pwd" "foobar"}";
@@ -28,6 +25,10 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     machine.wait_for_unit("plausible.service")
     machine.wait_for_open_port(8000)
 
+    # Ensure that the software does not make not make the machine
+    # listen on any public interfaces by default.
+    machine.fail("ss -tlpn 'src = 0.0.0.0 or src = [::]' | grep LISTEN")
+
     machine.succeed("curl -f localhost:8000 >&2")
 
     machine.succeed("curl -f localhost:8000/js/script.js >&2")
diff --git a/nixos/tests/pleroma.nix b/nixos/tests/pleroma.nix
index 4f1aef854146e..08a01585f8778 100644
--- a/nixos/tests/pleroma.nix
+++ b/nixos/tests/pleroma.nix
@@ -164,9 +164,12 @@ import ./make-test-python.nix ({ pkgs, ... }:
   '';
 
   tls-cert = pkgs.runCommand "selfSignedCerts" { buildInputs = [ pkgs.openssl ]; } ''
-    openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -subj '/CN=pleroma.nixos.test' -days 36500
     mkdir -p $out
-    cp key.pem cert.pem $out
+    openssl req -x509 \
+      -subj '/CN=pleroma.nixos.test/' -days 49710 \
+      -addext 'subjectAltName = DNS:pleroma.nixos.test' \
+      -keyout "$out/key.pem" -newkey ed25519 \
+      -out "$out/cert.pem" -noenc
   '';
 
   hosts = nodes: ''
@@ -180,7 +183,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
       security.pki.certificateFiles = [ "${tls-cert}/cert.pem" ];
       networking.extraHosts = hosts nodes;
       environment.systemPackages = with pkgs; [
-        toot
+        pkgs.toot
         send-toot
       ];
     };
diff --git a/nixos/tests/powerdns-admin.nix b/nixos/tests/powerdns-admin.nix
index d7bacb24eec5d..d326d74a98261 100644
--- a/nixos/tests/powerdns-admin.nix
+++ b/nixos/tests/powerdns-admin.nix
@@ -87,9 +87,7 @@ let
           ensureUsers = [
             {
               name = "powerdnsadmin";
-              ensurePermissions = {
-                "DATABASE powerdnsadmin" = "ALL PRIVILEGES";
-              };
+              ensureDBOwnership = true;
             }
           ];
         };
diff --git a/nixos/tests/prometheus-exporters.nix b/nixos/tests/prometheus-exporters.nix
index 4bad56991cc61..7840130d4a364 100644
--- a/nixos/tests/prometheus-exporters.nix
+++ b/nixos/tests/prometheus-exporters.nix
@@ -257,6 +257,21 @@ let
       '';
     };
 
+    exportarr-sonarr = {
+      nodeName = "exportarr_sonarr";
+      exporterConfig = {
+        enable = true;
+        url = "http://127.0.0.1:8989";
+        # testing for real data is tricky, because the api key can not be preconfigured
+        apiKeyFile = pkgs.writeText "dummy-api-key" "eccff6a992bc2e4b88e46d064b26bb4e";
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-exportarr-sonarr-exporter.service")
+        wait_for_open_port(9707)
+        succeed("curl -sSf 'http://localhost:9707/metrics")
+      '';
+    };
+
     fastly = {
       exporterConfig = {
         enable = true;
@@ -1318,12 +1333,12 @@ let
         wait_for_open_port(9374)
         wait_until_succeeds(
             "curl -sSf localhost:9374/metrics | grep '{}' | grep -v ' 0$'".format(
-                'smokeping_requests_total{host="127.0.0.1",ip="127.0.0.1"} '
+                'smokeping_requests_total{host="127.0.0.1",ip="127.0.0.1",source=""} '
             )
         )
         wait_until_succeeds(
             "curl -sSf localhost:9374/metrics | grep '{}'".format(
-                'smokeping_response_ttl{host="127.0.0.1",ip="127.0.0.1"}'
+                'smokeping_response_ttl{host="127.0.0.1",ip="127.0.0.1",source=""}'
             )
         )
       '';
diff --git a/nixos/tests/prometheus.nix b/nixos/tests/prometheus.nix
index a075cfc1f1b72..0111273893775 100644
--- a/nixos/tests/prometheus.nix
+++ b/nixos/tests/prometheus.nix
@@ -3,6 +3,7 @@ let
   queryPort  =  9090;
   minioPort  =  9000;
   pushgwPort =  9091;
+  frontPort  =  9092;
 
   s3 = {
     accessKey = "BKIKJAA5BMMU2RHO6IBB";
@@ -152,10 +153,15 @@ in import ./make-test-python.nix {
       services.thanos.query = {
         enable = true;
         http-address = "0.0.0.0:${toString queryPort}";
-        store.addresses = [
+        endpoints = [
           "prometheus:${toString grpcPort}"
         ];
       };
+      services.thanos.query-frontend = {
+        enable = true;
+        http-address = "0.0.0.0:${toString frontPort}";
+        query-frontend.downstream-url = "http://127.0.0.1:${toString queryPort}";
+      };
     };
 
     store = { pkgs, ... }: {
@@ -178,7 +184,7 @@ in import ./make-test-python.nix {
       services.thanos.query = {
         enable = true;
         http-address = "0.0.0.0:${toString queryPort}";
-        store.addresses = [
+        endpoints = [
           "localhost:${toString grpcPort}"
         ];
       };
@@ -262,6 +268,10 @@ in import ./make-test-python.nix {
     query.wait_for_unit("thanos-query.service")
     wait_for_metric(query)
 
+    # Test Thanos query frontend service
+    query.wait_for_unit("thanos-query-frontend.service")
+    query.succeed("curl -sS http://localhost:${toString frontPort}/-/healthy")
+
     # Test if the Thanos sidecar has correctly uploaded its TSDB to S3, if the
     # Thanos storage service has correctly downloaded it from S3 and if the Thanos
     # query service running on $store can correctly retrieve the metric:
diff --git a/nixos/tests/restic.nix b/nixos/tests/restic.nix
index 868ccb7efd748..4111720cf6be8 100644
--- a/nixos/tests/restic.nix
+++ b/nixos/tests/restic.nix
@@ -55,6 +55,7 @@ import ./make-test-python.nix (
               inherit passwordFile paths exclude pruneOpts backupPrepareCommand backupCleanupCommand;
               repository = remoteRepository;
               initialize = true;
+              timerConfig = null; # has no effect here, just checking that it doesn't break the service
             };
             remote-from-file-backup = {
               inherit passwordFile exclude pruneOpts;
diff --git a/nixos/tests/seatd.nix b/nixos/tests/seatd.nix
new file mode 100644
index 0000000000000..138a6cb1cf44c
--- /dev/null
+++ b/nixos/tests/seatd.nix
@@ -0,0 +1,51 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+let
+  seatd-test = pkgs.writeShellApplication {
+    name = "seatd-client-pid";
+    text = ''
+      journalctl -u seatd --no-pager -b | while read -r line; do
+          case "$line" in
+          *"New client connected"*)
+              line="''${line##*pid: }"
+              pid="''${line%%,*}"
+              ;;
+          *"Opened client"*)
+              echo "$pid"
+              exit
+          esac
+      done;
+    '';
+  };
+in
+{
+  name = "seatd";
+  meta.maintainers = with lib.maintainers; [ sinanmohd ];
+
+  nodes.machine = { ... }: {
+    imports = [ ./common/user-account.nix ];
+    services.getty.autologinUser = "alice";
+    users.users.alice.extraGroups = [ "seat" "wheel" ];
+
+    fonts.enableDefaultPackages = true;
+    environment.systemPackages = with pkgs; [
+      dwl
+      foot
+      seatd-test
+    ];
+
+    programs.bash.loginShellInit = ''
+      [ "$(tty)" = "/dev/tty1" ] &&
+          dwl -s 'foot touch /tmp/foot_started'
+    '';
+
+    hardware.opengl.enable = true;
+    virtualisation.qemu.options = [ "-vga none -device virtio-gpu-pci" ];
+    services.seatd.enable = true;
+  };
+
+  testScript = ''
+    machine.wait_for_file("/tmp/foot_started")
+    machine.succeed("test $(seatd-client-pid) = $(pgrep dwl)")
+  '';
+})
diff --git a/nixos/tests/sftpgo.nix b/nixos/tests/sftpgo.nix
index db0098d2ac48c..a5bb1981d2c3c 100644
--- a/nixos/tests/sftpgo.nix
+++ b/nixos/tests/sftpgo.nix
@@ -156,7 +156,7 @@ in
         ensureDatabases = [ "sftpgo" ];
         ensureUsers = [{
           name = "sftpgo";
-          ensurePermissions."DATABASE sftpgo" = "ALL PRIVILEGES";
+          ensureDBOwnership = true;
         }];
       };
 
diff --git a/nixos/tests/slimserver.nix b/nixos/tests/slimserver.nix
new file mode 100644
index 0000000000000..c3f7b6fde4de0
--- /dev/null
+++ b/nixos/tests/slimserver.nix
@@ -0,0 +1,47 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "slimserver";
+  meta.maintainers = with pkgs.lib.maintainers; [ adamcstephens ];
+
+  nodes.machine = { ... }: {
+    services.slimserver.enable = true;
+    services.squeezelite = {
+      enable = true;
+      extraArguments = "-s 127.0.0.1 -d slimproto=info";
+    };
+    sound.enable = true;
+    boot.initrd.kernelModules = ["snd-dummy"];
+  };
+
+  testScript =
+    ''
+      import json
+      rpc_get_player = {
+          "id": 1,
+          "method": "slim.request",
+          "params":[0,["player", "id", "0", "?"]]
+      }
+
+      with subtest("slimserver is started"):
+          machine.wait_for_unit("slimserver.service")
+          # give slimserver a moment to report errors
+          machine.sleep(2)
+
+      with subtest('slimserver module errors are not reported'):
+          machine.fail("journalctl -u slimserver.service | grep 'throw_exception'")
+          machine.fail("journalctl -u slimserver.service | grep 'not installed'")
+          machine.fail("journalctl -u slimserver.service | grep 'not found'")
+          machine.fail("journalctl -u slimserver.service | grep 'The following CPAN modules were found but cannot work with Logitech Media Server'")
+          machine.fail("journalctl -u slimserver.service | grep 'please use the buildme.sh'")
+
+      with subtest('slimserver is ready'):
+          machine.wait_for_open_port(9000)
+          machine.wait_until_succeeds("journalctl -u slimserver.service | grep 'Completed dbOptimize Scan'")
+
+      with subtest("squeezelite player successfully connects to slimserver"):
+          machine.wait_for_unit("squeezelite.service")
+          machine.wait_until_succeeds("journalctl -u squeezelite.service | grep 'slimproto:937 connected'")
+          player_mac = machine.wait_until_succeeds("journalctl -eu squeezelite.service | grep 'sendHELO:148 mac:'").strip().split(" ")[-1]
+          player_id = machine.succeed(f"curl http://localhost:9000/jsonrpc.js -g -X POST -d '{json.dumps(rpc_get_player)}'")
+          assert player_mac == json.loads(player_id)["result"]["_id"], "squeezelite player not found"
+    '';
+})
diff --git a/nixos/tests/sourcehut.nix b/nixos/tests/sourcehut.nix
index 87e6d82bdd8f4..0b258acc2af1d 100644
--- a/nixos/tests/sourcehut.nix
+++ b/nixos/tests/sourcehut.nix
@@ -126,6 +126,7 @@ in
     virtualisation.diskSize = 4 * 1024;
     virtualisation.memorySize = 2 * 1024;
     networking.domain = domain;
+    networking.enableIPv6 = false;
     networking.extraHosts = ''
       ${config.networking.primaryIPAddress} builds.${domain}
       ${config.networking.primaryIPAddress} git.${domain}
@@ -134,11 +135,6 @@ in
 
     services.sourcehut = {
       enable = true;
-      services = [
-        "builds"
-        "git"
-        "meta"
-      ];
       nginx.enable = true;
       nginx.virtualHost = {
         forceSSL = true;
diff --git a/nixos/tests/sudo.nix b/nixos/tests/sudo.nix
index 1b177391488d8..1fe478f0bff14 100644
--- a/nixos/tests/sudo.nix
+++ b/nixos/tests/sudo.nix
@@ -21,7 +21,8 @@ in
         };
 
         security.sudo = {
-          enable = true;
+          # Explicitly _not_ defining 'enable = true;' here, to check that sudo is enabled by default
+
           wheelNeedsPassword = false;
 
           extraConfig = ''
diff --git a/nixos/tests/systemd-boot.nix b/nixos/tests/systemd-boot.nix
index 13007d0d80d84..256a18532b0a2 100644
--- a/nixos/tests/systemd-boot.nix
+++ b/nixos/tests/systemd-boot.nix
@@ -252,6 +252,35 @@ in
     '';
   };
 
+  garbage-collect-entry = makeTest {
+    name = "systemd-boot-switch-test";
+    meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
+
+    nodes = {
+      inherit common;
+      machine = { pkgs, nodes, ... }: {
+        imports = [ common ];
+
+        # These are configs for different nodes, but we'll use them here in `machine`
+        system.extraDependencies = [
+          nodes.common.system.build.toplevel
+        ];
+      };
+    };
+
+    testScript = { nodes, ... }:
+      let
+        baseSystem = nodes.common.system.build.toplevel;
+      in
+      ''
+        machine.succeed("nix-env -p /nix/var/nix/profiles/system --set ${baseSystem}")
+        machine.succeed("nix-env -p /nix/var/nix/profiles/system --delete-generations 1")
+        machine.succeed("${baseSystem}/bin/switch-to-configuration boot")
+        machine.fail("test -e /boot/loader/entries/nixos-generation-1.conf")
+        machine.succeed("test -e /boot/loader/entries/nixos-generation-2.conf")
+      '';
+  };
+
   # Some UEFI firmwares fail on large reads. Now that systemd-boot loads initrd
   # itself, systems with such firmware won't boot without this fix
   uefiLargeFileWorkaround = makeTest {
@@ -277,4 +306,20 @@ in
       machine.wait_for_unit("multi-user.target")
     '';
   };
+
+  no-bootspec = makeTest
+    {
+      name = "systemd-boot-no-bootspec";
+      meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
+
+      nodes.machine = {
+        imports = [ common ];
+        boot.bootspec.enable = false;
+      };
+
+      testScript = ''
+        machine.start()
+        machine.wait_for_unit("multi-user.target")
+      '';
+    };
 }
diff --git a/nixos/tests/systemd-initrd-modprobe.nix b/nixos/tests/systemd-initrd-modprobe.nix
index bf635a10d0e97..0f93492176b44 100644
--- a/nixos/tests/systemd-initrd-modprobe.nix
+++ b/nixos/tests/systemd-initrd-modprobe.nix
@@ -2,6 +2,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
   name = "systemd-initrd-modprobe";
 
   nodes.machine = { pkgs, ... }: {
+    testing.initrdBackdoor = true;
     boot.initrd.systemd.enable = true;
     boot.initrd.kernelModules = [ "loop" ]; # Load module in initrd.
     boot.extraModprobeConfig = ''
@@ -10,6 +11,12 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
   };
 
   testScript = ''
+    machine.wait_for_unit("initrd.target")
+    max_loop = machine.succeed("cat /sys/module/loop/parameters/max_loop")
+    assert int(max_loop) == 42, "Parameter should be respected for initrd kernel modules"
+
+    # Make sure it sticks in stage 2
+    machine.switch_root()
     machine.wait_for_unit("multi-user.target")
     max_loop = machine.succeed("cat /sys/module/loop/parameters/max_loop")
     assert int(max_loop) == 42, "Parameter should be respected for initrd kernel modules"
diff --git a/nixos/tests/systemd-initrd-networkd-ssh.nix b/nixos/tests/systemd-initrd-networkd-ssh.nix
index 6aaa6c828f7bd..d4c168f40e29d 100644
--- a/nixos/tests/systemd-initrd-networkd-ssh.nix
+++ b/nixos/tests/systemd-initrd-networkd-ssh.nix
@@ -4,34 +4,16 @@ import ./make-test-python.nix ({ lib, ... }: {
 
   nodes = {
     server = { config, pkgs, ... }: {
-      environment.systemPackages = [ pkgs.cryptsetup ];
-      boot.loader.systemd-boot.enable = true;
-      boot.loader.timeout = 0;
-      virtualisation = {
-        emptyDiskImages = [ 4096 ];
-        useBootLoader = true;
-        # Booting off the encrypted disk requires an available init script from
-        # the Nix store
-        mountHostNixStore = true;
-        useEFIBoot = true;
-      };
-
-      specialisation.encrypted-root.configuration = {
-        virtualisation.rootDevice = "/dev/mapper/root";
-        virtualisation.fileSystems."/".autoFormat = true;
-        boot.initrd.luks.devices = lib.mkVMOverride {
-          root.device = "/dev/vdb";
-        };
-        boot.initrd.systemd.enable = true;
-        boot.initrd.network = {
+      testing.initrdBackdoor = true;
+      boot.initrd.systemd.enable = true;
+      boot.initrd.systemd.contents."/etc/msg".text = "foo";
+      boot.initrd.network = {
+        enable = true;
+        ssh = {
           enable = true;
-          ssh = {
-            enable = true;
-            authorizedKeys = [ (lib.readFile ./initrd-network-ssh/id_ed25519.pub) ];
-            port = 22;
-            # Terrible hack so it works with useBootLoader
-            hostKeys = [ { outPath = "${./initrd-network-ssh/ssh_host_ed25519_key}"; } ];
-          };
+          authorizedKeys = [ (lib.readFile ./initrd-network-ssh/id_ed25519.pub) ];
+          port = 22;
+          hostKeys = [ ./initrd-network-ssh/ssh_host_ed25519_key ];
         };
       };
     };
@@ -63,24 +45,16 @@ import ./make-test-python.nix ({ lib, ... }: {
         status, _ = client.execute("nc -z server 22")
         return status == 0
 
-    server.wait_for_unit("multi-user.target")
-    server.succeed(
-        "echo somepass | cryptsetup luksFormat --type=luks2 /dev/vdb",
-        "bootctl set-default nixos-generation-1-specialisation-encrypted-root.conf",
-        "sync",
-    )
-    server.shutdown()
-    server.start()
-
     client.wait_for_unit("network.target")
     with client.nested("waiting for SSH server to come up"):
         retry(ssh_is_up)
 
-    client.succeed(
-        "echo somepass | ssh -i /etc/sshKey -o UserKnownHostsFile=/etc/knownHosts server 'systemd-tty-ask-password-agent' & exit"
+    msg = client.succeed(
+        "ssh -i /etc/sshKey -o UserKnownHostsFile=/etc/knownHosts server 'cat /etc/msg'"
     )
+    assert "foo" in msg
 
+    server.switch_root()
     server.wait_for_unit("multi-user.target")
-    server.succeed("mount | grep '/dev/mapper/root on /'")
   '';
 })
diff --git a/nixos/tests/systemd-initrd-networkd.nix b/nixos/tests/systemd-initrd-networkd.nix
index 8376276d8f63d..9c4ddb6e4b363 100644
--- a/nixos/tests/systemd-initrd-networkd.nix
+++ b/nixos/tests/systemd-initrd-networkd.nix
@@ -1,14 +1,36 @@
-import ./make-test-python.nix ({ pkgs, lib, ... }: {
-  name = "systemd-initrd-network";
-  meta.maintainers = [ lib.maintainers.elvishjerricco ];
+{ system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../.. { inherit system config; }
+, lib ? pkgs.lib
+}:
 
-  nodes = let
-    mkFlushTest = flush: script: { ... }: {
-      boot.initrd.systemd.enable = true;
-      boot.initrd.network = {
-        enable = true;
-        flushBeforeStage2 = flush;
-      };
+with import ../lib/testing-python.nix { inherit system pkgs; };
+
+let
+  inherit (lib.maintainers) elvishjerricco;
+
+  common = {
+    boot.initrd.systemd = {
+      enable = true;
+      network.wait-online.timeout = 10;
+      network.wait-online.anyInterface = true;
+      targets.network-online.requiredBy = [ "initrd.target" ];
+      services.systemd-networkd-wait-online.requiredBy =
+        [ "network-online.target" ];
+      initrdBin = [ pkgs.iproute2 pkgs.iputils pkgs.gnugrep ];
+    };
+    testing.initrdBackdoor = true;
+    boot.initrd.network.enable = true;
+  };
+
+  mkFlushTest = flush: script: makeTest {
+    name = "systemd-initrd-network-${lib.optionalString (!flush) "no-"}flush";
+    meta.maintainers = [ elvishjerricco ];
+
+    nodes.machine = {
+      imports = [ common ];
+
+      boot.initrd.network.flushBeforeStage2 = flush;
       systemd.services.check-flush = {
         requiredBy = ["multi-user.target"];
         before = ["network-pre.target" "multi-user.target"];
@@ -19,57 +41,53 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
         inherit script;
       };
     };
-  in {
-    basic = { ... }: {
-      boot.initrd.network.enable = true;
 
-      boot.initrd.systemd = {
-        enable = true;
-        # Enable network-online to fail the test in case of timeout
-        network.wait-online.timeout = 10;
-        network.wait-online.anyInterface = true;
-        targets.network-online.requiredBy = [ "initrd.target" ];
-        services.systemd-networkd-wait-online.requiredBy =
-          [ "network-online.target" ];
+    testScript = ''
+      machine.wait_for_unit("network-online.target")
+      machine.succeed(
+          "ip addr | grep 10.0.2.15",
+          "ping -c1 10.0.2.2",
+      )
+      machine.switch_root()
 
-          initrdBin = [ pkgs.iproute2 pkgs.iputils pkgs.gnugrep ];
-          services.check = {
-            requiredBy = [ "initrd.target" ];
-            before = [ "initrd.target" ];
-            after = [ "network-online.target" ];
-            serviceConfig.Type = "oneshot";
-            path = [ pkgs.iproute2 pkgs.iputils pkgs.gnugrep ];
-            script = ''
-              ip addr | grep 10.0.2.15 || exit 1
-              ping -c1 10.0.2.2 || exit 1
-            '';
-          };
-      };
-    };
-
-    doFlush = mkFlushTest true ''
-      if ip addr | grep 10.0.2.15; then
-        echo "Network configuration survived switch-root; flushBeforeStage2 failed"
-        exit 1
-      fi
+      machine.wait_for_unit("multi-user.target")
     '';
+  };
+
+in {
+  basic = makeTest {
+    name = "systemd-initrd-network";
+    meta.maintainers = [ elvishjerricco ];
 
-    dontFlush = mkFlushTest false ''
-      if ! (ip addr | grep 10.0.2.15); then
-        echo "Network configuration didn't survive switch-root"
-        exit 1
-      fi
+    nodes.machine = common;
+
+    testScript = ''
+      machine.wait_for_unit("network-online.target")
+      machine.succeed(
+          "ip addr | grep 10.0.2.15",
+          "ping -c1 10.0.2.2",
+      )
+      machine.switch_root()
+
+      # Make sure the systemd-network user was set correctly in initrd
+      machine.wait_for_unit("multi-user.target")
+      machine.succeed("[ $(stat -c '%U,%G' /run/systemd/netif/links) = systemd-network,systemd-network ]")
+      machine.succeed("ip addr show >&2")
+      machine.succeed("ip route show >&2")
     '';
   };
 
-  testScript = ''
-    start_all()
-    basic.wait_for_unit("multi-user.target")
-    doFlush.wait_for_unit("multi-user.target")
-    dontFlush.wait_for_unit("multi-user.target")
-    # Make sure the systemd-network user was set correctly in initrd
-    basic.succeed("[ $(stat -c '%U,%G' /run/systemd/netif/links) = systemd-network,systemd-network ]")
-    basic.succeed("ip addr show >&2")
-    basic.succeed("ip route show >&2")
+  doFlush = mkFlushTest true ''
+    if ip addr | grep 10.0.2.15; then
+      echo "Network configuration survived switch-root; flushBeforeStage2 failed"
+      exit 1
+    fi
+  '';
+
+  dontFlush = mkFlushTest false ''
+    if ! (ip addr | grep 10.0.2.15); then
+      echo "Network configuration didn't survive switch-root"
+      exit 1
+    fi
   '';
-})
+}
diff --git a/nixos/tests/systemd-initrd-simple.nix b/nixos/tests/systemd-initrd-simple.nix
index a6a22e9d48e06..2b7283a821939 100644
--- a/nixos/tests/systemd-initrd-simple.nix
+++ b/nixos/tests/systemd-initrd-simple.nix
@@ -2,16 +2,19 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
   name = "systemd-initrd-simple";
 
   nodes.machine = { pkgs, ... }: {
-    boot.initrd.systemd = {
-      enable = true;
-      emergencyAccess = true;
-    };
+    testing.initrdBackdoor = true;
+    boot.initrd.systemd.enable = true;
     virtualisation.fileSystems."/".autoResize = true;
   };
 
   testScript = ''
     import subprocess
 
+    with subtest("testing initrd backdoor"):
+        machine.wait_for_unit("initrd.target")
+        machine.succeed("systemctl status initrd-fs.target")
+        machine.switch_root()
+
     with subtest("handover to stage-2 systemd works"):
         machine.wait_for_unit("multi-user.target")
         machine.succeed("systemd-analyze | grep -q '(initrd)'")  # direct handover
@@ -37,6 +40,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
         subprocess.check_call(["qemu-img", "resize", "vm-state-machine/machine.qcow2", "+1G"])
 
         machine.start()
+        machine.switch_root()
         newAvail = machine.succeed("df --output=avail / | sed 1d")
 
         assert int(oldAvail) < int(newAvail), "File system did not grow"
diff --git a/nixos/tests/systemd-networkd.nix b/nixos/tests/systemd-networkd.nix
index 6c423f4140b1f..6b241b93d5118 100644
--- a/nixos/tests/systemd-networkd.nix
+++ b/nixos/tests/systemd-networkd.nix
@@ -65,7 +65,7 @@ let generateNodeConf = { lib, pkgs, config, privk, pubk, peerId, nodeId, ...}: {
 in import ./make-test-python.nix ({pkgs, ... }: {
   name = "networkd";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ ninjatrappeur ];
+    maintainers = [ picnoir ];
   };
   nodes = {
     node1 = { pkgs, ... }@attrs:
diff --git a/nixos/tests/tandoor-recipes.nix b/nixos/tests/tandoor-recipes.nix
index 54456238fe634..18beaac6f062c 100644
--- a/nixos/tests/tandoor-recipes.nix
+++ b/nixos/tests/tandoor-recipes.nix
@@ -3,10 +3,8 @@ import ./make-test-python.nix ({ lib, ... }: {
   meta.maintainers = with lib.maintainers; [ ambroisie ];
 
   nodes.machine = { pkgs, ... }: {
-    # Setup using Postgres
     services.tandoor-recipes = {
       enable = true;
-
       extraConfig = {
         DB_ENGINE = "django.db.backends.postgresql";
         POSTGRES_HOST = "/run/postgresql";
@@ -21,7 +19,7 @@ import ./make-test-python.nix ({ lib, ... }: {
       ensureUsers = [
         {
           name = "tandoor_recipes";
-          ensurePermissions."DATABASE tandoor_recipes" = "ALL PRIVILEGES";
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/tests/terminal-emulators.nix b/nixos/tests/terminal-emulators.nix
index 6d76cc8e57412..b52801c898eb3 100644
--- a/nixos/tests/terminal-emulators.nix
+++ b/nixos/tests/terminal-emulators.nix
@@ -76,6 +76,7 @@ let tests = {
 
       rio.pkg = p: p.rio;
       rio.cmd = "rio -e $command";
+      rio.pinkValue = "#FF1261";
 
       roxterm.pkg = p: p.roxterm;
       roxterm.cmd = "roxterm -e $command";
diff --git a/nixos/tests/tomcat.nix b/nixos/tests/tomcat.nix
index 4cfb3cc5a7d84..a5f219e104ad4 100644
--- a/nixos/tests/tomcat.nix
+++ b/nixos/tests/tomcat.nix
@@ -1,21 +1,26 @@
-import ./make-test-python.nix ({ pkgs, ... }:
-
-{
+import ./make-test-python.nix ({ pkgs, ... }: {
   name = "tomcat";
 
   nodes.machine = { pkgs, ... }: {
-    services.tomcat.enable = true;
+    services.tomcat = {
+      enable = true;
+      axis2.enable = true;
+    };
   };
 
   testScript = ''
     machine.wait_for_unit("tomcat.service")
     machine.wait_for_open_port(8080)
     machine.wait_for_file("/var/tomcat/webapps/examples");
+
+    machine.succeed(
+        "curl -sS --fail http://localhost:8080/examples/servlets/servlet/HelloWorldExample | grep 'Hello World!'"
+    )
     machine.succeed(
-        "curl --fail http://localhost:8080/examples/servlets/servlet/HelloWorldExample | grep 'Hello World!'"
+        "curl -sS --fail http://localhost:8080/examples/jsp/jsp2/simpletag/hello.jsp | grep 'Hello, world!'"
     )
     machine.succeed(
-        "curl --fail http://localhost:8080/examples/jsp/jsp2/simpletag/hello.jsp | grep 'Hello, world!'"
+        "curl -sS --fail http://localhost:8080/axis2/axis2-web/HappyAxis.jsp | grep 'Found Axis2'"
     )
   '';
 })
diff --git a/nixos/tests/udisks2.nix b/nixos/tests/udisks2.nix
index 6afb200f85664..8cc148750c7bc 100644
--- a/nixos/tests/udisks2.nix
+++ b/nixos/tests/udisks2.nix
@@ -32,6 +32,9 @@ in
     ''
       import lzma
 
+      machine.systemctl("start udisks2")
+      machine.wait_for_unit("udisks2.service")
+
       with lzma.open(
           "${stick}"
       ) as data, open(machine.state_dir / "usbstick.img", "wb") as stick:
diff --git a/nixos/tests/vikunja.nix b/nixos/tests/vikunja.nix
index 2660aa9767ca7..60fd5ce13854e 100644
--- a/nixos/tests/vikunja.nix
+++ b/nixos/tests/vikunja.nix
@@ -33,7 +33,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
         ensureDatabases = [ "vikunja-api" ];
         ensureUsers = [
           { name = "vikunja-api";
-            ensurePermissions = { "DATABASE \"vikunja-api\"" = "ALL PRIVILEGES"; };
+            ensureDBOwnership = true;
           }
         ];
       };
diff --git a/nixos/tests/web-apps/mastodon/remote-postgresql.nix b/nixos/tests/web-apps/mastodon/remote-postgresql.nix
index 715477191bfbc..6548883db4526 100644
--- a/nixos/tests/web-apps/mastodon/remote-postgresql.nix
+++ b/nixos/tests/web-apps/mastodon/remote-postgresql.nix
@@ -16,7 +16,7 @@ in
   meta.maintainers = with pkgs.lib.maintainers; [ erictapen izorkin ];
 
   nodes = {
-    database = {
+    database = { config, ... }: {
       networking = {
         interfaces.eth1 = {
           ipv4.addresses = [
@@ -24,11 +24,13 @@ in
           ];
         };
         extraHosts = hosts;
-        firewall.allowedTCPPorts = [ 5432 ];
+        firewall.allowedTCPPorts = [ config.services.postgresql.port ];
       };
 
       services.postgresql = {
         enable = true;
+        # TODO remove once https://github.com/NixOS/nixpkgs/pull/266270 is resolved.
+        package = pkgs.postgresql_14;
         enableTCPIP = true;
         authentication = ''
           hostnossl mastodon_local mastodon_test 192.168.2.201/32 md5
@@ -41,7 +43,7 @@ in
       };
     };
 
-    nginx = {
+    nginx = { nodes, ... }: {
       networking = {
         interfaces.eth1 = {
           ipv4.addresses = [
@@ -69,18 +71,14 @@ in
             tryFiles = "$uri @proxy";
           };
           locations."@proxy" = {
-            proxyPass = "http://192.168.2.201:55001";
-            proxyWebsockets = true;
-          };
-          locations."/api/v1/streaming/" = {
-            proxyPass = "http://192.168.2.201:55002";
+            proxyPass = "http://192.168.2.201:${toString nodes.server.services.mastodon.webPort}";
             proxyWebsockets = true;
           };
         };
       };
     };
 
-    server = { pkgs, ... }: {
+    server = { config, pkgs, ... }: {
       virtualisation.memorySize = 2048;
 
       environment = {
@@ -98,7 +96,10 @@ in
           ];
         };
         extraHosts = hosts;
-        firewall.allowedTCPPorts = [ 55001 55002 ];
+        firewall.allowedTCPPorts = [
+          config.services.mastodon.webPort
+          config.services.mastodon.sidekiqPort
+        ];
       };
 
       services.mastodon = {
@@ -106,6 +107,7 @@ in
         configureNginx = false;
         localDomain = "mastodon.local";
         enableUnixSocket = false;
+        streamingProcesses = 2;
         database = {
           createLocally = false;
           host = "192.168.2.102";
diff --git a/nixos/tests/web-apps/mastodon/script.nix b/nixos/tests/web-apps/mastodon/script.nix
index a89b4b7480e99..afb7c0e0a0ebe 100644
--- a/nixos/tests/web-apps/mastodon/script.nix
+++ b/nixos/tests/web-apps/mastodon/script.nix
@@ -10,9 +10,8 @@
 
   server.wait_for_unit("redis-mastodon.service")
   server.wait_for_unit("mastodon-sidekiq-all.service")
-  server.wait_for_unit("mastodon-streaming.service")
+  server.wait_for_unit("mastodon-streaming.target")
   server.wait_for_unit("mastodon-web.service")
-  server.wait_for_open_port(55000)
   server.wait_for_open_port(55001)
 
   # Check that mastodon-media-auto-remove is scheduled
diff --git a/nixos/tests/web-apps/mastodon/standard.nix b/nixos/tests/web-apps/mastodon/standard.nix
index 14311afea3f78..e5eb30fef5976 100644
--- a/nixos/tests/web-apps/mastodon/standard.nix
+++ b/nixos/tests/web-apps/mastodon/standard.nix
@@ -40,11 +40,15 @@ in
         port = 31637;
       };
 
+      # TODO remove once https://github.com/NixOS/nixpkgs/pull/266270 is resolved.
+      services.postgresql.package = pkgs.postgresql_14;
+
       services.mastodon = {
         enable = true;
         configureNginx = true;
         localDomain = "mastodon.local";
         enableUnixSocket = false;
+        streamingProcesses = 2;
         smtp = {
           createLocally = false;
           fromAddress = "mastodon@mastodon.local";
diff --git a/nixos/tests/wiki-js.nix b/nixos/tests/wiki-js.nix
index fd054a9c59093..8b3c51935a6c4 100644
--- a/nixos/tests/wiki-js.nix
+++ b/nixos/tests/wiki-js.nix
@@ -10,14 +10,15 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
       enable = true;
       settings.db.host = "/run/postgresql";
       settings.db.user = "wiki-js";
+      settings.db.db = "wiki-js";
       settings.logLevel = "debug";
     };
     services.postgresql = {
       enable = true;
-      ensureDatabases = [ "wiki" ];
+      ensureDatabases = [ "wiki-js" ];
       ensureUsers = [
         { name = "wiki-js";
-          ensurePermissions."DATABASE wiki" = "ALL PRIVILEGES";
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/tests/wordpress.nix b/nixos/tests/wordpress.nix
index 937b505af2acd..592af9a094f1d 100644
--- a/nixos/tests/wordpress.nix
+++ b/nixos/tests/wordpress.nix
@@ -67,7 +67,7 @@ rec {
       networking.hosts."127.0.0.1" = [ "site1.local" "site2.local" ];
     };
   }) {} [
-    "6_3"
+    "6_3" "6_4"
   ];
 
   testScript = ''
diff --git a/nixos/tests/xmpp/ejabberd.nix b/nixos/tests/xmpp/ejabberd.nix
index 7926fe80de2fd..1a807b27b6f65 100644
--- a/nixos/tests/xmpp/ejabberd.nix
+++ b/nixos/tests/xmpp/ejabberd.nix
@@ -1,7 +1,7 @@
 import ../make-test-python.nix ({ pkgs, ... }: {
   name = "ejabberd";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ ajs124 ];
+    maintainers = [ ];
   };
   nodes = {
     client = { nodes, pkgs, ... }: {
diff --git a/nixos/tests/xscreensaver.nix b/nixos/tests/xscreensaver.nix
new file mode 100644
index 0000000000000..820ddbb0e9626
--- /dev/null
+++ b/nixos/tests/xscreensaver.nix
@@ -0,0 +1,64 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "pass-secret-service";
+  meta.maintainers = with lib.maintainers; [ vancluever AndersonTorres ];
+
+  nodes = {
+    ok = { nodes, pkgs, ... }:
+      {
+        imports = [ ./common/x11.nix ./common/user-account.nix ];
+        test-support.displayManager.auto.user = "alice";
+        services.xscreensaver.enable = true;
+      };
+
+    empty_wrapperPrefix = { nodes, pkgs, ... }:
+      {
+        imports = [ ./common/x11.nix ./common/user-account.nix ];
+        test-support.displayManager.auto.user = "alice";
+        services.xscreensaver.enable = true;
+        nixpkgs.overlays = [
+          (self: super: {
+            xscreensaver = super.xscreensaver.override {
+              wrapperPrefix = "";
+            };
+          })
+        ];
+      };
+
+    bad_wrapperPrefix = { nodes, pkgs, ... }:
+      {
+        imports = [ ./common/x11.nix ./common/user-account.nix ];
+        test-support.displayManager.auto.user = "alice";
+        services.xscreensaver.enable = true;
+        nixpkgs.overlays = [
+          (self: super: {
+            xscreensaver = super.xscreensaver.override {
+              wrapperPrefix = "/a/bad/path";
+            };
+          })
+        ];
+      };
+  };
+
+  testScript = ''
+    ok.wait_for_x()
+    ok.wait_for_unit("xscreensaver", "alice")
+    _, output_ok = ok.systemctl("status xscreensaver", "alice")
+    assert 'To prevent the kernel from randomly unlocking' not in output_ok
+    assert 'your screen via the out-of-memory killer' not in output_ok
+    assert '"xscreensaver-auth" must be setuid root' not in output_ok
+
+    empty_wrapperPrefix.wait_for_x()
+    empty_wrapperPrefix.wait_for_unit("xscreensaver", "alice")
+    _, output_empty_wrapperPrefix = empty_wrapperPrefix.systemctl("status xscreensaver", "alice")
+    assert 'To prevent the kernel from randomly unlocking' in output_empty_wrapperPrefix
+    assert 'your screen via the out-of-memory killer' in output_empty_wrapperPrefix
+    assert '"xscreensaver-auth" must be setuid root' in output_empty_wrapperPrefix
+
+    bad_wrapperPrefix.wait_for_x()
+    bad_wrapperPrefix.wait_for_unit("xscreensaver", "alice")
+    _, output_bad_wrapperPrefix = bad_wrapperPrefix.systemctl("status xscreensaver", "alice")
+    assert 'To prevent the kernel from randomly unlocking' in output_bad_wrapperPrefix
+    assert 'your screen via the out-of-memory killer' in output_bad_wrapperPrefix
+    assert '"xscreensaver-auth" must be setuid root' in output_bad_wrapperPrefix
+  '';
+})
diff --git a/nixos/tests/zfs.nix b/nixos/tests/zfs.nix
index 3454fbaf78fe5..ad4ea254f34d7 100644
--- a/nixos/tests/zfs.nix
+++ b/nixos/tests/zfs.nix
@@ -13,6 +13,7 @@ let
                       else pkgs.linuxPackages
     , enableUnstable ? false
     , enableSystemdStage1 ? false
+    , zfsPackage ? if enableUnstable then pkgs.zfs else pkgs.zfsUnstable
     , extraTest ? ""
     }:
     makeTest {
@@ -21,7 +22,7 @@ let
         maintainers = [ adisbladis elvishjerricco ];
       };
 
-      nodes.machine = { pkgs, lib, ... }:
+      nodes.machine = { config, pkgs, lib, ... }:
         let
           usersharePath = "/var/lib/samba/usershares";
         in {
@@ -35,8 +36,8 @@ let
         boot.loader.efi.canTouchEfiVariables = true;
         networking.hostId = "deadbeef";
         boot.kernelPackages = kernelPackage;
+        boot.zfs.package = zfsPackage;
         boot.supportedFilesystems = [ "zfs" ];
-        boot.zfs.enableUnstable = enableUnstable;
         boot.initrd.systemd.enable = enableSystemdStage1;
 
         environment.systemPackages = [ pkgs.parted ];
@@ -193,6 +194,11 @@ let
 
 in {
 
+  # maintainer: @raitobezarius
+  series_2_1 = makeZfsTest "2.1-series" {
+    zfsPackage = pkgs.zfs_2_1;
+  };
+
   stable = makeZfsTest "stable" { };
 
   unstable = makeZfsTest "unstable" {