about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/.gitignore2
-rw-r--r--nixos/doc/manual/Makefile30
-rw-r--r--nixos/doc/manual/administration/containers.chapter.md8
-rw-r--r--nixos/doc/manual/administration/running.md14
-rw-r--r--nixos/doc/manual/administration/running.xml21
-rw-r--r--nixos/doc/manual/administration/troubleshooting.chapter.md12
-rw-r--r--nixos/doc/manual/configuration/config-syntax.chapter.md8
-rw-r--r--nixos/doc/manual/configuration/configuration.md27
-rw-r--r--nixos/doc/manual/configuration/configuration.xml31
-rw-r--r--nixos/doc/manual/configuration/declarative-packages.section.md6
-rw-r--r--nixos/doc/manual/configuration/file-systems.chapter.md6
-rw-r--r--nixos/doc/manual/configuration/networking.chapter.md18
-rw-r--r--nixos/doc/manual/configuration/package-mgmt.chapter.md6
-rw-r--r--nixos/doc/manual/configuration/profiles.chapter.md24
-rw-r--r--nixos/doc/manual/default.nix243
-rw-r--r--nixos/doc/manual/development/developing-the-test-driver.chapter.md43
-rw-r--r--nixos/doc/manual/development/development.md15
-rw-r--r--nixos/doc/manual/development/development.xml20
-rw-r--r--nixos/doc/manual/development/nixos-tests.chapter.md10
-rw-r--r--nixos/doc/manual/development/option-declarations.section.md34
-rw-r--r--nixos/doc/manual/development/replace-modules.section.md11
-rw-r--r--nixos/doc/manual/development/what-happens-during-a-system-switch.chapter.md6
-rw-r--r--nixos/doc/manual/development/writing-documentation.chapter.md2
-rw-r--r--nixos/doc/manual/development/writing-modules.chapter.md20
-rw-r--r--nixos/doc/manual/development/writing-nixos-tests.section.md6
-rw-r--r--nixos/doc/manual/installation/installation.md11
-rw-r--r--nixos/doc/manual/installation/installation.xml18
-rw-r--r--nixos/doc/manual/installation/installing.chapter.md22
-rw-r--r--nixos/doc/manual/man-configuration.xml31
-rw-r--r--nixos/doc/manual/man-pages.xml30
-rw-r--r--nixos/doc/manual/manpages/nixos-build-vms.88
-rw-r--r--nixos/doc/manual/manpages/nixos-enter.88
-rw-r--r--nixos/doc/manual/manpages/nixos-generate-config.88
-rw-r--r--nixos/doc/manual/manpages/nixos-install.88
-rw-r--r--nixos/doc/manual/manpages/nixos-option.88
-rw-r--r--nixos/doc/manual/manpages/nixos-rebuild.88
-rw-r--r--nixos/doc/manual/manpages/nixos-version.88
-rw-r--r--nixos/doc/manual/manual.md56
-rw-r--r--nixos/doc/manual/manual.xml23
-rw-r--r--nixos/doc/manual/nixos-options.md7
-rw-r--r--nixos/doc/manual/preface.md11
-rw-r--r--nixos/doc/manual/preface.xml42
-rw-r--r--nixos/doc/manual/release-notes/release-notes.md25
-rw-r--r--nixos/doc/manual/release-notes/release-notes.xml30
-rw-r--r--nixos/doc/manual/release-notes/rl-2205.section.md2
-rw-r--r--nixos/doc/manual/release-notes/rl-2305.section.md84
-rw-r--r--nixos/doc/manual/shell.nix8
-rwxr-xr-xnixos/doc/varlistentry-fixer.rb124
-rw-r--r--nixos/doc/xmlformat.conf72
-rw-r--r--nixos/lib/make-disk-image.nix5
-rw-r--r--nixos/lib/make-multi-disk-zfs-image.nix4
-rw-r--r--nixos/lib/make-options-doc/default.nix24
-rw-r--r--nixos/lib/make-options-doc/generateDoc.py112
-rw-r--r--nixos/lib/test-driver/test_driver/driver.py1
-rw-r--r--nixos/lib/test-driver/test_driver/machine.py35
-rw-r--r--nixos/lib/testing/meta.nix2
-rw-r--r--nixos/modules/config/console.nix26
-rw-r--r--nixos/modules/config/fonts/fonts.nix53
-rw-r--r--nixos/modules/config/no-x-libs.nix9
-rw-r--r--nixos/modules/config/update-users-groups.pl10
-rw-r--r--nixos/modules/config/users-groups.nix6
-rw-r--r--nixos/modules/config/zram.nix20
-rw-r--r--nixos/modules/hardware/all-firmware.nix2
-rw-r--r--nixos/modules/hardware/device-tree.nix8
-rw-r--r--nixos/modules/hardware/flipperzero.nix18
-rw-r--r--nixos/modules/hardware/keyboard/qmk.nix16
-rw-r--r--nixos/modules/hardware/keyboard/teck.nix6
-rw-r--r--nixos/modules/hardware/keyboard/uhk.nix7
-rw-r--r--nixos/modules/hardware/keyboard/zsa.nix19
-rw-r--r--nixos/modules/hardware/video/hidpi.nix24
-rw-r--r--nixos/modules/hardware/video/nvidia.nix4
-rw-r--r--nixos/modules/installer/netboot/netboot-minimal.nix3
-rw-r--r--nixos/modules/installer/sd-card/sd-image-powerpc64le.nix49
-rw-r--r--nixos/modules/installer/tools/nix-fallback-paths.nix10
-rw-r--r--nixos/modules/installer/tools/nixos-generate-config.pl30
-rw-r--r--nixos/modules/installer/tools/tools.nix7
-rw-r--r--nixos/modules/misc/ids.nix5
-rw-r--r--nixos/modules/module-list.nix24
-rw-r--r--nixos/modules/profiles/base.nix16
-rw-r--r--nixos/modules/programs/atop.nix3
-rw-r--r--nixos/modules/programs/ccache.nix2
-rw-r--r--nixos/modules/programs/flashrom.nix1
-rw-r--r--nixos/modules/programs/java.nix24
-rw-r--r--nixos/modules/programs/nexttrace.nix25
-rw-r--r--nixos/modules/programs/regreet.nix75
-rw-r--r--nixos/modules/programs/starship.nix25
-rw-r--r--nixos/modules/programs/waybar.nix9
-rw-r--r--nixos/modules/rename.nix1
-rw-r--r--nixos/modules/security/doas.nix20
-rw-r--r--nixos/modules/security/pam.nix4
-rw-r--r--nixos/modules/services/audio/tts.nix151
-rw-r--r--nixos/modules/services/backup/borgbackup.md2
-rw-r--r--nixos/modules/services/backup/borgmatic.nix86
-rw-r--r--nixos/modules/services/backup/btrbk.nix57
-rw-r--r--nixos/modules/services/backup/restic.nix7
-rw-r--r--nixos/modules/services/cluster/hadoop/hbase.nix228
-rw-r--r--nixos/modules/services/cluster/k3s/default.nix9
-rw-r--r--nixos/modules/services/cluster/kubernetes/addons/dns.nix11
-rw-r--r--nixos/modules/services/cluster/kubernetes/pki.nix2
-rw-r--r--nixos/modules/services/continuous-integration/github-runner/options.nix41
-rw-r--r--nixos/modules/services/continuous-integration/github-runner/service.nix7
-rw-r--r--nixos/modules/services/continuous-integration/gitlab-runner.nix6
-rw-r--r--nixos/modules/services/continuous-integration/hydra/default.nix2
-rw-r--r--nixos/modules/services/continuous-integration/woodpecker/agent.nix99
-rw-r--r--nixos/modules/services/continuous-integration/woodpecker/server.nix98
-rw-r--r--nixos/modules/services/databases/clickhouse.nix2
-rw-r--r--nixos/modules/services/desktops/pipewire/daemon/pipewire-aes67.conf.json38
-rw-r--r--nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json9
-rw-r--r--nixos/modules/services/development/gemstash.nix103
-rw-r--r--nixos/modules/services/hardware/fwupd.nix16
-rw-r--r--nixos/modules/services/hardware/kanata.nix86
-rw-r--r--nixos/modules/services/hardware/keyd.nix112
-rw-r--r--nixos/modules/services/hardware/supergfxd.nix1
-rw-r--r--nixos/modules/services/hardware/undervolt.nix4
-rw-r--r--nixos/modules/services/home-automation/home-assistant.nix3
-rw-r--r--nixos/modules/services/mail/maddy.nix51
-rw-r--r--nixos/modules/services/mail/postfix.nix2
-rw-r--r--nixos/modules/services/mail/roundcube.nix2
-rw-r--r--nixos/modules/services/matrix/appservice-discord.nix16
-rw-r--r--nixos/modules/services/matrix/dendrite.nix4
-rw-r--r--nixos/modules/services/matrix/mautrix-facebook.nix2
-rw-r--r--nixos/modules/services/matrix/mautrix-telegram.nix2
-rw-r--r--nixos/modules/services/matrix/synapse.md7
-rw-r--r--nixos/modules/services/misc/gitea.nix32
-rw-r--r--nixos/modules/services/misc/gitlab.nix218
-rw-r--r--nixos/modules/services/misc/jellyseerr.nix62
-rw-r--r--nixos/modules/services/misc/klipper.nix2
-rw-r--r--nixos/modules/services/misc/mbpfan.nix39
-rw-r--r--nixos/modules/services/misc/moonraker.nix49
-rw-r--r--nixos/modules/services/misc/paperless.nix46
-rw-r--r--nixos/modules/services/misc/portunus.nix2
-rw-r--r--nixos/modules/services/misc/readarr.nix88
-rw-r--r--nixos/modules/services/misc/sssd.nix5
-rw-r--r--nixos/modules/services/misc/zoneminder.nix12
-rw-r--r--nixos/modules/services/monitoring/grafana.nix2
-rw-r--r--nixos/modules/services/monitoring/mimir.nix9
-rw-r--r--nixos/modules/services/monitoring/parsedmarc.nix4
-rw-r--r--nixos/modules/services/monitoring/prometheus/alertmanager-irc-relay.nix107
-rw-r--r--nixos/modules/services/monitoring/prometheus/default.nix4
-rw-r--r--nixos/modules/services/networking/avahi-daemon.nix36
-rw-r--r--nixos/modules/services/networking/cgit.nix203
-rw-r--r--nixos/modules/services/networking/firewall-nftables.nix16
-rw-r--r--nixos/modules/services/networking/firewall.nix4
-rw-r--r--nixos/modules/services/networking/headscale.nix58
-rw-r--r--nixos/modules/services/networking/imaginary.nix113
-rw-r--r--nixos/modules/services/networking/jicofo.nix48
-rw-r--r--nixos/modules/services/networking/multipath.nix8
-rw-r--r--nixos/modules/services/networking/murmur.nix30
-rw-r--r--nixos/modules/services/networking/networkd-dispatcher.nix98
-rw-r--r--nixos/modules/services/networking/nftables.nix51
-rw-r--r--nixos/modules/services/networking/ntp/chrony.nix2
-rw-r--r--nixos/modules/services/networking/openvpn.nix6
-rw-r--r--nixos/modules/services/networking/peroxide.nix131
-rw-r--r--nixos/modules/services/networking/radicale.nix6
-rw-r--r--nixos/modules/services/networking/ssh/sshd.nix2
-rw-r--r--nixos/modules/services/networking/tailscale.nix4
-rw-r--r--nixos/modules/services/networking/teleport.nix12
-rw-r--r--nixos/modules/services/networking/unbound.nix2
-rw-r--r--nixos/modules/services/networking/v2raya.nix49
-rw-r--r--nixos/modules/services/networking/wireguard.nix4
-rw-r--r--nixos/modules/services/networking/yggdrasil.nix200
-rw-r--r--nixos/modules/services/search/opensearch.nix248
-rw-r--r--nixos/modules/services/search/qdrant.nix128
-rw-r--r--nixos/modules/services/search/solr.nix110
-rw-r--r--nixos/modules/services/system/cachix-watch-store.nix2
-rw-r--r--nixos/modules/services/system/self-deploy.nix2
-rw-r--r--nixos/modules/services/web-apps/akkoma.md12
-rw-r--r--nixos/modules/services/web-apps/akkoma.nix12
-rw-r--r--nixos/modules/services/web-apps/alps.nix2
-rw-r--r--nixos/modules/services/web-apps/baget.nix170
-rw-r--r--nixos/modules/services/web-apps/coder.nix217
-rw-r--r--nixos/modules/services/web-apps/discourse.nix5
-rw-r--r--nixos/modules/services/web-apps/dolibarr.nix4
-rw-r--r--nixos/modules/services/web-apps/jitsi-meet.nix9
-rw-r--r--nixos/modules/services/web-apps/limesurvey.nix33
-rw-r--r--nixos/modules/services/web-apps/nextcloud-notify_push.nix96
-rw-r--r--nixos/modules/services/web-apps/nextcloud.md2
-rw-r--r--nixos/modules/services/web-apps/nextcloud.nix74
-rw-r--r--nixos/modules/services/web-apps/onlyoffice.nix2
-rw-r--r--nixos/modules/services/web-apps/writefreely.nix11
-rw-r--r--nixos/modules/services/web-servers/minio.nix79
-rw-r--r--nixos/modules/services/web-servers/nginx/default.nix18
-rw-r--r--nixos/modules/services/x11/desktop-managers/pantheon.nix3
-rw-r--r--nixos/modules/services/x11/desktop-managers/phosh.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/plasma5.nix168
-rw-r--r--nixos/modules/services/x11/display-managers/sddm.nix10
-rw-r--r--nixos/modules/services/x11/window-managers/nimdow.nix23
-rw-r--r--nixos/modules/services/x11/xserver.nix42
-rw-r--r--nixos/modules/system/activation/top-level.nix30
-rw-r--r--nixos/modules/system/boot/binfmt.nix18
-rw-r--r--nixos/modules/system/boot/loader/grub/install-grub.pl7
-rwxr-xr-xnixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py17
-rw-r--r--nixos/modules/system/boot/networkd.nix1096
-rw-r--r--nixos/modules/system/boot/systemd.nix2
-rw-r--r--nixos/modules/system/boot/systemd/coredump.nix4
-rw-r--r--nixos/modules/system/boot/systemd/initrd.nix36
-rw-r--r--nixos/modules/system/boot/systemd/repart.nix80
-rw-r--r--nixos/modules/tasks/filesystems.nix48
-rw-r--r--nixos/modules/tasks/filesystems/vfat.nix2
-rw-r--r--nixos/modules/virtualisation/docker.nix4
-rw-r--r--nixos/modules/virtualisation/ec2-metadata-fetcher.sh7
-rw-r--r--nixos/modules/virtualisation/multipass.nix2
-rw-r--r--nixos/modules/virtualisation/nixos-containers.nix11
-rw-r--r--nixos/modules/virtualisation/podman/default.nix10
-rw-r--r--nixos/modules/virtualisation/qemu-vm.nix99
-rw-r--r--nixos/modules/virtualisation/virtualbox-image.nix14
-rw-r--r--nixos/release-combined.nix4
-rw-r--r--nixos/release-small.nix13
-rw-r--r--nixos/release.nix1
-rw-r--r--nixos/tests/all-tests.nix23
-rw-r--r--nixos/tests/atuin.nix2
-rw-r--r--nixos/tests/btrbk-doas.nix114
-rw-r--r--nixos/tests/cgit.nix73
-rw-r--r--nixos/tests/clickhouse.nix2
-rw-r--r--nixos/tests/coder.nix24
-rw-r--r--nixos/tests/discourse.nix8
-rw-r--r--nixos/tests/docker-tools.nix54
-rw-r--r--nixos/tests/fsck.nix12
-rw-r--r--nixos/tests/garage/default.nix2
-rw-r--r--nixos/tests/gemstash.nix51
-rw-r--r--nixos/tests/gitea.nix33
-rw-r--r--nixos/tests/github-runner.nix37
-rw-r--r--nixos/tests/gitlab.nix12
-rw-r--r--nixos/tests/gollum.nix2
-rwxr-xr-xnixos/tests/google-oslogin/server.py10
-rw-r--r--nixos/tests/hadoop/hbase.nix25
-rw-r--r--nixos/tests/haproxy.nix1
-rw-r--r--nixos/tests/home-assistant.nix107
-rw-r--r--nixos/tests/hostname.nix86
-rw-r--r--nixos/tests/initrd-secrets-changing.nix58
-rw-r--r--nixos/tests/installer.nix6
-rw-r--r--nixos/tests/isso.nix2
-rw-r--r--nixos/tests/k3s/default.nix8
-rw-r--r--nixos/tests/k3s/multi-node.nix6
-rw-r--r--nixos/tests/k3s/single-node.nix9
-rw-r--r--nixos/tests/kea.nix120
-rw-r--r--nixos/tests/keepassxc.nix8
-rw-r--r--nixos/tests/keyd.nix82
-rw-r--r--nixos/tests/knot.nix48
-rw-r--r--nixos/tests/login.nix13
-rw-r--r--nixos/tests/luks.nix69
-rw-r--r--nixos/tests/maddy.nix2
-rw-r--r--nixos/tests/matrix/mjolnir.nix5
-rw-r--r--nixos/tests/minio.nix84
-rw-r--r--nixos/tests/miriway.nix6
-rw-r--r--nixos/tests/nat.nix15
-rw-r--r--nixos/tests/netdata.nix2
-rw-r--r--nixos/tests/nextcloud/default.nix2
-rw-r--r--nixos/tests/nextcloud/openssl-sse.nix5
-rw-r--r--nixos/tests/nextcloud/with-postgresql-and-redis.nix13
-rw-r--r--nixos/tests/nixops/default.nix8
-rw-r--r--nixos/tests/noto-fonts-cjk-qt-default-weight.nix30
-rw-r--r--nixos/tests/openldap.nix2
-rw-r--r--nixos/tests/opensearch.nix52
-rw-r--r--nixos/tests/pantheon.nix5
-rw-r--r--nixos/tests/pass-secret-service.nix2
-rw-r--r--nixos/tests/peroxide.nix16
-rw-r--r--nixos/tests/phosh.nix2
-rw-r--r--nixos/tests/pleroma.nix4
-rw-r--r--nixos/tests/podman/default.nix121
-rw-r--r--nixos/tests/pomerium.nix7
-rw-r--r--nixos/tests/predictable-interface-names.nix2
-rw-r--r--nixos/tests/quake3.nix8
-rw-r--r--nixos/tests/readarr.nix18
-rw-r--r--nixos/tests/restic.nix6
-rw-r--r--nixos/tests/sgtpuzzles.nix34
-rw-r--r--nixos/tests/shadow.nix23
-rw-r--r--nixos/tests/soapui.nix2
-rw-r--r--nixos/tests/solr.nix56
-rw-r--r--nixos/tests/sssd.nix1
-rw-r--r--nixos/tests/systemd-credentials-tpm2.nix124
-rw-r--r--nixos/tests/systemd-cryptenroll.nix1
-rw-r--r--nixos/tests/systemd-initrd-simple.nix4
-rw-r--r--nixos/tests/systemd-initrd-vconsole.nix33
-rw-r--r--nixos/tests/systemd-repart.nix34
-rw-r--r--nixos/tests/systemd-shutdown.nix1
-rw-r--r--nixos/tests/teleport.nix82
-rw-r--r--nixos/tests/timescaledb.nix93
-rw-r--r--nixos/tests/tuxguitar.nix2
-rw-r--r--nixos/tests/txredisapi.nix2
-rw-r--r--nixos/tests/unifi.nix2
-rw-r--r--nixos/tests/wireguard/namespaces.nix1
-rw-r--r--nixos/tests/wireguard/snakeoil-keys.nix3
-rw-r--r--nixos/tests/xfce.nix2
-rw-r--r--nixos/tests/zram-generator.nix34
285 files changed, 7526 insertions, 2570 deletions
diff --git a/nixos/doc/manual/.gitignore b/nixos/doc/manual/.gitignore
deleted file mode 100644
index 8792826242173..0000000000000
--- a/nixos/doc/manual/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-generated
-manual-combined.xml
diff --git a/nixos/doc/manual/Makefile b/nixos/doc/manual/Makefile
deleted file mode 100644
index b2b6481b20c75..0000000000000
--- a/nixos/doc/manual/Makefile
+++ /dev/null
@@ -1,30 +0,0 @@
-.PHONY: all
-all: manual-combined.xml
-
-.PHONY: debug
-debug: generated manual-combined.xml
-
-manual-combined.xml: generated *.xml **/*.xml
-	rm -f ./manual-combined.xml
-	nix-shell --pure -Q --packages xmloscopy \
-		--run "xmloscopy --docbook5 ./manual.xml ./manual-combined.xml"
-
-.PHONY: format
-format:
-	nix-shell --pure -Q --packages xmlformat \
-		--run "find ../../ -iname '*.xml' -type f -print0 | xargs -0 -I{} -n1 \
-		xmlformat --config-file '../xmlformat.conf' -i {}"
-
-.PHONY: fix-misc-xml
-fix-misc-xml:
-	find . -iname '*.xml' -type f \
-		-exec ../varlistentry-fixer.rb {} ';'
-
-.PHONY: clean
-clean:
-	rm -f manual-combined.xml generated
-
-generated:
-	nix-build ../../release.nix \
-		--attr manualGeneratedSources.x86_64-linux \
-		--out-link ./generated
diff --git a/nixos/doc/manual/administration/containers.chapter.md b/nixos/doc/manual/administration/containers.chapter.md
index ea51f91f698fb..50493b562b542 100644
--- a/nixos/doc/manual/administration/containers.chapter.md
+++ b/nixos/doc/manual/administration/containers.chapter.md
@@ -21,8 +21,8 @@ which is often not what you want. By contrast, in the imperative
 approach, containers are configured and updated independently from the
 host system.
 
-```{=docbook}
-<xi:include href="imperative-containers.section.xml" />
-<xi:include href="declarative-containers.section.xml" />
-<xi:include href="container-networking.section.xml" />
+```{=include=} sections
+imperative-containers.section.md
+declarative-containers.section.md
+container-networking.section.md
 ```
diff --git a/nixos/doc/manual/administration/running.md b/nixos/doc/manual/administration/running.md
new file mode 100644
index 0000000000000..48e8c7c6668b7
--- /dev/null
+++ b/nixos/doc/manual/administration/running.md
@@ -0,0 +1,14 @@
+# Administration {#ch-running}
+
+This chapter describes various aspects of managing a running NixOS system, such as how to use the {command}`systemd` service manager.
+
+```{=include=} chapters
+service-mgmt.chapter.md
+rebooting.chapter.md
+user-sessions.chapter.md
+control-groups.chapter.md
+logging.chapter.md
+cleaning-store.chapter.md
+containers.chapter.md
+troubleshooting.chapter.md
+```
diff --git a/nixos/doc/manual/administration/running.xml b/nixos/doc/manual/administration/running.xml
deleted file mode 100644
index d9fcc1aee263f..0000000000000
--- a/nixos/doc/manual/administration/running.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<part xmlns="http://docbook.org/ns/docbook"
-      xmlns:xlink="http://www.w3.org/1999/xlink"
-      xmlns:xi="http://www.w3.org/2001/XInclude"
-      version="5.0"
-      xml:id="ch-running">
- <title>Administration</title>
- <partintro xml:id="ch-running-intro">
-  <para>
-   This chapter describes various aspects of managing a running NixOS system,
-   such as how to use the <command>systemd</command> service manager.
-  </para>
- </partintro>
- <xi:include href="../from_md/administration/service-mgmt.chapter.xml" />
- <xi:include href="../from_md/administration/rebooting.chapter.xml" />
- <xi:include href="../from_md/administration/user-sessions.chapter.xml" />
- <xi:include href="../from_md/administration/control-groups.chapter.xml" />
- <xi:include href="../from_md/administration/logging.chapter.xml" />
- <xi:include href="../from_md/administration/cleaning-store.chapter.xml" />
- <xi:include href="../from_md/administration/containers.chapter.xml" />
- <xi:include href="../from_md/administration/troubleshooting.chapter.xml" />
-</part>
diff --git a/nixos/doc/manual/administration/troubleshooting.chapter.md b/nixos/doc/manual/administration/troubleshooting.chapter.md
index 548456eaf6d67..1253607f8efc2 100644
--- a/nixos/doc/manual/administration/troubleshooting.chapter.md
+++ b/nixos/doc/manual/administration/troubleshooting.chapter.md
@@ -3,10 +3,10 @@
 This chapter describes solutions to common problems you might encounter
 when you manage your NixOS system.
 
-```{=docbook}
-<xi:include href="boot-problems.section.xml" />
-<xi:include href="maintenance-mode.section.xml" />
-<xi:include href="rollback.section.xml" />
-<xi:include href="store-corruption.section.xml" />
-<xi:include href="network-problems.section.xml" />
+```{=include=} sections
+boot-problems.section.md
+maintenance-mode.section.md
+rollback.section.md
+store-corruption.section.md
+network-problems.section.md
 ```
diff --git a/nixos/doc/manual/configuration/config-syntax.chapter.md b/nixos/doc/manual/configuration/config-syntax.chapter.md
index 9f8d45d588997..9e606b2b82af3 100644
--- a/nixos/doc/manual/configuration/config-syntax.chapter.md
+++ b/nixos/doc/manual/configuration/config-syntax.chapter.md
@@ -11,8 +11,8 @@ manual](https://nixos.org/nix/manual/#chap-writing-nix-expressions), but
 here we give a short overview of the most important constructs useful in
 NixOS configuration files.
 
-```{=docbook}
-<xi:include href="config-file.section.xml" />
-<xi:include href="abstractions.section.xml" />
-<xi:include href="modularity.section.xml" />
+```{=include=} sections
+config-file.section.md
+abstractions.section.md
+modularity.section.md
 ```
diff --git a/nixos/doc/manual/configuration/configuration.md b/nixos/doc/manual/configuration/configuration.md
new file mode 100644
index 0000000000000..4c966f3325b99
--- /dev/null
+++ b/nixos/doc/manual/configuration/configuration.md
@@ -0,0 +1,27 @@
+# Configuration {#ch-configuration}
+
+This chapter describes how to configure various aspects of a NixOS machine through the configuration file {file}`/etc/nixos/configuration.nix`. As described in [](#sec-changing-config), changes to this file only take effect after you run {command}`nixos-rebuild`.
+
+```{=include=} chapters
+config-syntax.chapter.md
+package-mgmt.chapter.md
+user-mgmt.chapter.md
+file-systems.chapter.md
+x-windows.chapter.md
+wayland.chapter.md
+gpu-accel.chapter.md
+xfce.chapter.md
+networking.chapter.md
+linux-kernel.chapter.md
+subversion.chapter.md
+```
+
+```{=include=} chapters
+@MODULE_CHAPTERS@
+```
+
+```{=include=} chapters
+profiles.chapter.md
+kubernetes.chapter.md
+```
+<!-- Apache; libvirtd virtualisation -->
diff --git a/nixos/doc/manual/configuration/configuration.xml b/nixos/doc/manual/configuration/configuration.xml
deleted file mode 100644
index b04316cfa48e2..0000000000000
--- a/nixos/doc/manual/configuration/configuration.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<part xmlns="http://docbook.org/ns/docbook"
-      xmlns:xlink="http://www.w3.org/1999/xlink"
-      xmlns:xi="http://www.w3.org/2001/XInclude"
-      version="5.0"
-      xml:id="ch-configuration">
- <title>Configuration</title>
- <partintro xml:id="ch-configuration-intro">
-  <para>
-   This chapter describes how to configure various aspects of a NixOS machine
-   through the configuration file
-   <filename>/etc/nixos/configuration.nix</filename>. As described in
-   <xref linkend="sec-changing-config" />, changes to this file only take
-   effect after you run <command>nixos-rebuild</command>.
-  </para>
- </partintro>
- <xi:include href="../from_md/configuration/config-syntax.chapter.xml" />
- <xi:include href="../from_md/configuration/package-mgmt.chapter.xml" />
- <xi:include href="../from_md/configuration/user-mgmt.chapter.xml" />
- <xi:include href="../from_md/configuration/file-systems.chapter.xml" />
- <xi:include href="../from_md/configuration/x-windows.chapter.xml" />
- <xi:include href="../from_md/configuration/wayland.chapter.xml" />
- <xi:include href="../from_md/configuration/gpu-accel.chapter.xml" />
- <xi:include href="../from_md/configuration/xfce.chapter.xml" />
- <xi:include href="../from_md/configuration/networking.chapter.xml" />
- <xi:include href="../from_md/configuration/linux-kernel.chapter.xml" />
- <xi:include href="../from_md/configuration/subversion.chapter.xml" />
- <xi:include href="../generated/modules.xml" xpointer="xpointer(//section[@id='modules']/*)" />
- <xi:include href="../from_md/configuration/profiles.chapter.xml" />
- <xi:include href="../from_md/configuration/kubernetes.chapter.xml" />
-<!-- Apache; libvirtd virtualisation -->
-</part>
diff --git a/nixos/doc/manual/configuration/declarative-packages.section.md b/nixos/doc/manual/configuration/declarative-packages.section.md
index 337cdf8472e40..02eaa56192e46 100644
--- a/nixos/doc/manual/configuration/declarative-packages.section.md
+++ b/nixos/doc/manual/configuration/declarative-packages.section.md
@@ -40,7 +40,7 @@ configuration use `pkgs` prefix (variable).
 To "uninstall" a package, simply remove it from
 [](#opt-environment.systemPackages) and run `nixos-rebuild switch`.
 
-```{=docbook}
-<xi:include href="customizing-packages.section.xml" />
-<xi:include href="adding-custom-packages.section.xml" />
+```{=include=} sections
+customizing-packages.section.md
+adding-custom-packages.section.md
 ```
diff --git a/nixos/doc/manual/configuration/file-systems.chapter.md b/nixos/doc/manual/configuration/file-systems.chapter.md
index 901e2e4f181b3..aca978be064d3 100644
--- a/nixos/doc/manual/configuration/file-systems.chapter.md
+++ b/nixos/doc/manual/configuration/file-systems.chapter.md
@@ -36,7 +36,7 @@ dropping you to the emergency shell. You can make a mount asynchronous
 and non-critical by adding `options = [ "nofail" ];`.
 :::
 
-```{=docbook}
-<xi:include href="luks-file-systems.section.xml" />
-<xi:include href="sshfs-file-systems.section.xml" />
+```{=include=} sections
+luks-file-systems.section.md
+sshfs-file-systems.section.md
 ```
diff --git a/nixos/doc/manual/configuration/networking.chapter.md b/nixos/doc/manual/configuration/networking.chapter.md
index 529dc0610bdaf..abbd9766f173b 100644
--- a/nixos/doc/manual/configuration/networking.chapter.md
+++ b/nixos/doc/manual/configuration/networking.chapter.md
@@ -3,14 +3,14 @@
 This section describes how to configure networking components
 on your NixOS machine.
 
-```{=docbook}
-<xi:include href="network-manager.section.xml" />
-<xi:include href="ssh.section.xml" />
-<xi:include href="ipv4-config.section.xml" />
-<xi:include href="ipv6-config.section.xml" />
-<xi:include href="firewall.section.xml" />
-<xi:include href="wireless.section.xml" />
-<xi:include href="ad-hoc-network-config.section.xml" />
-<xi:include href="renaming-interfaces.section.xml" />
+```{=include=} sections
+network-manager.section.md
+ssh.section.md
+ipv4-config.section.md
+ipv6-config.section.md
+firewall.section.md
+wireless.section.md
+ad-hoc-network-config.section.md
+renaming-interfaces.section.md
 ```
 <!-- TODO: OpenVPN, NAT -->
diff --git a/nixos/doc/manual/configuration/package-mgmt.chapter.md b/nixos/doc/manual/configuration/package-mgmt.chapter.md
index a6c414be59a97..1148bbe84740c 100644
--- a/nixos/doc/manual/configuration/package-mgmt.chapter.md
+++ b/nixos/doc/manual/configuration/package-mgmt.chapter.md
@@ -12,7 +12,7 @@ NixOS has two distinct styles of package management:
     `nix-env` command. This style allows mixing packages from different
     Nixpkgs versions. It's the only choice for non-root users.
 
-```{=docbook}
-<xi:include href="declarative-packages.section.xml" />
-<xi:include href="ad-hoc-packages.section.xml" />
+```{=include=} sections
+declarative-packages.section.md
+ad-hoc-packages.section.md
 ```
diff --git a/nixos/doc/manual/configuration/profiles.chapter.md b/nixos/doc/manual/configuration/profiles.chapter.md
index 2c3dea27c1818..9f1f48f742ac5 100644
--- a/nixos/doc/manual/configuration/profiles.chapter.md
+++ b/nixos/doc/manual/configuration/profiles.chapter.md
@@ -19,16 +19,16 @@ install media, many are actually intended to be used in real installs.
 What follows is a brief explanation on the purpose and use-case for each
 profile. Detailing each option configured by each one is out of scope.
 
-```{=docbook}
-<xi:include href="profiles/all-hardware.section.xml" />
-<xi:include href="profiles/base.section.xml" />
-<xi:include href="profiles/clone-config.section.xml" />
-<xi:include href="profiles/demo.section.xml" />
-<xi:include href="profiles/docker-container.section.xml" />
-<xi:include href="profiles/graphical.section.xml" />
-<xi:include href="profiles/hardened.section.xml" />
-<xi:include href="profiles/headless.section.xml" />
-<xi:include href="profiles/installation-device.section.xml" />
-<xi:include href="profiles/minimal.section.xml" />
-<xi:include href="profiles/qemu-guest.section.xml" />
+```{=include=} sections
+profiles/all-hardware.section.md
+profiles/base.section.md
+profiles/clone-config.section.md
+profiles/demo.section.md
+profiles/docker-container.section.md
+profiles/graphical.section.md
+profiles/hardened.section.md
+profiles/headless.section.md
+profiles/installation-device.section.md
+profiles/minimal.section.md
+profiles/qemu-guest.section.md
 ```
diff --git a/nixos/doc/manual/default.nix b/nixos/doc/manual/default.nix
index e0bcec1ea6b5f..4032595e80598 100644
--- a/nixos/doc/manual/default.nix
+++ b/nixos/doc/manual/default.nix
@@ -68,56 +68,6 @@ let
       optionIdPrefix = "test-opt-";
     };
 
-  sources = runCommand "manual-sources" {
-    inputs = lib.sourceFilesBySuffices ./. [ ".xml" ".md" ];
-    nativeBuildInputs = [ pkgs.nixos-render-docs ];
-  } ''
-    mkdir $out
-    cd $out
-    cp -r --no-preserve=all $inputs/* .
-
-    declare -a convert_args
-    while read -r mf; do
-      if [[ "$mf" = *.chapter.md ]]; then
-        convert_args+=("--chapter")
-      else
-        convert_args+=("--section")
-      fi
-
-      convert_args+=("from_md/''${mf%.md}.xml" "$mf")
-    done < <(find . -type f -name '*.md')
-
-    nixos-render-docs manual docbook-fragment \
-      --manpage-urls ${manpageUrls} \
-      "''${convert_args[@]}"
-  '';
-
-  modulesDoc = runCommand "modules.xml" {
-    nativeBuildInputs = [ pkgs.nixos-render-docs ];
-  } ''
-    nixos-render-docs manual docbook-section \
-      --manpage-urls ${manpageUrls} \
-      "$out" \
-      --section \
-        --section-id modules \
-        --chapters ${lib.concatMapStrings (p: "${p.value} ") config.meta.doc}
-  '';
-
-  generatedSources = runCommand "generated-docbook" {} ''
-    mkdir $out
-    ln -s ${modulesDoc} $out/modules.xml
-    ln -s ${optionsDoc.optionsDocBook} $out/options-db.xml
-    ln -s ${testOptionsDoc.optionsDocBook} $out/test-options-db.xml
-    printf "%s" "${version}" > $out/version
-  '';
-
-  copySources =
-    ''
-      cp -prd $sources/* . # */
-      ln -s ${generatedSources} ./generated
-      chmod -R u+w .
-    '';
-
   toc = builtins.toFile "toc.xml"
     ''
       <toc role="chunk-toc">
@@ -148,70 +98,117 @@ let
     "--stringparam chunk.toc ${toc}"
   ];
 
+  linterFunctions = ''
+    # outputs the context of an xmllint error output
+    # LEN lines around the failing line are printed
+    function context {
+      # length of context
+      local LEN=6
+      # lines to print before error line
+      local BEFORE=4
+
+      # xmllint output lines are:
+      # file.xml:1234: there was an error on line 1234
+      while IFS=':' read -r file line rest; do
+        echo
+        if [[ -n "$rest" ]]; then
+          echo "$file:$line:$rest"
+          local FROM=$(($line>$BEFORE ? $line - $BEFORE : 1))
+          # number lines & filter context
+          nl --body-numbering=a "$file" | sed -n "$FROM,+$LEN p"
+        else
+          if [[ -n "$line" ]]; then
+            echo "$file:$line"
+          else
+            echo "$file"
+          fi
+        fi
+      done
+    }
+
+    function lintrng {
+      xmllint --debug --noout --nonet \
+        --relaxng ${docbook5}/xml/rng/docbook/docbook.rng \
+        "$1" \
+        2>&1 | context 1>&2
+        # ^ redirect assumes xmllint doesn’t print to stdout
+    }
+  '';
+
+  prepareManualFromMD = ''
+    cp -r --no-preserve=all $inputs/* .
+
+    substituteInPlace ./manual.md \
+      --replace '@NIXOS_VERSION@' "${version}"
+    substituteInPlace ./configuration/configuration.md \
+      --replace \
+          '@MODULE_CHAPTERS@' \
+          ${lib.escapeShellArg (lib.concatMapStringsSep "\n" (p: "${p.value}") config.meta.doc)}
+    substituteInPlace ./nixos-options.md \
+      --replace \
+        '@NIXOS_OPTIONS_JSON@' \
+        ${optionsDoc.optionsJSON}/share/doc/nixos/options.json
+    substituteInPlace ./development/writing-nixos-tests.section.md \
+      --replace \
+        '@NIXOS_TEST_OPTIONS_JSON@' \
+        ${testOptionsDoc.optionsJSON}/share/doc/nixos/options.json
+  '';
+
   manual-combined = runCommand "nixos-manual-combined"
-    { inherit sources;
-      nativeBuildInputs = [ buildPackages.libxml2.bin buildPackages.libxslt.bin ];
+    { inputs = lib.sourceFilesBySuffices ./. [ ".xml" ".md" ];
+      nativeBuildInputs = [ pkgs.nixos-render-docs pkgs.libxml2.bin pkgs.libxslt.bin ];
       meta.description = "The NixOS manual as plain docbook XML";
     }
     ''
-      ${copySources}
-
-      xmllint --xinclude --output ./manual-combined.xml ./manual.xml
-      xmllint --xinclude --noxincludenode \
-         --output ./man-pages-combined.xml ./man-pages.xml
-
-      # outputs the context of an xmllint error output
-      # LEN lines around the failing line are printed
-      function context {
-        # length of context
-        local LEN=6
-        # lines to print before error line
-        local BEFORE=4
-
-        # xmllint output lines are:
-        # file.xml:1234: there was an error on line 1234
-        while IFS=':' read -r file line rest; do
-          echo
-          if [[ -n "$rest" ]]; then
-            echo "$file:$line:$rest"
-            local FROM=$(($line>$BEFORE ? $line - $BEFORE : 1))
-            # number lines & filter context
-            nl --body-numbering=a "$file" | sed -n "$FROM,+$LEN p"
-          else
-            if [[ -n "$line" ]]; then
-              echo "$file:$line"
-            else
-              echo "$file"
-            fi
-          fi
-        done
-      }
+      ${prepareManualFromMD}
+
+      nixos-render-docs -j $NIX_BUILD_CORES manual docbook \
+        --manpage-urls ${manpageUrls} \
+        --revision ${lib.escapeShellArg revision} \
+        ./manual.md \
+        ./manual-combined-pre.xml
 
-      function lintrng {
-        xmllint --debug --noout --nonet \
-          --relaxng ${docbook5}/xml/rng/docbook/docbook.rng \
-          "$1" \
-          2>&1 | context 1>&2
-          # ^ redirect assumes xmllint doesn’t print to stdout
-      }
+      ${pkgs.libxslt.bin}/bin/xsltproc \
+        -o manual-combined.xml ${./../../lib/make-options-doc/postprocess-option-descriptions.xsl} \
+        manual-combined-pre.xml
+
+      ${linterFunctions}
 
       mkdir $out
       cp manual-combined.xml $out/
-      cp man-pages-combined.xml $out/
 
       lintrng $out/manual-combined.xml
-      lintrng $out/man-pages-combined.xml
     '';
 
-in rec {
-  inherit generatedSources;
+  manpages-combined = runCommand "nixos-manpages-combined.xml"
+    { nativeBuildInputs = [ buildPackages.libxml2.bin buildPackages.libxslt.bin ];
+      meta.description = "The NixOS manpages as plain docbook XML";
+    }
+    ''
+      mkdir generated
+      cp -prd ${./man-pages.xml} man-pages.xml
+      ln -s ${optionsDoc.optionsDocBook} generated/options-db.xml
+
+      xmllint --xinclude --noxincludenode --output $out ./man-pages.xml
 
+      ${linterFunctions}
+
+      lintrng $out
+    '';
+
+in rec {
   inherit (optionsDoc) optionsJSON optionsNix optionsDocBook optionsUsedDocbook;
 
   # Generate the NixOS manual.
   manualHTML = runCommand "nixos-manual-html"
-    { inherit sources;
-      nativeBuildInputs = [ buildPackages.libxml2.bin buildPackages.libxslt.bin ];
+    { nativeBuildInputs =
+        if allowDocBook then [
+          buildPackages.libxml2.bin
+          buildPackages.libxslt.bin
+        ] else [
+          buildPackages.nixos-render-docs
+        ];
+      inputs = lib.optionals (! allowDocBook) (lib.sourceFilesBySuffices ./. [ ".md" ]);
       meta.description = "The NixOS manual in HTML format";
       allowedReferences = ["out"];
     }
@@ -219,23 +216,44 @@ in rec {
       # Generate the HTML manual.
       dst=$out/share/doc/nixos
       mkdir -p $dst
-      xsltproc \
-        ${manualXsltprocOptions} \
-        --stringparam id.warnings "1" \
-        --nonet --output $dst/ \
-        ${docbook_xsl_ns}/xml/xsl/docbook/xhtml/chunktoc.xsl \
-        ${manual-combined}/manual-combined.xml \
-        |& tee xsltproc.out
-      grep "^ID recommended on" xsltproc.out &>/dev/null && echo "error: some IDs are missing" && false
-      rm xsltproc.out
-
-      mkdir -p $dst/images/callouts
-      cp ${docbook_xsl_ns}/xml/xsl/docbook/images/callouts/*.svg $dst/images/callouts/
 
       cp ${../../../doc/style.css} $dst/style.css
       cp ${../../../doc/overrides.css} $dst/overrides.css
       cp -r ${pkgs.documentation-highlighter} $dst/highlightjs
 
+      ${if allowDocBook then ''
+          xsltproc \
+            ${manualXsltprocOptions} \
+            --stringparam id.warnings "1" \
+            --nonet --output $dst/ \
+            ${docbook_xsl_ns}/xml/xsl/docbook/xhtml/chunktoc.xsl \
+            ${manual-combined}/manual-combined.xml \
+            |& tee xsltproc.out
+          grep "^ID recommended on" xsltproc.out &>/dev/null && echo "error: some IDs are missing" && false
+          rm xsltproc.out
+
+          mkdir -p $dst/images/callouts
+          cp ${docbook_xsl_ns}/xml/xsl/docbook/images/callouts/*.svg $dst/images/callouts/
+        '' else ''
+          ${prepareManualFromMD}
+
+          # TODO generator is set like this because the docbook/md manual compare workflow will
+          # trigger if it's different
+          nixos-render-docs -j $NIX_BUILD_CORES manual html \
+            --manpage-urls ${manpageUrls} \
+            --revision ${lib.escapeShellArg revision} \
+            --generator "DocBook XSL Stylesheets V${docbook_xsl_ns.version}" \
+            --stylesheet style.css \
+            --stylesheet overrides.css \
+            --stylesheet highlightjs/mono-blue.css \
+            --script ./highlightjs/highlight.pack.js \
+            --script ./highlightjs/loader.js \
+            --toc-depth 1 \
+            --chunk-toc-depth 1 \
+            ./manual.md \
+            $dst/index.html
+        ''}
+
       mkdir -p $out/nix-support
       echo "nix-build out $out" >> $out/nix-support/hydra-build-products
       echo "doc manual $dst" >> $out/nix-support/hydra-build-products
@@ -248,8 +266,7 @@ in rec {
   manualHTMLIndex = "${manualHTML}/share/doc/nixos/index.html";
 
   manualEpub = runCommand "nixos-manual-epub"
-    { inherit sources;
-      nativeBuildInputs = [ buildPackages.libxml2.bin buildPackages.libxslt.bin buildPackages.zip ];
+    { nativeBuildInputs = [ buildPackages.libxml2.bin buildPackages.libxslt.bin buildPackages.zip ];
     }
     ''
       # Generate the epub manual.
@@ -300,11 +317,11 @@ in rec {
             --param man.endnotes.are.numbered 0 \
             --param man.break.after.slash 1 \
             ${docbook_xsl_ns}/xml/xsl/docbook/manpages/docbook.xsl \
-            ${manual-combined}/man-pages-combined.xml
+            ${manpages-combined}
         ''
         else ''
           mkdir -p $out/share/man/man5
-          nixos-render-docs options manpage \
+          nixos-render-docs -j $NIX_BUILD_CORES options manpage \
             --revision ${lib.escapeShellArg revision} \
             ${optionsJSON}/share/doc/nixos/options.json \
             $out/share/man/man5/configuration.nix.5
diff --git a/nixos/doc/manual/development/developing-the-test-driver.chapter.md b/nixos/doc/manual/development/developing-the-test-driver.chapter.md
new file mode 100644
index 0000000000000..4b70fe00af476
--- /dev/null
+++ b/nixos/doc/manual/development/developing-the-test-driver.chapter.md
@@ -0,0 +1,43 @@
+
+# Developing the NixOS Test Driver {#chap-developing-the-test-driver}
+
+The NixOS test framework is a project of its own.
+
+It consists of roughly the following components:
+
+ - `nixos/lib/test-driver`: The Python framework that sets up the test and runs the [`testScript`](#test-opt-testScript)
+ - `nixos/lib/testing`: The Nix code responsible for the wiring, written using the (NixOS) Module System.
+
+These components are exposed publicly through:
+
+ - `nixos/lib/default.nix`: The public interface that exposes the `nixos/lib/testing` entrypoint.
+ - `flake.nix`: Exposes the `lib.nixos`, including the public test interface.
+
+Beyond the test driver itself, its integration into NixOS and Nixpkgs is important.
+
+ - `pkgs/top-level/all-packages.nix`: Defines the `nixosTests` attribute, used
+   by the package `tests` attributes and OfBorg.
+ - `nixos/release.nix`: Defines the `tests` attribute built by Hydra, independently, but analogous to `nixosTests`
+ - `nixos/release-combined.nix`: Defines which tests are channel blockers.
+
+Finally, we have legacy entrypoints that users should move away from, but are cared for on a best effort basis.
+These include `pkgs.nixosTest`, `testing-python.nix` and `make-test-python.nix`.
+
+## Testing changes to the test framework {#sec-test-the-test-framework}
+
+When making significant changes to the test framework, we run the tests on Hydra, to avoid disrupting the larger NixOS project.
+
+For this, we use the `python-test-refactoring` branch in the `NixOS/nixpkgs` repository, and its [corresponding Hydra jobset](https://hydra.nixos.org/jobset/nixos/python-test-refactoring).
+This branch is used as a pointer, and not as a feature branch.
+
+1. Rebase the PR onto a recent, good evaluation of `nixos-unstable`
+2. Create a baseline evaluation by force-pushing this revision of `nixos-unstable` to `python-test-refactoring`.
+3. Note the evaluation number (we'll call it `<previous>`)
+4. Push the PR to `python-test-refactoring` and evaluate the PR on Hydra
+5. Create a comparison URL by navigating to the latest build of the PR and adding to the URL `?compare=<previous>`. This is not necessary for the evaluation that comes right after the baseline.
+
+Review the removed tests and newly failed tests using the constructed URL; otherwise you will accidentally compare iterations of the PR instead of changes to the PR base.
+
+As we currently have some flaky tests, newly failing tests are expected, but should be reviewed to make sure that
+ - The number of failures did not increase significantly.
+ - All failures that do occur can reasonably be assumed to fail for a different reason than the changes.
diff --git a/nixos/doc/manual/development/development.md b/nixos/doc/manual/development/development.md
new file mode 100644
index 0000000000000..76f405c3b29cc
--- /dev/null
+++ b/nixos/doc/manual/development/development.md
@@ -0,0 +1,15 @@
+# Development {#ch-development}
+
+This chapter describes how you can modify and extend NixOS.
+
+```{=include=} chapters
+sources.chapter.md
+writing-modules.chapter.md
+building-parts.chapter.md
+bootspec.chapter.md
+what-happens-during-a-system-switch.chapter.md
+writing-documentation.chapter.md
+nixos-tests.chapter.md
+developing-the-test-driver.chapter.md
+testing-installer.chapter.md
+```
diff --git a/nixos/doc/manual/development/development.xml b/nixos/doc/manual/development/development.xml
deleted file mode 100644
index 949468c9021df..0000000000000
--- a/nixos/doc/manual/development/development.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<part   xmlns="http://docbook.org/ns/docbook"
-        xmlns:xlink="http://www.w3.org/1999/xlink"
-        xmlns:xi="http://www.w3.org/2001/XInclude"
-        version="5.0"
-        xml:id="ch-development">
- <title>Development</title>
- <partintro xml:id="ch-development-intro">
-  <para>
-   This chapter describes how you can modify and extend NixOS.
-  </para>
- </partintro>
- <xi:include href="../from_md/development/sources.chapter.xml" />
- <xi:include href="../from_md/development/writing-modules.chapter.xml" />
- <xi:include href="../from_md/development/building-parts.chapter.xml" />
- <xi:include href="../from_md/development/bootspec.chapter.xml" />
- <xi:include href="../from_md/development/what-happens-during-a-system-switch.chapter.xml" />
- <xi:include href="../from_md/development/writing-documentation.chapter.xml" />
- <xi:include href="../from_md/development/nixos-tests.chapter.xml" />
- <xi:include href="../from_md/development/testing-installer.chapter.xml" />
-</part>
diff --git a/nixos/doc/manual/development/nixos-tests.chapter.md b/nixos/doc/manual/development/nixos-tests.chapter.md
index 2a4fdddeaa666..ec0e4b9f076af 100644
--- a/nixos/doc/manual/development/nixos-tests.chapter.md
+++ b/nixos/doc/manual/development/nixos-tests.chapter.md
@@ -5,9 +5,9 @@ NixOS tests are kept in the directory `nixos/tests`, and are executed
 (using Nix) by a testing framework that automatically starts one or more
 virtual machines containing the NixOS system(s) required for the test.
 
-```{=docbook}
-<xi:include href="writing-nixos-tests.section.xml" />
-<xi:include href="running-nixos-tests.section.xml" />
-<xi:include href="running-nixos-tests-interactively.section.xml" />
-<xi:include href="linking-nixos-tests-to-packages.section.xml" />
+```{=include=} sections
+writing-nixos-tests.section.md
+running-nixos-tests.section.md
+running-nixos-tests-interactively.section.md
+linking-nixos-tests-to-packages.section.md
 ```
diff --git a/nixos/doc/manual/development/option-declarations.section.md b/nixos/doc/manual/development/option-declarations.section.md
index 59470bf1bc115..f6fed3e16837f 100644
--- a/nixos/doc/manual/development/option-declarations.section.md
+++ b/nixos/doc/manual/development/option-declarations.section.md
@@ -101,11 +101,24 @@ Creates an Option attribute set for an option that specifies the package a modul
 
 **Note**: You shouldn’t necessarily make package options for all of your modules. You can always overwrite a specific package throughout nixpkgs by using [nixpkgs overlays](https://nixos.org/manual/nixpkgs/stable/#chap-overlays).
 
-The default package is specified as a list of strings representing its attribute path in nixpkgs. Because of this, you need to pass nixpkgs itself as the first argument.
+The package is specified in the third argument under `default` as a list of strings
+representing its attribute path in nixpkgs (or another package set).
+Because of this, you need to pass nixpkgs itself (or a subset) as the first argument.
 
-The second argument is the name of the option, used in the description "The \<name\> package to use.". You can also pass an example value, either a literal string or a package's attribute path.
+The second argument may be either a string or a list of strings.
+It provides the display name of the package in the description of the generated option
+(using only the last element if the passed value is a list)
+and serves as the fallback value for the `default` argument.
 
-You can omit the default path if the name of the option is also attribute path in nixpkgs.
+To include extra information in the description, pass `extraDescription` to
+append arbitrary text to the generated description.
+You can also pass an `example` value, either a literal string or an attribute path.
+
+The default argument can be omitted if the provided name is
+an attribute of pkgs (if name is a string) or a
+valid attribute path in pkgs (if name is a list).
+
+If you wish to explicitly provide no default, pass `null` as `default`.
 
 During the transition to CommonMark documentation `mkPackageOption` creates an option with a DocBook description attribute, once the transition is completed it will create a CommonMark description instead. `mkPackageOptionMD` always creates an option with a CommonMark description attribute and will be removed some time after the transition is completed.
 
@@ -142,6 +155,21 @@ lib.mkOption {
 ```
 :::
 
+::: {#ex-options-declarations-util-mkPackageOption-extraDescription .example}
+```nix
+mkPackageOption pkgs [ "python39Packages" "pytorch" ] {
+  extraDescription = "This is an example and doesn't actually do anything.";
+}
+# is like
+lib.mkOption {
+  type = lib.types.package;
+  default = pkgs.python39Packages.pytorch;
+  defaultText = lib.literalExpression "pkgs.python39Packages.pytorch";
+  description = "The pytorch package to use. This is an example and doesn't actually do anything.";
+}
+```
+:::
+
 ## Extensible Option Types {#sec-option-declarations-eot}
 
 Extensible option types is a feature that allow to extend certain types
diff --git a/nixos/doc/manual/development/replace-modules.section.md b/nixos/doc/manual/development/replace-modules.section.md
index 0c0d6a7ac2f19..ac9f5adbaf981 100644
--- a/nixos/doc/manual/development/replace-modules.section.md
+++ b/nixos/doc/manual/development/replace-modules.section.md
@@ -8,8 +8,15 @@ the system on a stable release.
 
 `disabledModules` is a top level attribute like `imports`, `options` and
 `config`. It contains a list of modules that will be disabled. This can
-either be the full path to the module or a string with the filename
-relative to the modules path (eg. \<nixpkgs/nixos/modules> for nixos).
+either be:
+ - the full path to the module,
+ - or a string with the filename relative to the modules path (eg. \<nixpkgs/nixos/modules> for nixos),
+ - or an attribute set containing a specific `key` attribute.
+
+The latter allows some modules to be disabled, despite them being distributed
+via attributes instead of file paths. The `key` should be globally unique, so
+it is recommended to include a file path in it, or rely on a framework to do it
+for you.
 
 This example will replace the existing postgresql module with the
 version defined in the nixos-unstable channel while keeping the rest of
diff --git a/nixos/doc/manual/development/what-happens-during-a-system-switch.chapter.md b/nixos/doc/manual/development/what-happens-during-a-system-switch.chapter.md
index aad82831a3c24..9cbec729803ac 100644
--- a/nixos/doc/manual/development/what-happens-during-a-system-switch.chapter.md
+++ b/nixos/doc/manual/development/what-happens-during-a-system-switch.chapter.md
@@ -47,7 +47,7 @@ Most of these actions are either self-explaining but some of them have to do
 with our units or the activation script. For this reason, these topics are
 explained in the next sections.
 
-```{=docbook}
-<xi:include href="unit-handling.section.xml" />
-<xi:include href="activation-script.section.xml" />
+```{=include=} sections
+unit-handling.section.md
+activation-script.section.md
 ```
diff --git a/nixos/doc/manual/development/writing-documentation.chapter.md b/nixos/doc/manual/development/writing-documentation.chapter.md
index 4986c9f0a81b6..8d504dfb0b0a0 100644
--- a/nixos/doc/manual/development/writing-documentation.chapter.md
+++ b/nixos/doc/manual/development/writing-documentation.chapter.md
@@ -83,7 +83,7 @@ Keep the following guidelines in mind when you create and add a topic:
 
 ## Adding a Topic to the Book {#sec-writing-docs-adding-a-topic}
 
-Open the parent XML file and add an `xi:include` element to the list of
+Open the parent CommonMark file and add a line to the list of
 chapters with the file name of the topic that you created. If you
 created a `section`, you add the file to the `chapter` file. If you created
 a `chapter`, you add the file to the `part` file.
diff --git a/nixos/doc/manual/development/writing-modules.chapter.md b/nixos/doc/manual/development/writing-modules.chapter.md
index a0ec4a5df96e9..ae657458d7680 100644
--- a/nixos/doc/manual/development/writing-modules.chapter.md
+++ b/nixos/doc/manual/development/writing-modules.chapter.md
@@ -189,14 +189,14 @@ in {
 ```
 :::
 
-```{=docbook}
-<xi:include href="option-declarations.section.xml" />
-<xi:include href="option-types.section.xml" />
-<xi:include href="option-def.section.xml" />
-<xi:include href="assertions.section.xml" />
-<xi:include href="meta-attributes.section.xml" />
-<xi:include href="importing-modules.section.xml" />
-<xi:include href="replace-modules.section.xml" />
-<xi:include href="freeform-modules.section.xml" />
-<xi:include href="settings-options.section.xml" />
+```{=include=} sections
+option-declarations.section.md
+option-types.section.md
+option-def.section.md
+assertions.section.md
+meta-attributes.section.md
+importing-modules.section.md
+replace-modules.section.md
+freeform-modules.section.md
+settings-options.section.md
 ```
diff --git a/nixos/doc/manual/development/writing-nixos-tests.section.md b/nixos/doc/manual/development/writing-nixos-tests.section.md
index d80e314e6251b..3de46fda3df67 100644
--- a/nixos/doc/manual/development/writing-nixos-tests.section.md
+++ b/nixos/doc/manual/development/writing-nixos-tests.section.md
@@ -470,6 +470,8 @@ In that case, `numpy` is chosen from the generic `python3Packages`.
 
 The following options can be used when writing tests.
 
-```{=docbook}
-<xi:include href="../../generated/test-options-db.xml" xpointer="test-options-list"/>
+```{=include=} options
+id-prefix: test-opt-
+list-id: test-options-list
+source: @NIXOS_TEST_OPTIONS_JSON@
 ```
diff --git a/nixos/doc/manual/installation/installation.md b/nixos/doc/manual/installation/installation.md
new file mode 100644
index 0000000000000..140594256609f
--- /dev/null
+++ b/nixos/doc/manual/installation/installation.md
@@ -0,0 +1,11 @@
+# Installation {#ch-installation}
+
+This section describes how to obtain, install, and configure NixOS for first-time use.
+
+```{=include=} chapters
+obtaining.chapter.md
+installing.chapter.md
+changing-config.chapter.md
+upgrading.chapter.md
+building-nixos.chapter.md
+```
diff --git a/nixos/doc/manual/installation/installation.xml b/nixos/doc/manual/installation/installation.xml
deleted file mode 100644
index ba07d71d0ca35..0000000000000
--- a/nixos/doc/manual/installation/installation.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<part xmlns="http://docbook.org/ns/docbook"
-      xmlns:xlink="http://www.w3.org/1999/xlink"
-      xmlns:xi="http://www.w3.org/2001/XInclude"
-      version="5.0"
-      xml:id="ch-installation">
- <title>Installation</title>
- <partintro xml:id="ch-installation-intro">
-  <para>
-   This section describes how to obtain, install, and configure NixOS for
-   first-time use.
-  </para>
- </partintro>
- <xi:include href="../from_md/installation/obtaining.chapter.xml" />
- <xi:include href="../from_md/installation/installing.chapter.xml" />
- <xi:include href="../from_md/installation/changing-config.chapter.xml" />
- <xi:include href="../from_md/installation/upgrading.chapter.xml" />
- <xi:include href="../from_md/installation/building-nixos.chapter.xml" />
-</part>
diff --git a/nixos/doc/manual/installation/installing.chapter.md b/nixos/doc/manual/installation/installing.chapter.md
index e1908017a7e44..7d67894e59f92 100644
--- a/nixos/doc/manual/installation/installing.chapter.md
+++ b/nixos/doc/manual/installation/installing.chapter.md
@@ -428,7 +428,7 @@ Use the following commands:
 
     UEFI systems
 
-    :   You must select a boot-loader, either system-boot or GRUB. The recommended
+    :   You must select a boot-loader, either systemd-boot or GRUB. The recommended
         option is systemd-boot: set the option [](#opt-boot.loader.systemd-boot.enable)
         to `true`. `nixos-generate-config` should do this automatically
         for new configurations when booted in UEFI mode.
@@ -441,10 +441,10 @@ Use the following commands:
         If you want to use GRUB, set [](#opt-boot.loader.grub.device) to `nodev` and
         [](#opt-boot.loader.grub.efiSupport) to `true`.
 
-        With system-boot, you should not need any special configuration to detect
+        With systemd-boot, you should not need any special configuration to detect
         other installed systems. With GRUB, set [](#opt-boot.loader.grub.useOSProber)
-        to `true`, but this will only detect windows partitions, not other linux
-        distributions. If you dual boot another linux distribution, use system-boot
+        to `true`, but this will only detect windows partitions, not other Linux
+        distributions. If you dual boot another Linux distribution, use systemd-boot
         instead.
 
     If you need to configure networking for your machine the
@@ -602,11 +602,11 @@ With a partitioned disk.
 
 ## Additional installation notes {#sec-installation-additional-notes}
 
-```{=docbook}
-<xi:include href="installing-usb.section.xml" />
-<xi:include href="installing-pxe.section.xml" />
-<xi:include href="installing-kexec.section.xml" />
-<xi:include href="installing-virtualbox-guest.section.xml" />
-<xi:include href="installing-from-other-distro.section.xml" />
-<xi:include href="installing-behind-a-proxy.section.xml" />
+```{=include=} sections
+installing-usb.section.md
+installing-pxe.section.md
+installing-kexec.section.md
+installing-virtualbox-guest.section.md
+installing-from-other-distro.section.md
+installing-behind-a-proxy.section.md
 ```
diff --git a/nixos/doc/manual/man-configuration.xml b/nixos/doc/manual/man-configuration.xml
deleted file mode 100644
index ddb1408fdcf59..0000000000000
--- a/nixos/doc/manual/man-configuration.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<refentry xmlns="http://docbook.org/ns/docbook"
-          xmlns:xlink="http://www.w3.org/1999/xlink"
-          xmlns:xi="http://www.w3.org/2001/XInclude">
- <refmeta>
-  <refentrytitle><filename>configuration.nix</filename>
-  </refentrytitle><manvolnum>5</manvolnum>
-  <refmiscinfo class="source">NixOS</refmiscinfo>
-<!-- <refmiscinfo class="version"><xi:include href="version.txt" parse="text"/></refmiscinfo> -->
- </refmeta>
- <refnamediv>
-  <refname><filename>configuration.nix</filename></refname>
-  <refpurpose>NixOS system configuration specification</refpurpose>
- </refnamediv>
- <refsection>
-  <title>Description</title>
-  <para>
-   The file <filename>/etc/nixos/configuration.nix</filename> contains the
-   declarative specification of your NixOS system configuration. The command
-   <command>nixos-rebuild</command> takes this file and realises the system
-   configuration specified therein.
-  </para>
- </refsection>
- <refsection>
-  <title>Options</title>
-  <para>
-   You can use the following options in <filename>configuration.nix</filename>.
-  </para>
-  <xi:include href="./generated/options-db.xml"
-            xpointer="configuration-variable-list" />
- </refsection>
-</refentry>
diff --git a/nixos/doc/manual/man-pages.xml b/nixos/doc/manual/man-pages.xml
index 0186221bddce9..52183f1f9ee0c 100644
--- a/nixos/doc/manual/man-pages.xml
+++ b/nixos/doc/manual/man-pages.xml
@@ -14,5 +14,33 @@
   <copyright><year>2007-2022</year><holder>Eelco Dolstra and the Nixpkgs/NixOS contributors</holder>
   </copyright>
  </info>
- <xi:include href="man-configuration.xml" />
+ <refentry>
+  <refmeta>
+   <refentrytitle><filename>configuration.nix</filename>
+   </refentrytitle><manvolnum>5</manvolnum>
+   <refmiscinfo class="source">NixOS</refmiscinfo>
+ <!-- <refmiscinfo class="version"><xi:include href="version.txt" parse="text"/></refmiscinfo> -->
+  </refmeta>
+  <refnamediv>
+   <refname><filename>configuration.nix</filename></refname>
+   <refpurpose>NixOS system configuration specification</refpurpose>
+  </refnamediv>
+  <refsection>
+   <title>Description</title>
+   <para>
+    The file <filename>/etc/nixos/configuration.nix</filename> contains the
+    declarative specification of your NixOS system configuration. The command
+    <command>nixos-rebuild</command> takes this file and realises the system
+    configuration specified therein.
+   </para>
+  </refsection>
+  <refsection>
+   <title>Options</title>
+   <para>
+    You can use the following options in <filename>configuration.nix</filename>.
+   </para>
+   <xi:include href="./generated/options-db.xml"
+             xpointer="configuration-variable-list" />
+  </refsection>
+ </refentry>
 </reference>
diff --git a/nixos/doc/manual/manpages/nixos-build-vms.8 b/nixos/doc/manual/manpages/nixos-build-vms.8
index abf06b5dd5962..6a8f2c42eddfa 100644
--- a/nixos/doc/manual/manpages/nixos-build-vms.8
+++ b/nixos/doc/manual/manpages/nixos-build-vms.8
@@ -1,10 +1,6 @@
 .Dd January 1, 1980
-.\" nixpkgs groff will use Nixpkgs as the OS in the title by default, taking it from
-.\" doc-default-operating-system. mandoc doesn't have this register set by default,
-.\" so we can use it as a groff/mandoc switch.
-.ie ddoc-default-operating-system .Dt nixos-build-vms \&8 "NixOS System Manager's Manual"
-.el .Dt nixos-build-vms 8
-.Os NixOS
+.Dt nixos-build-vms 8
+.Os
 .Sh NAME
 .Nm nixos-build-vms
 .Nd build a network of virtual machines from a network of NixOS configurations
diff --git a/nixos/doc/manual/manpages/nixos-enter.8 b/nixos/doc/manual/manpages/nixos-enter.8
index bc8df31e5f2d8..646f92199d62f 100644
--- a/nixos/doc/manual/manpages/nixos-enter.8
+++ b/nixos/doc/manual/manpages/nixos-enter.8
@@ -1,10 +1,6 @@
 .Dd January 1, 1980
-.\" nixpkgs groff will use Nixpkgs as the OS in the title by default, taking it from
-.\" doc-default-operating-system. mandoc doesn't have this register set by default,
-.\" so we can use it as a groff/mandoc switch.
-.ie ddoc-default-operating-system .Dt nixos-enter \&8 "NixOS System Manager's Manual"
-.el .Dt nixos-enter 8
-.Os NixOS
+.Dt nixos-enter 8
+.Os
 .Sh NAME
 .Nm nixos-enter
 .Nd run a command in a NixOS chroot environment
diff --git a/nixos/doc/manual/manpages/nixos-generate-config.8 b/nixos/doc/manual/manpages/nixos-generate-config.8
index 8b9bb4d42483b..1b95599e156ae 100644
--- a/nixos/doc/manual/manpages/nixos-generate-config.8
+++ b/nixos/doc/manual/manpages/nixos-generate-config.8
@@ -1,10 +1,6 @@
 .Dd January 1, 1980
-.\" nixpkgs groff will use Nixpkgs as the OS in the title by default, taking it from
-.\" doc-default-operating-system. mandoc doesn't have this register set by default,
-.\" so we can use it as a groff/mandoc switch.
-.ie ddoc-default-operating-system .Dt nixos-generate-config \&8 "NixOS System Manager's Manual"
-.el .Dt nixos-generate-config 8
-.Os NixOS
+.Dt nixos-generate-config 8
+.Os
 .Sh NAME
 .Nm nixos-generate-config
 .Nd generate NixOS configuration modules
diff --git a/nixos/doc/manual/manpages/nixos-install.8 b/nixos/doc/manual/manpages/nixos-install.8
index dc0a5da275d88..c6c8ed15224d3 100644
--- a/nixos/doc/manual/manpages/nixos-install.8
+++ b/nixos/doc/manual/manpages/nixos-install.8
@@ -1,10 +1,6 @@
 .Dd January 1, 1980
-.\" nixpkgs groff will use Nixpkgs as the OS in the title by default, taking it from
-.\" doc-default-operating-system. mandoc doesn't have this register set by default,
-.\" so we can use it as a groff/mandoc switch.
-.ie ddoc-default-operating-system .Dt nixos-install \&8 "NixOS System Manager's Manual"
-.el .Dt nixos-install 8
-.Os NixOS
+.Dt nixos-install 8
+.Os
 .Sh NAME
 .Nm nixos-install
 .Nd install bootloader and NixOS
diff --git a/nixos/doc/manual/manpages/nixos-option.8 b/nixos/doc/manual/manpages/nixos-option.8
index 79cc113cfaf2d..28438b03580b1 100644
--- a/nixos/doc/manual/manpages/nixos-option.8
+++ b/nixos/doc/manual/manpages/nixos-option.8
@@ -1,10 +1,6 @@
 .Dd January 1, 1980
-.\" nixpkgs groff will use Nixpkgs as the OS in the title by default, taking it from
-.\" doc-default-operating-system. mandoc doesn't have this register set by default,
-.\" so we can use it as a groff/mandoc switch.
-.ie ddoc-default-operating-system .Dt nixos-option \&8 "NixOS System Manager's Manual"
-.el .Dt nixos-option 8
-.Os NixOS
+.Dt nixos-option 8
+.Os
 .Sh NAME
 .Nm nixos-option
 .Nd inspect a NixOS configuration
diff --git a/nixos/doc/manual/manpages/nixos-rebuild.8 b/nixos/doc/manual/manpages/nixos-rebuild.8
index 8ff59d6da9cfb..64bbbee411d7f 100644
--- a/nixos/doc/manual/manpages/nixos-rebuild.8
+++ b/nixos/doc/manual/manpages/nixos-rebuild.8
@@ -1,10 +1,6 @@
 .Dd January 1, 1980
-.\" nixpkgs groff will use Nixpkgs the OS in the title by default, taking it from
-.\" doc-default-operating-system. mandoc doesn't have this register set by default,
-.\" so we can use it as a groff/mandoc switch.
-.ie ddoc-default-operating-system .Dt nixos-rebuild \&8 "NixOS System Manager's Manual"
-.el .Dt nixos-rebuild 8
-.Os NixOS
+.Dt nixos-rebuild 8
+.Os
 .Sh NAME
 .Nm nixos-rebuild
 .Nd reconfigure a NixOS machine
diff --git a/nixos/doc/manual/manpages/nixos-version.8 b/nixos/doc/manual/manpages/nixos-version.8
index 980ee8b1f49e9..f661611599fb0 100644
--- a/nixos/doc/manual/manpages/nixos-version.8
+++ b/nixos/doc/manual/manpages/nixos-version.8
@@ -1,10 +1,6 @@
 .Dd January 1, 1980
-.\" nixpkgs groff will use Nixpkgs as the OS in the title by default, taking it from
-.\" doc-default-operating-system. mandoc doesn't have this register set by default,
-.\" so we can use it as a groff/mandoc switch.
-.ie ddoc-default-operating-system .Dt nixos-version \&8 "NixOS System Manager's Manual"
-.el .Dt nixos-version 8
-.Os NixOS
+.Dt nixos-version 8
+.Os
 .Sh NAME
 .Nm nixos-version
 .Nd show the NixOS version
diff --git a/nixos/doc/manual/manual.md b/nixos/doc/manual/manual.md
new file mode 100644
index 0000000000000..8cb766eeccf64
--- /dev/null
+++ b/nixos/doc/manual/manual.md
@@ -0,0 +1,56 @@
+# NixOS Manual {#book-nixos-manual}
+## Version @NIXOS_VERSION@
+
+<!--
+  this is the top-level structure file for the nixos manual.
+
+  the manual structure extends the nixpkgs commonmark further with include
+  blocks to allow better organization of input text. there are six types of
+  include blocks: preface, parts, chapters, sections, appendix, and options.
+  each type except `options`` corresponds to the docbook elements of (roughly)
+  the same name, and can itself can further include blocks to denote its
+  substructure.
+
+  non-`options`` include blocks are fenced code blocks that list a number of
+  files to include, in the form
+
+     ```{=include=} <type>
+     <file-name-1>
+     <file-name-2>
+     <...>
+     ```
+
+  `options` include blocks do not list file names but contain a list of key-value
+  pairs that describe the options to be included and how to convert them into
+  elements of the manual output type:
+
+      ```{=include=} options
+      id-prefix: <options id prefix>
+      list-id: <variable list element id>
+      source: <path to options.json>
+      ```
+
+-->
+
+```{=include=} preface
+preface.md
+```
+
+```{=include=} parts
+installation/installation.md
+configuration/configuration.md
+administration/running.md
+development/development.md
+```
+
+```{=include=} chapters
+contributing-to-this-manual.chapter.md
+```
+
+```{=include=} appendix html:into-file=//options.html
+nixos-options.md
+```
+
+```{=include=} appendix html:into-file=//release-notes.html
+release-notes/release-notes.md
+```
diff --git a/nixos/doc/manual/manual.xml b/nixos/doc/manual/manual.xml
deleted file mode 100644
index 4440f8e04baaa..0000000000000
--- a/nixos/doc/manual/manual.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<book xmlns="http://docbook.org/ns/docbook"
-      xmlns:xlink="http://www.w3.org/1999/xlink"
-      xmlns:xi="http://www.w3.org/2001/XInclude"
-      version="5.0"
-      xml:id="book-nixos-manual">
- <info>
-  <title>NixOS Manual</title>
-  <subtitle>Version <xi:include href="./generated/version" parse="text" />
-  </subtitle>
- </info>
- <xi:include href="preface.xml" />
- <xi:include href="installation/installation.xml" />
- <xi:include href="configuration/configuration.xml" />
- <xi:include href="administration/running.xml" />
- <xi:include href="development/development.xml" />
- <xi:include href="./from_md/contributing-to-this-manual.chapter.xml" />
- <appendix xml:id="ch-options">
-  <title>Configuration Options</title>
-  <xi:include href="./generated/options-db.xml"
-                xpointer="configuration-variable-list" />
- </appendix>
- <xi:include href="release-notes/release-notes.xml" />
-</book>
diff --git a/nixos/doc/manual/nixos-options.md b/nixos/doc/manual/nixos-options.md
new file mode 100644
index 0000000000000..33b487c95a2e1
--- /dev/null
+++ b/nixos/doc/manual/nixos-options.md
@@ -0,0 +1,7 @@
+# Configuration Options {#ch-options}
+
+```{=include=} options
+id-prefix: opt-
+list-id: configuration-variable-list
+source: @NIXOS_OPTIONS_JSON@
+```
diff --git a/nixos/doc/manual/preface.md b/nixos/doc/manual/preface.md
new file mode 100644
index 0000000000000..d5e6364780a7d
--- /dev/null
+++ b/nixos/doc/manual/preface.md
@@ -0,0 +1,11 @@
+# Preface {#preface}
+
+This manual describes how to install, use and extend NixOS, a Linux distribution based on the purely functional package management system [Nix](https://nixos.org/nix), that is composed using modules and packages defined in the [Nixpkgs](https://nixos.org/nixpkgs) project.
+
+Additional information regarding the Nix package manager and the Nixpkgs project can be found in respectively the [Nix manual](https://nixos.org/nix/manual) and the [Nixpkgs manual](https://nixos.org/nixpkgs/manual).
+
+If you encounter problems, please report them on the [`Discourse`](https://discourse.nixos.org), the [Matrix room](https://matrix.to/#nix:nixos.org), or on the [`#nixos` channel on Libera.Chat](irc://irc.libera.chat/#nixos). Alternatively, consider [contributing to this manual](#chap-contributing). Bugs should be reported in [NixOS’ GitHub issue tracker](https://github.com/NixOS/nixpkgs/issues).
+
+::: {.note}
+Commands prefixed with `#` have to be run as root, either requiring to login as root user or temporarily switching to it using `sudo` for example.
+:::
diff --git a/nixos/doc/manual/preface.xml b/nixos/doc/manual/preface.xml
deleted file mode 100644
index c0d530c3d1b5b..0000000000000
--- a/nixos/doc/manual/preface.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<preface xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xml:id="preface">
- <title>Preface</title>
- <para>
-  This manual describes how to install, use and extend NixOS, a Linux
-  distribution based on the purely functional package management system
-  <link xlink:href="https://nixos.org/nix">Nix</link>, that is composed
-  using modules and packages defined in the
-  <link xlink:href="https://nixos.org/nixpkgs">Nixpkgs</link> project.
- </para>
- <para>
-  Additional information regarding the Nix package manager and the Nixpkgs
-  project can be found in respectively the
-  <link xlink:href="https://nixos.org/nix/manual">Nix manual</link> and the
-  <link xlink:href="https://nixos.org/nixpkgs/manual">Nixpkgs manual</link>.
- </para>
- <para>
-  If you encounter problems, please report them on the
-  <literal
-   xlink:href="https://discourse.nixos.org">Discourse</literal>,
-  the <link
-   xlink:href="https://matrix.to/#nix:nixos.org">Matrix room</link>,
-  or on the <link
-   xlink:href="irc://irc.libera.chat/#nixos">
-  <literal>#nixos</literal> channel on Libera.Chat</link>.
-  Alternatively, consider <link
-   xlink:href="#chap-contributing">
-   contributing to this manual</link>. Bugs should be
-  reported in
-  <link
-   xlink:href="https://github.com/NixOS/nixpkgs/issues">NixOS’
-  GitHub issue tracker</link>.
- </para>
- <note>
-  <para>
-   Commands prefixed with <literal>#</literal> have to be run as root, either
-   requiring to login as root user or temporarily switching to it using
-   <literal>sudo</literal> for example.
-  </para>
- </note>
-</preface>
diff --git a/nixos/doc/manual/release-notes/release-notes.md b/nixos/doc/manual/release-notes/release-notes.md
new file mode 100644
index 0000000000000..ac61de3793e85
--- /dev/null
+++ b/nixos/doc/manual/release-notes/release-notes.md
@@ -0,0 +1,25 @@
+# Release Notes {#ch-release-notes}
+
+This section lists the release notes for each stable version of NixOS and current unstable revision.
+
+```{=include=} sections
+rl-2305.section.md
+rl-2211.section.md
+rl-2205.section.md
+rl-2111.section.md
+rl-2105.section.md
+rl-2009.section.md
+rl-2003.section.md
+rl-1909.section.md
+rl-1903.section.md
+rl-1809.section.md
+rl-1803.section.md
+rl-1709.section.md
+rl-1703.section.md
+rl-1609.section.md
+rl-1603.section.md
+rl-1509.section.md
+rl-1412.section.md
+rl-1404.section.md
+rl-1310.section.md
+```
diff --git a/nixos/doc/manual/release-notes/release-notes.xml b/nixos/doc/manual/release-notes/release-notes.xml
deleted file mode 100644
index bb5cc677afb80..0000000000000
--- a/nixos/doc/manual/release-notes/release-notes.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<appendix xmlns="http://docbook.org/ns/docbook"
-          xmlns:xlink="http://www.w3.org/1999/xlink"
-          xmlns:xi="http://www.w3.org/2001/XInclude"
-          version="5.0"
-          xml:id="ch-release-notes">
- <title>Release Notes</title>
- <para>
-  This section lists the release notes for each stable version of NixOS and
-  current unstable revision.
- </para>
- <xi:include href="../from_md/release-notes/rl-2305.section.xml" />
- <xi:include href="../from_md/release-notes/rl-2211.section.xml" />
- <xi:include href="../from_md/release-notes/rl-2205.section.xml" />
- <xi:include href="../from_md/release-notes/rl-2111.section.xml" />
- <xi:include href="../from_md/release-notes/rl-2105.section.xml" />
- <xi:include href="../from_md/release-notes/rl-2009.section.xml" />
- <xi:include href="../from_md/release-notes/rl-2003.section.xml" />
- <xi:include href="../from_md/release-notes/rl-1909.section.xml" />
- <xi:include href="../from_md/release-notes/rl-1903.section.xml" />
- <xi:include href="../from_md/release-notes/rl-1809.section.xml" />
- <xi:include href="../from_md/release-notes/rl-1803.section.xml" />
- <xi:include href="../from_md/release-notes/rl-1709.section.xml" />
- <xi:include href="../from_md/release-notes/rl-1703.section.xml" />
- <xi:include href="../from_md/release-notes/rl-1609.section.xml" />
- <xi:include href="../from_md/release-notes/rl-1603.section.xml" />
- <xi:include href="../from_md/release-notes/rl-1509.section.xml" />
- <xi:include href="../from_md/release-notes/rl-1412.section.xml" />
- <xi:include href="../from_md/release-notes/rl-1404.section.xml" />
- <xi:include href="../from_md/release-notes/rl-1310.section.xml" />
-</appendix>
diff --git a/nixos/doc/manual/release-notes/rl-2205.section.md b/nixos/doc/manual/release-notes/rl-2205.section.md
index 24f73dc3f00b6..dce3b35900328 100644
--- a/nixos/doc/manual/release-notes/rl-2205.section.md
+++ b/nixos/doc/manual/release-notes/rl-2205.section.md
@@ -65,7 +65,7 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - [ArchiSteamFarm](https://github.com/JustArchiNET/ArchiSteamFarm), a C# application with primary purpose of idling Steam cards from multiple accounts simultaneously. Available as [services.archisteamfarm](#opt-services.archisteamfarm.enable).
 
-- [BaGet](https://loic-sharma.github.io/BaGet/), a lightweight NuGet and symbol server. Available at [services.baget](#opt-services.baget.enable).
+- [BaGet](https://loic-sharma.github.io/BaGet/), a lightweight NuGet and symbol server. Available at services.baget.
 
 - [bird-lg](https://github.com/xddxdd/bird-lg-go), a BGP looking glass for Bird Routing. Available as [services.bird-lg](#opt-services.bird-lg.package).
 
diff --git a/nixos/doc/manual/release-notes/rl-2305.section.md b/nixos/doc/manual/release-notes/rl-2305.section.md
index bcd277ba6f89b..deeda73d2fb5f 100644
--- a/nixos/doc/manual/release-notes/rl-2305.section.md
+++ b/nixos/doc/manual/release-notes/rl-2305.section.md
@@ -8,8 +8,14 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
 
+- Core version changes:
+
+  - default linux: 5.15 -\> 6.1, all supported kernels available
+
 - Cinnamon has been updated to 5.6, see [the pull request](https://github.com/NixOS/nixpkgs/pull/201328#issue-1449910204) for what is changed.
 
+- KDE Plasma has been updated to v5.27, see [the release notes](https://kde.org/announcements/plasma/5/5.27.0/) for what is changed.
+
 - `nixos-rebuild` now supports an extra `--specialisation` option that can be used to change specialisation for `switch` and `test` commands.
 
 ## New Services {#sec-release-23.05-new-services}
@@ -27,29 +33,56 @@ In addition to numerous new and upgraded packages, this release has the followin
 - [Cloudlog](https://www.magicbug.co.uk/cloudlog/), a web-based Amateur Radio logging application. Available as [services.cloudlog](#opt-services.cloudlog.enable).
 
 - [fzf](https://github.com/junegunn/fzf), a command line fuzzyfinder. Available as [programs.fzf](#opt-programs.fzf.fuzzyCompletion).
+- [readarr](https://github.com/Readarr/Readarr), Book Manager and Automation (Sonarr for Ebooks). Available as [services.readarr](options.html#opt-services.readarr.enable).
+
+- [gemstash](https://github.com/rubygems/gemstash), a RubyGems.org cache and private gem server. Available as [services.gemstash](#opt-services.gemstash.enable).
 
 - [gmediarender](https://github.com/hzeller/gmrender-resurrect), a simple, headless UPnP/DLNA renderer.  Available as [services.gmediarender](options.html#opt-services.gmediarender.enable).
 
 - [stevenblack-blocklist](https://github.com/StevenBlack/hosts), A unified hosts file with base extensions for blocking unwanted websites. Available as [networking.stevenblack](options.html#opt-networking.stevenblack.enable).
 
+- [imaginary](https://github.com/h2non/imaginary), a microservice for high-level image processing that Nextcloud can use to generate previews. Available as [services.imaginary](#opt-services.imaginary.enable).
+
+- [opensearch](https://opensearch.org), a search server alternative to Elasticsearch. Available as [services.opensearch](options.html#opt-services.opensearch.enable).
+
 - [goeland](https://github.com/slurdge/goeland), an alternative to rss2email written in golang with many filters. Available as [services.goeland](#opt-services.goeland.enable).
 
+- [alertmanager-irc-relay](https://github.com/google/alertmanager-irc-relay), a Prometheus Alertmanager IRC Relay. Available as [services.prometheus.alertmanagerIrcRelay](options.html#opt-services.prometheus.alertmanagerIrcRelay.enable).
+
+- [tts](https://github.com/coqui-ai/TTS), a battle-tested deep learning toolkit for Text-to-Speech. Mutiple servers may be configured below [services.tts.servers](#opt-services.tts.servers).
+
 - [atuin](https://github.com/ellie/atuin), a sync server for shell history. Available as [services.atuin](#opt-services.atuin.enable).
 
+- [networkd-dispatcher](https://gitlab.com/craftyguy/networkd-dispatcher), a dispatcher service for systemd-networkd connection status changes. Available as [services.networkd-dispatcher](#opt-services.networkd-dispatcher.enable).
+
 - [mmsd](https://gitlab.com/kop316/mmsd), a lower level daemon that transmits and recieves MMSes. Available as [services.mmsd](#opt-services.mmsd.enable).
 
 - [QDMR](https://dm3mat.darc.de/qdmr/), a GUI application and command line tool for programming DMR radios [programs.qdmr](#opt-programs.qdmr.enable)
 
+- [keyd](https://github.com/rvaiya/keyd), a key remapping daemon for linux. Available as [services.keyd](#opt-services.keyd.enable).
+
 - [v2rayA](https://v2raya.org), a Linux web GUI client of Project V which supports V2Ray, Xray, SS, SSR, Trojan and Pingtunnel. Available as [services.v2raya](options.html#opt-services.v2raya.enable).
 
 - [ulogd](https://www.netfilter.org/projects/ulogd/index.html), a userspace logging daemon for netfilter/iptables related logging. Available as [services.ulogd](options.html#opt-services.ulogd.enable).
 
+- [jellyseerr](https://github.com/Fallenbagel/jellyseerr), a web-based requests manager for Jellyfin, forked from Overseerr. Available as [services.jellyseerr](#opt-services.jellyseerr.enable).
+
 - [photoprism](https://photoprism.app/), a AI-Powered Photos App for the Decentralized Web. Available as [services.photoprism](options.html#opt-services.photoprism.enable).
 
+- [peroxide](https://github.com/ljanyst/peroxide), a fork of the official [ProtonMail bridge](https://github.com/ProtonMail/proton-bridge) that aims to be similar to [Hydroxide](https://github.com/emersion/hydroxide). Available as [services.peroxide](#opt-services.peroxide.enable).
+
 - [autosuspend](https://github.com/languitar/autosuspend), a python daemon that suspends a system if certain conditions are met, or not met.
 
 - [sharing](https://github.com/parvardegr/sharing), a command-line tool to share directories and files from the CLI to iOS and Android devices without the need of an extra client app. Available as [programs.sharing](#opt-programs.sharing.enable).
 
+- [nimdow](https://github.com/avahe-kellenberger/nimdow), a window manager written in Nim, inspired by dwm.
+
+- [woodpecker-agent](https://woodpecker-ci.org/), a simple CI engine with great extensibility. Available as [services.woodpecker-agent](#opt-services.woodpecker-agent.enable).
+
+- [woodpecker-server](https://woodpecker-ci.org/), a simple CI engine with great extensibility. Available as [services.woodpecker-server](#opt-services.woodpecker-server.enable).
+
+- [ReGreet](https://github.com/rharish101/ReGreet), a clean and customizable greeter for greetd. Available as [programs.regreet](#opt-programs.regreet.enable).
+
 ## Backward Incompatibilities {#sec-release-23.05-incompatibilities}
 
 <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
@@ -62,10 +95,16 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - `borgbackup` module now has an option for inhibiting system sleep while backups are running, defaulting to off (not inhibiting sleep), available as [`services.borgbackup.jobs.<name>.inhibitsSleep`](#opt-services.borgbackup.jobs._name_.inhibitsSleep).
 
+- The `ssh` client tool now disables the `~C` escape sequence by default. This can be re-enabled by setting `EnableEscapeCommandline yes`
+
 - `podman` now uses the `netavark` network stack. Users will need to delete all of their local containers, images, volumes, etc, by running `podman system reset --force` once before upgrading their systems.
 
 - `git-bug` has been updated to at least version 0.8.0, which includes backwards incompatible changes. The `git-bug-migration` package can be used to upgrade existing repositories.
 
+- `nushell` has been updated to at least version 0.77.0, which includes potential breaking changes in aliases. The old aliases are now available as `old-alias` but it is recommended you migrate to the new format. See [Reworked aliases](https://www.nushell.sh/blog/2023-03-14-nushell_0_77.html#reworked-aliases-breaking-changes-kubouch).
+
+- `keepassx` and `keepassx2` have been removed, due to upstream [stopping development](https://www.keepassx.org/index.html%3Fp=636.html). Consider [KeePassXC](https://keepassxc.org) as a maintained alternative.
+
 - The `services.kubo.settings` option is now no longer stateful. If you changed any of the options in `services.kubo.settings` in the past and then removed them from your NixOS configuration again, those changes are still in your Kubo configuration file but will now be reset to the default. If you're unsure, you may want to make a backup of your configuration file (probably /var/lib/ipfs/config) and compare after the update.
 
 - The EC2 image module no longer fetches instance metadata in stage-1. This results in a significantly smaller initramfs, since network drivers no longer need to be included, and faster boots, since metadata fetching can happen in parallel with startup of other services.
@@ -85,13 +124,17 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - `tut` has been updated from 1.0.34 to 2.0.0, and now uses the TOML format for the configuration file instead of INI. Additional information can be found [here](https://github.com/RasmusLindroth/tut/releases/tag/2.0.0).
 
+- `i3status-rust` has been updated from 0.22.0 to 0.30.5, and this brings many changes to its configuration format. Additional information can be found [here](https://github.com/greshake/i3status-rust/blob/v0.30.0/NEWS.md).
+
 - The `wordpress` derivation no longer contains any builtin plugins or themes. If you need them you have to add them back to prevent your site from breaking. You can find them in `wordpressPackages.{plugins,themes}`.
 
 - `llvmPackages_rocm.llvm` will not contain `clang` or `compiler-rt`. `llvmPackages_rocm.clang` will not contain `llvm`. `llvmPackages_rocm.clangNoCompilerRt` has been removed in favor of using `llvmPackages_rocm.clang-unwrapped`.
 
+- `services.xserver.desktopManager.plasma5.excludePackages` has been moved to `environment.plasma5.excludePackages`, for consistency with other Desktop Environments
+
 - The EC2 image module previously detected and automatically mounted ext3-formatted instance store devices and partitions in stage-1 (initramfs), storing `/tmp` on the first discovered device. This behaviour, which only catered to very specific use cases and could not be disabled, has been removed. Users relying on this should provide their own implementation, and probably use ext4 and perform the mount in stage-2.
 
-- `teleport` has been upgraded to major version 11. Please see upstream [upgrade instructions](https://goteleport.com/docs/setup/operations/upgrading/) and [release notes](https://goteleport.com/docs/changelog/#1100).
+- `teleport` has been upgraded from major version 10 to major version 12. Please see upstream [upgrade instructions](https://goteleport.com/docs/setup/operations/upgrading/) and release notes for versions [11](https://goteleport.com/docs/changelog/#1100) and [12](https://goteleport.com/docs/changelog/#1201). 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 11.x version by setting `services.teleport.package = pkgs.teleport_11`. Afterwards, this option can be removed to upgrade to the default version (12).
 
 - The EC2 image module previously detected and activated swap-formatted instance store devices and partitions in stage-1 (initramfs). This behaviour has been removed. Users relying on this should provide their own implementation.
 
@@ -99,10 +142,16 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - Calling `makeSetupHook` without passing a `name` argument is deprecated.
 
+- `lib.systems.examples.ghcjs` and consequently `pkgsCross.ghcjs` now use the target triplet `javascript-unknown-ghcjs` instead of `js-unknown-ghcjs`. This has been done to match an [upstream decision](https://gitlab.haskell.org/ghc/ghc/-/commit/6636b670233522f01d002c9b97827d00289dbf5c) to follow Cabal's platform naming more closely. Nixpkgs will also reject `js` as an architecture name.
+
+- The `cosmoc` package has been removed. The upstream scripts in `cosmocc` should be used instead.
+
 - Qt 5.12 and 5.14 have been removed, as the corresponding branches have been EOL upstream for a long time. This affected under 10 packages in nixpkgs, largely unmaintained upstream as well, however, out-of-tree package expressions may need to be updated manually.
 
 - The [services.wordpress.sites.&lt;name&gt;.plugins](#opt-services.wordpress.sites._name_.plugins) and [services.wordpress.sites.&lt;name&gt;.themes](#opt-services.wordpress.sites._name_.themes) options have been converted from sets to attribute sets to allow for consumers to specify explicit install paths via attribute name.
 
+- `protonmail-bridge` package has been updated to v3.0 and the CLI executable is now named bridge instead of protonmail-bridge to be more in line with upstream.
+
 - Nebula now runs as a system user and group created for each nebula network, using the `CAP_NET_ADMIN` ambient capability on launch rather than starting as root. Ensure that any files each Nebula instance needs to access are owned by the correct user and group, by default `nebula-${networkName}`.
 
 - In `mastodon` it is now necessary to specify location of file with `PostgreSQL` database password. In `services.mastodon.database.passwordFile` parameter default value `/var/lib/mastodon/secrets/db-password` has been changed to `null`.
@@ -113,6 +162,12 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - Deprecated `xlibsWrapper` transitional package has been removed in favour of direct use of its constitutents: `xorg.libX11`, `freetype` and others.
 
+- The latest available version of Nextcloud is v26 (available as `pkgs.nextcloud26`) which uses PHP 8.2 as interpreter by default. The installation logic is as follows:
+  - If `system.stateVersion` is >=23.05, `pkgs.nextcloud26` will be installed by default.
+  - If `system.stateVersion` is >=22.11, `pkgs.nextcloud25` will be installed by default.
+  - Please note that an upgrade from v24 (or older) to v26 directly is not possible. Please upgrade to `nextcloud25` (or earlier) first. Nextcloud prohibits skipping major versions while upgrading. You can upgrade by declaring [`services.nextcloud.package = pkgs.nextcloud25;`](options.html#opt-services.nextcloud.package).
+  - It's recommended to use the latest version available (i.e. v26) and to specify that using `services.nextcloud.package`.
+
 - .NET 5.0 was removed due to being end-of-life, use a newer, supported .NET version - https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core
 
 - The iputils package, which is installed by default, no longer provides the
@@ -120,12 +175,20 @@ In addition to numerous new and upgraded packages, this release has the followin
   [upstream's release notes](https://github.com/iputils/iputils/releases/tag/20221126)
   for more details and available replacements.
 
+- [services.xserver.videoDrivers](options.html#opt-services.xserver.videoDrivers) now defaults to the `modesetting` driver over device-specific ones. The `radeon`, `amdgpu` and `nouveau` drivers are still available, but effectively unmaintained and not recommended for use.
+
+- conntrack helper autodetection has been removed from kernels 6.0 and up upstream, and an assertion was added to ensure things don't silently stop working. Migrate your configuration to assign helpers explicitly or use an older LTS kernel branch as a temporary workaround.
+
+- The `baget` package and module was removed due to being unmaintained.
+
 ## Other Notable Changes {#sec-release-23.05-notable-changes}
 
 <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
 
 - `vim_configurable` has been renamed to `vim-full` to avoid confusion: `vim-full`'s build-time features are configurable, but both `vim` and `vim-full` are _customizable_ (in the sense of user configuration, like vimrc).
 
+- Pantheon now defaults to Mutter 42 and GNOME settings daemon 42, all Pantheon packages are now tracking elementary OS 7 updates.
+
 - The module for the application firewall `opensnitch` got the ability to configure rules. Available as [services.opensnitch.rules](#opt-services.opensnitch.rules)
 
 - The module `usbmuxd` now has the ability to change the package used by the daemon. In case you're experiencing issues with `usbmuxd` you can try an alternative program like `usbmuxd2`. Available as [services.usbmuxd.package](#opt-services.usbmuxd.package)
@@ -150,6 +213,8 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - NixOS now defaults to using nsncd (a non-caching reimplementation in Rust) as NSS lookup dispatcher, instead of the buggy and deprecated glibc-provided nscd. If you need to switch back, set `services.nscd.enableNsncd = false`, but please open an issue in nixpkgs so your issue can be fixed.
 
+- `services.borgmatic` now allows for multiple configurations, placed in `/etc/borgmatic.d/`, you can define them with `services.borgmatic.configurations`.
+
 - The `dnsmasq` service now takes configuration via the
   `services.dnsmasq.settings` attribute set. The option
   `services.dnsmasq.extraConfig` will be deprecated when NixOS 22.11 reaches
@@ -163,6 +228,8 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - To reduce closure size in `nixos/modules/profiles/minimal.nix` profile disabled installation documentations and manuals. Also disabled `logrotate` and `udisks2` services.
 
+- To reduce closure size in `nixos/modules/installer/netboot/netboot-minimal.nix` profile disabled load linux firmwares, pre-installing the complete stdenv and `networking.wireless` service.
+
 - The minimal ISO image now uses the `nixos/modules/profiles/minimal.nix` profile.
 
 - The `ghcWithPackages` and `ghcWithHoogle` wrappers will now also symlink GHC's
@@ -172,6 +239,8 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - `mastodon` now supports connection to a remote `PostgreSQL` database.
 
+- `nextcloud` has an option to enable SSE-C in S3.
+
 - `services.peertube` now requires you to specify the secret file `secrets.secretsFile`. It can be generated by running `openssl rand -hex 32`.
   Before upgrading, read the release notes for PeerTube:
     - [Release v5.0.0](https://github.com/Chocobozzz/PeerTube/releases/tag/v5.0.0)
@@ -189,6 +258,11 @@ In addition to numerous new and upgraded packages, this release has the followin
     [headscale's example configuration](https://github.com/juanfont/headscale/blob/main/config-example.yaml)
     can be directly written as attribute-set in Nix within this option.
 
+- The `hardware.video.hidpi.enable` was renamed to `fonts.optimizeForVeryHighDPI` to be consistent with what it actually does.
+  They disable by default: antialiasing, hinting and LCD filter for subpixel rendering. They can be overridden if you experience problems with font rendering.
+  On Xorg, the default cursor is upscaled.
+  Please see the documentation for the new option to decide if you want to keep it enabled.
+
 - `nixos/lib/make-disk-image.nix` can now mutate EFI variables, run user-provided EFI firmware or variable templates. This is now extensively documented in the NixOS manual.
 
 - `services.grafana` listens only on localhost by default again. This was changed to upstreams default of `0.0.0.0` by accident in the freeform setting conversion.
@@ -243,14 +317,18 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - The `unifi-poller` package and corresponding NixOS module have been renamed to `unpoller` to match upstream.
 
+- `protonmail-bridge` package has been updated to v3.0 and the CLI executable is now named bridge instead of protonmail-bridge to be more in line with upstream.
+
 - The new option `services.tailscale.useRoutingFeatures` controls various settings for using Tailscale features like exit nodes and subnet routers. If you wish to use your machine as an exit node, you can set this setting to `server`, otherwise if you wish to use an exit node you can set this setting to `client`. The strict RPF warning has been removed as the RPF will be loosened automatically based on the value of this setting.
 
-- [Xastir](https://xastir.org/index.php/Main_Page) can now access AX.25 interfaces via the `libax25` package.
+- `openjdk` from version 11 and above is not build with `openjfx` (i.e.: JavaFX) support by default anymore. You can re-enable it by overriding, e.g.: `openjdk11.override { enableJavaFX = true; };`.
 
-- `tvbrowser-bin` was removed, and now `tvbrowser` is built from source.
+- [Xastir](https://xastir.org/index.php/Main_Page) can now access AX.25 interfaces via the `libax25` package.
 
 - `nixos-version` now accepts `--configuration-revision` to display more information about the current generation revision
 
 - The option `services.nomad.extraSettingsPlugins` has been fixed to allow more than one plugin in the path.
 
 - The option `services.prometheus.exporters.pihole.interval` does not exist anymore and has been removed.
+
+- `k3s` can now be configured with an EnvironmentFile for its systemd service, allowing secrets to be provided without ending up in the Nix Store.
diff --git a/nixos/doc/manual/shell.nix b/nixos/doc/manual/shell.nix
deleted file mode 100644
index e5ec9b8f97f7d..0000000000000
--- a/nixos/doc/manual/shell.nix
+++ /dev/null
@@ -1,8 +0,0 @@
-let
-  pkgs = import ../../.. { };
-in
-pkgs.mkShell {
-  name = "nixos-manual";
-
-  packages = with pkgs; [ xmlformat jing xmloscopy ruby ];
-}
diff --git a/nixos/doc/varlistentry-fixer.rb b/nixos/doc/varlistentry-fixer.rb
deleted file mode 100755
index 02168016b554b..0000000000000
--- a/nixos/doc/varlistentry-fixer.rb
+++ /dev/null
@@ -1,124 +0,0 @@
-#!/usr/bin/env ruby
-
-# This script is written intended as a living, evolving tooling
-# to fix oopsies within the docbook documentation.
-#
-# This is *not* a formatter. It, instead, handles some known cases
-# where something bad happened, and fixing it manually is tedious.
-#
-# Read the code to see the different cases it handles.
-#
-# ALWAYS `make format` after fixing with this!
-# ALWAYS read the changes, this tool isn't yet proven to be always right.
-
-require "rexml/document"
-include REXML
-
-if ARGV.length < 1 then
-  $stderr.puts "Needs a filename."
-  exit 1
-end
-
-filename = ARGV.shift
-doc = Document.new(File.open(filename))
-
-$touched = false
-
-# Fixing varnames having a sibling element without spacing.
-# This is to fix an initial `xmlformat` issue where `term`
-# would mangle as spaces.
-#
-#   <varlistentry>
-#    <term><varname>types.separatedString</varname><replaceable>sep</replaceable> <----
-#    </term>
-#    ...
-#
-# Generates: types.separatedStringsep
-#                               ^^^^
-#
-# <varlistentry xml:id='fun-makeWrapper'>
-#  <term>
-#   <function>makeWrapper</function><replaceable>executable</replaceable><replaceable>wrapperfile</replaceable><replaceable>args</replaceable>  <----
-#  </term>
-#
-# Generates: makeWrapperexecutablewrapperfileargs
-#                     ^^^^      ^^^^    ^^  ^^
-#
-#    <term>
-#     <option>--option</option><replaceable>name</replaceable><replaceable>value</replaceable> <-----
-#    </term>
-#
-# Generates: --optionnamevalue
-#                   ^^  ^^
-doc.elements.each("//varlistentry/term") do |term|
-  ["varname", "function", "option", "replaceable"].each do |prev_name|
-    term.elements.each(prev_name) do |el|
-      if el.next_element and
-          el.next_element.name == "replaceable" and
-          el.next_sibling_node.class == Element
-        then
-        $touched = true
-        term.insert_after(el, Text.new(" "))
-      end
-    end
-  end
-end
-
-
-
-#  <cmdsynopsis>
-#   <command>nixos-option</command>
-#   <arg>
-#    <option>-I</option><replaceable>path</replaceable>        <------
-#   </arg>
-#
-# Generates: -Ipath
-#             ^^
-doc.elements.each("//cmdsynopsis/arg") do |term|
-  ["option", "replaceable"].each do |prev_name|
-    term.elements.each(prev_name) do |el|
-      if el.next_element and
-        el.next_element.name == "replaceable" and
-        el.next_sibling_node.class == Element
-      then
-        $touched = true
-        term.insert_after(el, Text.new(" "))
-      end
-    end
-  end
-end
-
-#  <cmdsynopsis>
-#   <arg>
-#    <group choice='req'>
-#    <arg choice='plain'>
-#     <option>--profile-name</option>
-#    </arg>
-#
-#    <arg choice='plain'>
-#     <option>-p</option>
-#    </arg>
-#     </group><replaceable>name</replaceable>   <----
-#   </arg>
-#
-# Generates: [{--profile-name | -p }name]
-#                                   ^^^^
-doc.elements.each("//cmdsynopsis/arg") do |term|
-  ["group"].each do |prev_name|
-    term.elements.each(prev_name) do |el|
-      if el.next_element and
-        el.next_element.name == "replaceable" and
-        el.next_sibling_node.class == Element
-      then
-        $touched = true
-        term.insert_after(el, Text.new(" "))
-      end
-    end
-  end
-end
-
-
-if $touched then
-  doc.context[:attribute_quote] = :quote
-  doc.write(output: File.open(filename, "w"))
-end
diff --git a/nixos/doc/xmlformat.conf b/nixos/doc/xmlformat.conf
deleted file mode 100644
index c3f39c7fd81b8..0000000000000
--- a/nixos/doc/xmlformat.conf
+++ /dev/null
@@ -1,72 +0,0 @@
-#
-# DocBook Configuration file for "xmlformat"
-# see http://www.kitebird.com/software/xmlformat/
-# 10 Sept. 2004
-#
-
-# Only block elements
-ackno address appendix article biblioentry bibliography bibliomixed \
-biblioset blockquote book bridgehead callout calloutlist caption caution \
-chapter chapterinfo classsynopsis cmdsynopsis colophon constraintdef \
-constructorsynopsis dedication destructorsynopsis entry epigraph equation example \
-figure formalpara funcsynopsis glossary glossdef glossdiv glossentry glosslist \
-glosssee glossseealso graphic graphicco highlights imageobjectco important \
-index indexdiv indexentry indexinfo info informalequation informalexample \
-informalfigure informaltable legalnotice literallayout lot lotentry mediaobject \
-mediaobjectco msgmain msgset note orderedlist para part preface primaryie \
-procedure qandadiv qandaentry qandaset refentry refentrytitle reference \
-refnamediv refsect1 refsect2 refsect3 refsection revhistory screenshot sect1 \
-sect2 sect3 sect4 sect5 section seglistitem set setindex sidebar simpara \
-simplesect step substeps synopfragment synopsis table term title \
-toc variablelist varlistentry warning itemizedlist listitem \
-footnote colspec partintro row simplelist subtitle tbody tgroup thead tip
-  format      block
-  normalize   no
-
-
-#appendix bibliography chapter glossary preface reference
-#  element-break   3
-
-sect1 section
-  element-break   2
-
-
-#
-para abstract
-  format       block
-  entry-break  1
-  exit-break   1
-  normalize    yes
-
-title
-  format       block
-  normalize = yes
-  entry-break = 0
-  exit-break = 0
-
-# Inline elements
-abbrev accel acronym action application citation citebiblioid citerefentry citetitle \
-classname co code command computeroutput constant country database date email emphasis \
-envar errorcode errorname errortext errortype exceptionname fax filename \
-firstname firstterm footnoteref foreignphrase funcdef funcparams function \
-glossterm group guibutton guiicon guilabel guimenu guimenuitem guisubmenu \
-hardware holder honorific indexterm inlineequation inlinegraphic inlinemediaobject \
-interface interfacename \
-keycap keycode keycombo keysym lineage link literal manvolnum markup medialabel \
-menuchoice methodname methodparam modifier mousebutton olink ooclass ooexception \
-oointerface option optional otheraddr othername package paramdef parameter personname \
-phrase pob postcode productname prompt property quote refpurpose replaceable \
-returnvalue revnumber sgmltag state street structfield structname subscript \
-superscript surname symbol systemitem token trademark type ulink userinput \
-uri varargs varname void wordasword xref year mathphrase member tag
-  format       inline
-
-programlisting screen
-  format       verbatim
-  entry-break = 0
-  exit-break = 0
-
-# This is needed so that the spacing inside those tags is kept.
-term cmdsynopsis arg
-  normalize yes
-  format    block
diff --git a/nixos/lib/make-disk-image.nix b/nixos/lib/make-disk-image.nix
index 365fc1f03a5bc..d641d1289fe44 100644
--- a/nixos/lib/make-disk-image.nix
+++ b/nixos/lib/make-disk-image.nix
@@ -154,6 +154,9 @@ To solve this, you can run `fdisk -l $image` and generate `dd if=$image of=$imag
 , # Shell code executed after the VM has finished.
   postVM ? ""
 
+, # Guest memory size
+  memSize ? 1024
+
 , # Copy the contents of the Nix store to the root of the image and
   # skip further setup. Incompatible with `contents`,
   # `installBootLoader` and `configFile`.
@@ -525,7 +528,7 @@ let format' = format; in let
           "-drive if=pflash,format=raw,unit=1,file=$efiVars"
         ]
       );
-      memSize = 1024;
+      inherit memSize;
     } ''
       export PATH=${binPath}:$PATH
 
diff --git a/nixos/lib/make-multi-disk-zfs-image.nix b/nixos/lib/make-multi-disk-zfs-image.nix
index f9046a485a7dd..ecbde44971a99 100644
--- a/nixos/lib/make-multi-disk-zfs-image.nix
+++ b/nixos/lib/make-multi-disk-zfs-image.nix
@@ -73,6 +73,9 @@
 , # Shell code executed after the VM has finished.
   postVM ? ""
 
+, # Guest memory size
+  memSize ? 1024
+
 , name ? "nixos-disk-image"
 
 , # Disk image format, one of qcow2, qcow2-compressed, vdi, vpc, raw.
@@ -242,6 +245,7 @@ let
       {
         QEMU_OPTS = "-drive file=$bootDiskImage,if=virtio,cache=unsafe,werror=report"
          + " -drive file=$rootDiskImage,if=virtio,cache=unsafe,werror=report";
+         inherit memSize;
         preVM = ''
           PATH=$PATH:${pkgs.qemu_kvm}/bin
           mkdir $out
diff --git a/nixos/lib/make-options-doc/default.nix b/nixos/lib/make-options-doc/default.nix
index 09b0191d2bb8e..a2385582a014d 100644
--- a/nixos/lib/make-options-doc/default.nix
+++ b/nixos/lib/make-options-doc/default.nix
@@ -91,18 +91,24 @@ let
 in rec {
   inherit optionsNix;
 
-  optionsAsciiDoc = pkgs.runCommand "options.adoc" {} ''
-    ${pkgs.python3Minimal}/bin/python ${./generateDoc.py} \
-      --format asciidoc \
+  optionsAsciiDoc = pkgs.runCommand "options.adoc" {
+    nativeBuildInputs = [ pkgs.nixos-render-docs ];
+  } ''
+    nixos-render-docs -j $NIX_BUILD_CORES options asciidoc \
+      --manpage-urls ${pkgs.path + "/doc/manpage-urls.json"} \
+      --revision ${lib.escapeShellArg revision} \
       ${optionsJSON}/share/doc/nixos/options.json \
-      > $out
+      $out
   '';
 
-  optionsCommonMark = pkgs.runCommand "options.md" {} ''
-    ${pkgs.python3Minimal}/bin/python ${./generateDoc.py} \
-      --format commonmark \
+  optionsCommonMark = pkgs.runCommand "options.md" {
+    nativeBuildInputs = [ pkgs.nixos-render-docs ];
+  } ''
+    nixos-render-docs -j $NIX_BUILD_CORES options commonmark \
+      --manpage-urls ${pkgs.path + "/doc/manpage-urls.json"} \
+      --revision ${lib.escapeShellArg revision} \
       ${optionsJSON}/share/doc/nixos/options.json \
-      > $out
+      $out
   '';
 
   optionsJSON = pkgs.runCommand "options.json"
@@ -152,7 +158,7 @@ in rec {
       pkgs.nixos-render-docs
     ];
   } ''
-    nixos-render-docs options docbook \
+    nixos-render-docs -j $NIX_BUILD_CORES options docbook \
       --manpage-urls ${pkgs.path + "/doc/manpage-urls.json"} \
       --revision ${lib.escapeShellArg revision} \
       --document-type ${lib.escapeShellArg documentType} \
diff --git a/nixos/lib/make-options-doc/generateDoc.py b/nixos/lib/make-options-doc/generateDoc.py
deleted file mode 100644
index 07884ed657e49..0000000000000
--- a/nixos/lib/make-options-doc/generateDoc.py
+++ /dev/null
@@ -1,112 +0,0 @@
-import argparse
-import json
-import sys
-
-formats = ['commonmark', 'asciidoc']
-
-parser = argparse.ArgumentParser(
-    description = 'Generate documentation for a set of JSON-formatted NixOS options'
-)
-parser.add_argument(
-    'nix_options_path',
-    help = 'a path to a JSON file containing the NixOS options'
-)
-parser.add_argument(
-    '-f',
-    '--format',
-    choices = formats,
-    required = True,
-    help = f'the documentation format to generate'
-)
-
-args = parser.parse_args()
-
-class OptionsEncoder(json.JSONEncoder):
-    def encode(self, obj):
-        # Unpack literal expressions and other Nix types.
-        # Don't escape the strings: they were escaped when initially serialized to JSON.
-        if isinstance(obj, dict):
-            _type = obj.get('_type')
-            if _type is not None:
-                if _type == 'literalExpression' or _type == 'literalDocBook':
-                    return obj['text']
-
-                if _type == 'derivation':
-                    return obj['name']
-
-                raise Exception(f'Unexpected type `{_type}` in {json.dumps(obj)}')
-
-        return super().encode(obj)
-
-def generate_commonmark(options):
-    for (name, value) in options.items():
-        print('##', name.replace('<', '&lt;').replace('>', '&gt;'))
-        print(value['description'])
-        print()
-        if 'type' in value:
-            print('*_Type_*')
-            print ('```')
-            print(value['type'])
-            print ('```')
-        print()
-        print()
-        if 'default' in value:
-            print('*_Default_*')
-            print('```')
-            print(json.dumps(value['default'], cls=OptionsEncoder, ensure_ascii=False, separators=(',', ':')))
-            print('```')
-        print()
-        print()
-        if 'example' in value:
-            print('*_Example_*')
-            print('```')
-            print(json.dumps(value['example'], cls=OptionsEncoder, ensure_ascii=False, separators=(',', ':')))
-            print('```')
-        print()
-        print()
-
-# TODO: declarations: link to github
-def generate_asciidoc(options):
-    for (name, value) in options.items():
-        print(f'== {name}')
-        print()
-        print(value['description'])
-        print()
-        print('[discrete]')
-        print('=== details')
-        print()
-        print(f'Type:: {value["type"]}')
-        if 'default' in value:
-            print('Default::')
-            print('+')
-            print('----')
-            print(json.dumps(value['default'], cls=OptionsEncoder, ensure_ascii=False, separators=(',', ':')))
-            print('----')
-            print()
-        else:
-            print('No Default:: {blank}')
-        if value['readOnly']:
-            print('Read Only:: {blank}')
-        else:
-            print()
-        if 'example' in value:
-            print('Example::')
-            print('+')
-            print('----')
-            print(json.dumps(value['example'], cls=OptionsEncoder, ensure_ascii=False, separators=(',', ':')))
-            print('----')
-            print()
-        else:
-            print('No Example:: {blank}')
-        print()
-
-with open(args.nix_options_path) as nix_options_json:
-    options = json.load(nix_options_json)
-
-    if args.format == 'commonmark':
-        generate_commonmark(options)
-    elif args.format == 'asciidoc':
-        generate_asciidoc(options)
-    else:
-        raise Exception(f'Unsupported documentation format `--format {args.format}`')
-
diff --git a/nixos/lib/test-driver/test_driver/driver.py b/nixos/lib/test-driver/test_driver/driver.py
index de6abbb4679e2..ad52f365737c1 100644
--- a/nixos/lib/test-driver/test_driver/driver.py
+++ b/nixos/lib/test-driver/test_driver/driver.py
@@ -179,7 +179,6 @@ class Driver:
             start_command=cmd,
             name=name,
             keep_vm_state=args.get("keep_vm_state", False),
-            allow_reboot=args.get("allow_reboot", False),
         )
 
     def serial_stdout_on(self) -> None:
diff --git a/nixos/lib/test-driver/test_driver/machine.py b/nixos/lib/test-driver/test_driver/machine.py
index 0db7930f496b2..9de98c217a583 100644
--- a/nixos/lib/test-driver/test_driver/machine.py
+++ b/nixos/lib/test-driver/test_driver/machine.py
@@ -144,7 +144,7 @@ class StartCommand:
         self,
         monitor_socket_path: Path,
         shell_socket_path: Path,
-        allow_reboot: bool = False,  # TODO: unused, legacy?
+        allow_reboot: bool = False,
     ) -> str:
         display_opts = ""
         display_available = any(x in os.environ for x in ["DISPLAY", "WAYLAND_DISPLAY"])
@@ -152,16 +152,14 @@ class StartCommand:
             display_opts += " -nographic"
 
         # qemu options
-        qemu_opts = ""
-        qemu_opts += (
-            ""
-            if allow_reboot
-            else " -no-reboot"
+        qemu_opts = (
             " -device virtio-serial"
             " -device virtconsole,chardev=shell"
             " -device virtio-rng-pci"
             " -serial stdio"
         )
+        if not allow_reboot:
+            qemu_opts += " -no-reboot"
         # TODO: qemu script already catpures this env variable, legacy?
         qemu_opts += " " + os.environ.get("QEMU_OPTS", "")
 
@@ -195,9 +193,10 @@ class StartCommand:
         shared_dir: Path,
         monitor_socket_path: Path,
         shell_socket_path: Path,
+        allow_reboot: bool,
     ) -> subprocess.Popen:
         return subprocess.Popen(
-            self.cmd(monitor_socket_path, shell_socket_path),
+            self.cmd(monitor_socket_path, shell_socket_path, allow_reboot),
             stdin=subprocess.PIPE,
             stdout=subprocess.PIPE,
             stderr=subprocess.STDOUT,
@@ -312,7 +311,6 @@ class Machine:
 
     start_command: StartCommand
     keep_vm_state: bool
-    allow_reboot: bool
 
     process: Optional[subprocess.Popen]
     pid: Optional[int]
@@ -337,13 +335,11 @@ class Machine:
         start_command: StartCommand,
         name: str = "machine",
         keep_vm_state: bool = False,
-        allow_reboot: bool = False,
         callbacks: Optional[List[Callable]] = None,
     ) -> None:
         self.out_dir = out_dir
         self.tmp_dir = tmp_dir
         self.keep_vm_state = keep_vm_state
-        self.allow_reboot = allow_reboot
         self.name = name
         self.start_command = start_command
         self.callbacks = callbacks if callbacks is not None else []
@@ -741,9 +737,10 @@ class Machine:
             self.connected = True
 
     def screenshot(self, filename: str) -> None:
-        word_pattern = re.compile(r"^\w+$")
-        if word_pattern.match(filename):
-            filename = os.path.join(self.out_dir, f"{filename}.png")
+        if "." not in filename:
+            filename += ".png"
+        if "/" not in filename:
+            filename = os.path.join(self.out_dir, filename)
         tmp = f"{filename}.ppm"
 
         with self.nested(
@@ -874,7 +871,7 @@ class Machine:
         self.process.stdin.write(chars.encode())
         self.process.stdin.flush()
 
-    def start(self) -> None:
+    def start(self, allow_reboot: bool = False) -> None:
         if self.booted:
             return
 
@@ -898,6 +895,7 @@ class Machine:
             self.shared_dir,
             self.monitor_path,
             self.shell_path,
+            allow_reboot,
         )
         self.monitor, _ = monitor_socket.accept()
         self.shell, _ = shell_socket.accept()
@@ -946,6 +944,15 @@ class Machine:
         self.send_monitor_command("quit")
         self.wait_for_shutdown()
 
+    def reboot(self) -> None:
+        """Press Ctrl+Alt+Delete in the guest.
+
+        Prepares the machine to be reconnected which is useful if the
+        machine was started with `allow_reboot = True`
+        """
+        self.send_key(f"ctrl-alt-delete")
+        self.connected = False
+
     def wait_for_x(self) -> None:
         """Wait until it is possible to connect to the X server.  Note that
         testing the existence of /tmp/.X11-unix/X0 is insufficient.
diff --git a/nixos/lib/testing/meta.nix b/nixos/lib/testing/meta.nix
index 65754fe3c5419..805b7520edff3 100644
--- a/nixos/lib/testing/meta.nix
+++ b/nixos/lib/testing/meta.nix
@@ -22,7 +22,7 @@ in
           };
           timeout = lib.mkOption {
             type = types.nullOr types.int;
-            default = null; # NOTE: null values are filtered out by `meta`.
+            default = 3600;  # 1 hour
             description = mdDoc ''
               The [{option}`test`](#test-opt-test)'s [`meta.timeout`](https://nixos.org/manual/nixpkgs/stable/#var-meta-timeout) in seconds.
             '';
diff --git a/nixos/modules/config/console.nix b/nixos/modules/config/console.nix
index 854d7acf92326..1e8bb78f302d6 100644
--- a/nixos/modules/config/console.nix
+++ b/nixos/modules/config/console.nix
@@ -21,7 +21,7 @@ let
   # Sadly, systemd-vconsole-setup doesn't support binary keymaps.
   vconsoleConf = pkgs.writeText "vconsole.conf" ''
     KEYMAP=${cfg.keyMap}
-    FONT=${cfg.font}
+    ${optionalString (cfg.font != null) "FONT=${cfg.font}"}
   '';
 
   consoleEnv = kbd: pkgs.buildEnv {
@@ -45,14 +45,19 @@ in
     };
 
     font = mkOption {
-      type = with types; either str path;
-      default = "Lat2-Terminus16";
+      type = with types; nullOr (either str path);
+      default = null;
       example = "LatArCyrHeb-16";
       description = mdDoc ''
-        The font used for the virtual consoles.  Leave empty to use
-        whatever the {command}`setfont` program considers the
-        default font.
-        Can be either a font name or a path to a PSF font file.
+        The font used for the virtual consoles.
+        Can be `null`, a font name, or a path to a PSF font file.
+
+        Use `null` to let the kernel choose a built-in font.
+        The default is 8x16, and, as of Linux 5.3, Terminus 32 bold for display
+        resolutions of 2560x1080 and higher.
+        These fonts cover the [IBM437][] character set.
+
+        [IBM437]: https://en.wikipedia.org/wiki/Code_page_437
       '';
     };
 
@@ -151,7 +156,7 @@ in
           printf "\033%%${if isUnicode then "G" else "@"}" >> /dev/console
           loadkmap < ${optimizedKeymap}
 
-          ${optionalString cfg.earlySetup ''
+          ${optionalString (cfg.earlySetup && cfg.font != null) ''
             setfont -C /dev/console $extraUtils/share/consolefonts/font.psf
           ''}
         '');
@@ -168,8 +173,7 @@ in
           "${config.boot.initrd.systemd.package.kbd}/bin/setfont"
           "${config.boot.initrd.systemd.package.kbd}/bin/loadkeys"
           "${config.boot.initrd.systemd.package.kbd.gzip}/bin/gzip" # Fonts and keyboard layouts are compressed
-          "${config.boot.initrd.systemd.package.kbd.gzip}/bin/.gzip-wrapped"
-        ] ++ optionals (hasPrefix builtins.storeDir cfg.font) [
+        ] ++ optionals (cfg.font != null && hasPrefix builtins.storeDir cfg.font) [
           "${cfg.font}"
         ] ++ optionals (hasPrefix builtins.storeDir cfg.keyMap) [
           "${cfg.keyMap}"
@@ -196,7 +200,7 @@ in
         ];
       })
 
-      (mkIf (cfg.earlySetup && !config.boot.initrd.systemd.enable) {
+      (mkIf (cfg.earlySetup && cfg.font != null && !config.boot.initrd.systemd.enable) {
         boot.initrd.extraUtilsCommands = ''
           mkdir -p $out/share/consolefonts
           ${if substring 0 1 cfg.font == "/" then ''
diff --git a/nixos/modules/config/fonts/fonts.nix b/nixos/modules/config/fonts/fonts.nix
index c0619fa31a327..efbd554582fcb 100644
--- a/nixos/modules/config/fonts/fonts.nix
+++ b/nixos/modules/config/fonts/fonts.nix
@@ -3,29 +3,7 @@
 with lib;
 
 let
-  # A scalable variant of the X11 "core" cursor
-  #
-  # If not running a fancy desktop environment, the cursor is likely set to
-  # the default `cursor.pcf` bitmap font. This is 17px wide, so it's very
-  # small and almost invisible on 4K displays.
-  fontcursormisc_hidpi = pkgs.xorg.fontxfree86type1.overrideAttrs (old:
-    let
-      # The scaling constant is 230/96: the scalable `left_ptr` glyph at
-      # about 23 points is rendered as 17px, on a 96dpi display.
-      # Note: the XLFD font size is in decipoints.
-      size = 2.39583 * config.services.xserver.dpi;
-      sizeString = builtins.head (builtins.split "\\." (toString size));
-    in
-    {
-      postInstall = ''
-        alias='cursor -xfree86-cursor-medium-r-normal--0-${sizeString}-0-0-p-0-adobe-fontspecific'
-        echo "$alias" > $out/lib/X11/fonts/Type1/fonts.alias
-      '';
-    });
-
-  hasHidpi =
-    config.hardware.video.hidpi.enable &&
-    config.services.xserver.dpi != null;
+  cfg = config.fonts;
 
   defaultFonts =
     [ pkgs.dejavu_fonts
@@ -36,16 +14,12 @@ let
       pkgs.noto-fonts-emoji
     ];
 
-  defaultXFonts =
-    [ (if hasHidpi then fontcursormisc_hidpi else pkgs.xorg.fontcursormisc)
-      pkgs.xorg.fontmiscmisc
-    ];
-
 in
 
 {
   imports = [
     (mkRemovedOptionModule [ "fonts" "enableCoreFonts" ] "Use fonts.fonts = [ pkgs.corefonts ]; instead.")
+    (mkRenamedOptionModule [ "hardware" "video" "hidpi" "enable" ] [ "fonts" "optimizeForVeryHighDPI" ])
   ];
 
   options = {
@@ -69,13 +43,32 @@ in
         '';
       };
 
+      optimizeForVeryHighDPI = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Optimize configuration for very high-density (>200 DPI) displays:
+            - disable subpixel anti-aliasing
+            - disable hinting
+            - automatically upscale the default X11 cursor
+        '';
+      };
     };
 
   };
 
   config = mkMerge [
-    { fonts.fonts = mkIf config.fonts.enableDefaultFonts defaultFonts; }
-    { fonts.fonts = mkIf config.services.xserver.enable defaultXFonts; }
+    { fonts.fonts = mkIf cfg.enableDefaultFonts defaultFonts; }
+    (mkIf cfg.optimizeForVeryHighDPI {
+      services.xserver.upscaleDefaultCursor = mkDefault true;
+      # Conforms to the recommendation in fonts/fontconfig.nix
+      # for > 200DPI.
+      fonts.fontconfig = {
+        antialias = mkDefault false;
+        hinting.enable = mkDefault false;
+        subpixel.lcdfilter = mkDefault "none";
+      };
+    })
   ];
 
 }
diff --git a/nixos/modules/config/no-x-libs.nix b/nixos/modules/config/no-x-libs.nix
index e18003c0d486a..3ebe2fa9f1647 100644
--- a/nixos/modules/config/no-x-libs.nix
+++ b/nixos/modules/config/no-x-libs.nix
@@ -30,8 +30,8 @@ with lib;
       beam = super.beam_nox;
       cairo = super.cairo.override { x11Support = false; };
       dbus = super.dbus.override { x11Support = false; };
-      ffmpeg_4 = super.ffmpeg_4-headless;
-      ffmpeg_5 = super.ffmpeg_5-headless;
+      ffmpeg_4 = super.ffmpeg_4.override { ffmpegVariant = "headless"; };
+      ffmpeg_5 = super.ffmpeg_5.override { ffmpegVariant = "headless"; };
       # dep of graphviz, libXpm is optional for Xpm support
       gd = super.gd.override { withXorg = false; };
       gobject-introspection = super.gobject-introspection.override { x11Support = false; };
@@ -61,12 +61,13 @@ with lib;
       pinentry = super.pinentry.override { enabledFlavors = [ "curses" "tty" "emacs" ]; withLibsecret = false; };
       qemu = super.qemu.override { gtkSupport = false; spiceSupport = false; sdlSupport = false; };
       qrencode = super.qrencode.overrideAttrs (_: { doCheck = false; });
-      qt5 = super.qt5.overrideScope' (self': super': {
+      qt5 = super.qt5.overrideScope (const (super': {
         qtbase = super'.qtbase.override { withGtk3 = false; };
-      });
+      }));
       stoken = super.stoken.override { withGTK3 = false; };
       # translateManpages -> perlPackages.po4a -> texlive-combined-basic -> texlive-core-big -> libX11
       util-linux = super.util-linux.override { translateManpages = false; };
+      vim-full = super.vim-full.override { guiSupport = false; };
       zbar = super.zbar.override { enableVideo = false; withXorg = false; };
     }));
   };
diff --git a/nixos/modules/config/update-users-groups.pl b/nixos/modules/config/update-users-groups.pl
index 4368ec24ea9e9..54352a517a24d 100644
--- a/nixos/modules/config/update-users-groups.pl
+++ b/nixos/modules/config/update-users-groups.pl
@@ -215,10 +215,12 @@ foreach my $u (@{$spec->{users}}) {
     } else {
         $u->{uid} = allocUid($name, $u->{isSystemUser}) if !defined $u->{uid};
 
-        if (defined $u->{initialPassword}) {
-            $u->{hashedPassword} = hashPassword($u->{initialPassword});
-        } elsif (defined $u->{initialHashedPassword}) {
-            $u->{hashedPassword} = $u->{initialHashedPassword};
+        if (!defined $u->{hashedPassword}) {
+            if (defined $u->{initialPassword}) {
+                $u->{hashedPassword} = hashPassword($u->{initialPassword});
+            } elsif (defined $u->{initialHashedPassword}) {
+                $u->{hashedPassword} = $u->{initialHashedPassword};
+            }
         }
     }
 
diff --git a/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix
index 352d6e39435b9..ee4692fc6a6a6 100644
--- a/nixos/modules/config/users-groups.nix
+++ b/nixos/modules/config/users-groups.nix
@@ -273,6 +273,9 @@ let
           {command}`passwd` command. Otherwise, it's
           equivalent to setting the {option}`hashedPassword` option.
 
+          Note that the {option}`hashedPassword` option will override
+          this option if both are set.
+
           ${hashedPasswordDescription}
         '';
       };
@@ -291,6 +294,9 @@ let
           is world-readable in the Nix store, so it should only be
           used for guest accounts or passwords that will be changed
           promptly.
+
+          Note that the {option}`password` option will override this
+          option if both are set.
         '';
       };
 
diff --git a/nixos/modules/config/zram.nix b/nixos/modules/config/zram.nix
index 4df646cf27966..991387ea9b2bd 100644
--- a/nixos/modules/config/zram.nix
+++ b/nixos/modules/config/zram.nix
@@ -82,12 +82,30 @@ in
           {command}`cat /sys/class/block/zram*/comp_algorithm`
         '';
       };
+
+      writebackDevice = lib.mkOption {
+        default = null;
+        example = "/dev/zvol/tarta-zoot/swap-writeback";
+        type = lib.types.nullOr lib.types.path;
+        description = lib.mdDoc ''
+          Write incompressible pages to this device,
+          as there's no gain from keeping them in RAM.
+        '';
+      };
     };
 
   };
 
   config = lib.mkIf cfg.enable {
 
+    assertions = [
+      {
+        assertion = cfg.writebackDevice == null || cfg.swapDevices <= 1;
+        message = "A single writeback device cannot be shared among multiple zram devices";
+      }
+    ];
+
+
     system.requiredKernelConfig = with config.lib.kernelConfig; [
       (isModule "ZRAM")
     ];
@@ -112,6 +130,8 @@ in
                 zram-size = if cfg.memoryMax != null then "min(${size}, ${toString cfg.memoryMax} / 1024 / 1024)" else size;
                 compression-algorithm = cfg.algorithm;
                 swap-priority = cfg.priority;
+              } // lib.optionalAttrs (cfg.writebackDevice != null) {
+                writeback-device = cfg.writebackDevice;
               };
           })
           devices));
diff --git a/nixos/modules/hardware/all-firmware.nix b/nixos/modules/hardware/all-firmware.nix
index 2d5a0007ff01c..75247286368bc 100644
--- a/nixos/modules/hardware/all-firmware.nix
+++ b/nixos/modules/hardware/all-firmware.nix
@@ -65,8 +65,6 @@ in {
       ] ++ optional pkgs.stdenv.hostPlatform.isAarch raspberrypiWirelessFirmware
         ++ optionals (versionOlder config.boot.kernelPackages.kernel.version "4.13") [
         rtl8723bs-firmware
-      ] ++ optionals (versionOlder config.boot.kernelPackages.kernel.version "5.16") [
-        rtw89-firmware
       ];
       hardware.wirelessRegulatoryDatabase = true;
     })
diff --git a/nixos/modules/hardware/device-tree.nix b/nixos/modules/hardware/device-tree.nix
index 2807313a5a9c4..c568f52ab677d 100644
--- a/nixos/modules/hardware/device-tree.nix
+++ b/nixos/modules/hardware/device-tree.nix
@@ -65,7 +65,7 @@ let
     };
   };
 
-  filterDTBs = src: if isNull cfg.filter
+  filterDTBs = src: if cfg.filter == null
     then "${src}/dtbs"
     else
       pkgs.runCommand "dtbs-filtered" {} ''
@@ -93,8 +93,8 @@ let
   # Fill in `dtboFile` for each overlay if not set already.
   # Existence of one of these is guarded by assertion below
   withDTBOs = xs: flip map xs (o: o // { dtboFile =
-    if isNull o.dtboFile then
-      if !isNull o.dtsFile then compileDTS o.name o.dtsFile
+    if o.dtboFile == null then
+      if o.dtsFile != null then compileDTS o.name o.dtsFile
       else compileDTS o.name (pkgs.writeText "dts" o.dtsText)
     else o.dtboFile; } );
 
@@ -181,7 +181,7 @@ in
   config = mkIf (cfg.enable) {
 
     assertions = let
-      invalidOverlay = o: isNull o.dtsFile && isNull o.dtsText && isNull o.dtboFile;
+      invalidOverlay = o: (o.dtsFile == null) && (o.dtsText == null) && (o.dtboFile == null);
     in lib.singleton {
       assertion = lib.all (o: !invalidOverlay o) cfg.overlays;
       message = ''
diff --git a/nixos/modules/hardware/flipperzero.nix b/nixos/modules/hardware/flipperzero.nix
new file mode 100644
index 0000000000000..82f9b76fa3a73
--- /dev/null
+++ b/nixos/modules/hardware/flipperzero.nix
@@ -0,0 +1,18 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.hardware.flipperzero;
+
+in
+
+{
+  options.hardware.flipperzero.enable = mkEnableOption (mdDoc "udev rules and software for Flipper Zero devices");
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.qFlipper ];
+    services.udev.packages = [ pkgs.qFlipper ];
+  };
+}
diff --git a/nixos/modules/hardware/keyboard/qmk.nix b/nixos/modules/hardware/keyboard/qmk.nix
new file mode 100644
index 0000000000000..df3bcaeccd2ec
--- /dev/null
+++ b/nixos/modules/hardware/keyboard/qmk.nix
@@ -0,0 +1,16 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.hardware.keyboard.qmk;
+  inherit (lib) mdDoc mkEnableOption mkIf;
+
+in
+{
+  options.hardware.keyboard.qmk = {
+    enable = mkEnableOption (mdDoc "non-root access to the firmware of QMK keyboards");
+  };
+
+  config = mkIf cfg.enable {
+    services.udev.packages = [ pkgs.qmk-udev-rules ];
+  };
+}
diff --git a/nixos/modules/hardware/keyboard/teck.nix b/nixos/modules/hardware/keyboard/teck.nix
index 2705668d9a750..8376c6b9c50b2 100644
--- a/nixos/modules/hardware/keyboard/teck.nix
+++ b/nixos/modules/hardware/keyboard/teck.nix
@@ -1,16 +1,16 @@
 { config, lib, pkgs, ... }:
 
-with lib;
 let
   cfg = config.hardware.keyboard.teck;
+  inherit (lib) mdDoc mkEnableOption mkIf;
+
 in
 {
   options.hardware.keyboard.teck = {
-    enable = mkEnableOption (lib.mdDoc "non-root access to the firmware of TECK keyboards");
+    enable = mkEnableOption (mdDoc "non-root access to the firmware of TECK keyboards");
   };
 
   config = mkIf cfg.enable {
     services.udev.packages = [ pkgs.teck-udev-rules ];
   };
 }
-
diff --git a/nixos/modules/hardware/keyboard/uhk.nix b/nixos/modules/hardware/keyboard/uhk.nix
index c18051439938d..17baff83d886b 100644
--- a/nixos/modules/hardware/keyboard/uhk.nix
+++ b/nixos/modules/hardware/keyboard/uhk.nix
@@ -1,13 +1,14 @@
 { config, lib, pkgs, ... }:
 
-with lib;
 let
   cfg = config.hardware.keyboard.uhk;
+  inherit (lib) mdDoc mkEnableOption mkIf;
+
 in
 {
   options.hardware.keyboard.uhk = {
-    enable = mkEnableOption (lib.mdDoc ''
-    non-root access to the firmware of UHK keyboards.
+    enable = mkEnableOption (mdDoc ''
+      non-root access to the firmware of UHK keyboards.
       You need it when you want to flash a new firmware on the keyboard.
       Access to the keyboard is granted to users in the "input" group.
       You may want to install the uhk-agent package.
diff --git a/nixos/modules/hardware/keyboard/zsa.nix b/nixos/modules/hardware/keyboard/zsa.nix
index 5bf4022cdc435..a04b67b5c8d0e 100644
--- a/nixos/modules/hardware/keyboard/zsa.nix
+++ b/nixos/modules/hardware/keyboard/zsa.nix
@@ -1,21 +1,18 @@
 { config, lib, pkgs, ... }:
 
 let
-  inherit (lib) mkOption mkIf types;
   cfg = config.hardware.keyboard.zsa;
+  inherit (lib) mkEnableOption mkIf mdDoc;
+
 in
 {
   options.hardware.keyboard.zsa = {
-    enable = mkOption {
-      type = types.bool;
-      default = false;
-      description = lib.mdDoc ''
-        Enables udev rules for keyboards from ZSA like the ErgoDox EZ, Planck EZ and Moonlander Mark I.
-        You need it when you want to flash a new configuration on the keyboard
-        or use their live training in the browser.
-        You may want to install the wally-cli package.
-      '';
-    };
+    enable = mkEnableOption (mdDoc ''
+      udev rules for keyboards from ZSA like the ErgoDox EZ, Planck EZ and Moonlander Mark I.
+      You need it when you want to flash a new configuration on the keyboard
+      or use their live training in the browser.
+      You may want to install the wally-cli package.
+    '');
   };
 
   config = mkIf cfg.enable {
diff --git a/nixos/modules/hardware/video/hidpi.nix b/nixos/modules/hardware/video/hidpi.nix
deleted file mode 100644
index 8c8f8bc0c2652..0000000000000
--- a/nixos/modules/hardware/video/hidpi.nix
+++ /dev/null
@@ -1,24 +0,0 @@
-{ lib, pkgs, config, ...}:
-with lib;
-
-{
-  options.hardware.video.hidpi.enable = mkEnableOption (lib.mdDoc "Font/DPI configuration optimized for HiDPI displays");
-
-  config = mkIf config.hardware.video.hidpi.enable {
-    console.font = lib.mkDefault "${pkgs.terminus_font}/share/consolefonts/ter-v32n.psf.gz";
-
-    # Needed when typing in passwords for full disk encryption
-    console.earlySetup = mkDefault true;
-    boot.loader.systemd-boot.consoleMode = mkDefault "1";
-
-
-    # Grayscale anti-aliasing for fonts
-    fonts.fontconfig.antialias = mkDefault true;
-    fonts.fontconfig.subpixel = {
-      rgba = mkDefault "none";
-      lcdfilter = mkDefault "none";
-    };
-
-    # TODO Find reasonable defaults X11 & wayland
-  };
-}
diff --git a/nixos/modules/hardware/video/nvidia.nix b/nixos/modules/hardware/video/nvidia.nix
index 2eff322d96013..434931ccae5a7 100644
--- a/nixos/modules/hardware/video/nvidia.nix
+++ b/nixos/modules/hardware/video/nvidia.nix
@@ -461,7 +461,9 @@ in
     # If requested enable modesetting via kernel parameter.
     boot.kernelParams = optional (offloadCfg.enable || cfg.modesetting.enable) "nvidia-drm.modeset=1"
       ++ optional cfg.powerManagement.enable "nvidia.NVreg_PreserveVideoMemoryAllocations=1"
-      ++ optional cfg.open "nvidia.NVreg_OpenRmEnableUnsupportedGpus=1";
+      ++ optional cfg.open "nvidia.NVreg_OpenRmEnableUnsupportedGpus=1"
+      # proprietary driver is not compiled with support for X86_KERNEL_IBT
+      ++ optional (!cfg.open && config.boot.kernelPackages.kernel.kernelAtLeast "6.2") "ibt=off";
 
     services.udev.extraRules =
       ''
diff --git a/nixos/modules/installer/netboot/netboot-minimal.nix b/nixos/modules/installer/netboot/netboot-minimal.nix
index 91065d52faf47..5ca255acf35f4 100644
--- a/nixos/modules/installer/netboot/netboot-minimal.nix
+++ b/nixos/modules/installer/netboot/netboot-minimal.nix
@@ -9,4 +9,7 @@
   ];
 
   documentation.man.enable = lib.mkOverride 500 true;
+  hardware.enableRedistributableFirmware = lib.mkOverride 70 false;
+  system.extraDependencies = lib.mkOverride 70 [];
+  networking.wireless.enable = lib.mkOverride 500 false;
 }
diff --git a/nixos/modules/installer/sd-card/sd-image-powerpc64le.nix b/nixos/modules/installer/sd-card/sd-image-powerpc64le.nix
new file mode 100644
index 0000000000000..143c678e43fbc
--- /dev/null
+++ b/nixos/modules/installer/sd-card/sd-image-powerpc64le.nix
@@ -0,0 +1,49 @@
+# To build, use:
+# nix-build nixos -I nixos-config=nixos/modules/installer/sd-card/sd-image-powerpc64le.nix -A config.system.build.sdImage
+{ config, lib, pkgs, ... }:
+
+{
+  imports = [
+    ../../profiles/base.nix
+    ../../profiles/installation-device.nix
+    ./sd-image.nix
+  ];
+
+  boot.loader = {
+    # powerpc64le-linux typically uses petitboot
+    grub.enable = false;
+    generic-extlinux-compatible = {
+      # petitboot is not does not support all of the extlinux extensions to
+      # syslinux, but its parser is very forgiving; it essentially ignores
+      # whatever it doesn't understand.  See below for a filename adjustment.
+      enable = true;
+    };
+  };
+
+  boot.consoleLogLevel = lib.mkDefault 7;
+  boot.kernelParams = [ "console=hvc0" ];
+
+  sdImage = {
+    populateFirmwareCommands = "";
+    populateRootCommands = ''
+      mkdir -p ./files/boot
+      ${config.boot.loader.generic-extlinux-compatible.populateCmd} \
+        -c ${config.system.build.toplevel} \
+        -d ./files/boot
+    ''
+    # https://github.com/open-power/petitboot/blob/master/discover/syslinux-parser.c
+    # petitboot will look in these paths (plus all-caps versions of them):
+    #  /boot/syslinux/syslinux.cfg
+    #  /syslinux/syslinux.cfg
+    #  /syslinux.cfg
+    + ''
+      mv ./files/boot/extlinux ./files/boot/syslinux
+      mv ./files/boot/syslinux/extlinux.conf ./files/boot/syslinux/syslinux.cfg
+    ''
+    # petitboot does not support relative paths for LINUX or INITRD; it prepends
+    # a `/` when parsing these fields
+    + ''
+      sed -i 's_^\(\W\W*\(INITRD\|initrd\|LINUX\|linux\)\W\)\.\./_\1/boot/_' ./files/boot/syslinux/syslinux.cfg
+    '';
+  };
+}
diff --git a/nixos/modules/installer/tools/nix-fallback-paths.nix b/nixos/modules/installer/tools/nix-fallback-paths.nix
index c9cb65dbbe5a3..1058a34133b14 100644
--- a/nixos/modules/installer/tools/nix-fallback-paths.nix
+++ b/nixos/modules/installer/tools/nix-fallback-paths.nix
@@ -1,7 +1,7 @@
 {
-  x86_64-linux = "/nix/store/lsr79q5xqd9dv97wn87x12kzax8s8i1s-nix-2.13.2";
-  i686-linux = "/nix/store/wky9xjwiwzpifgk0s3f2nrg8nr67bi7x-nix-2.13.2";
-  aarch64-linux = "/nix/store/v8drr3x1ia6bdr8y4vl79mlz61xynrpm-nix-2.13.2";
-  x86_64-darwin = "/nix/store/1l14si31p4aw7c1gwgjy0nq55k38j9nj-nix-2.13.2";
-  aarch64-darwin = "/nix/store/6x7nr1r780fgn254zhkwhih3f3i8cr45-nix-2.13.2";
+  x86_64-linux = "/nix/store/mc43d38fibi94pp5crfwacl5gbslccd0-nix-2.13.3";
+  i686-linux = "/nix/store/09m966pj26cgd4ihlg8ihl1106j3vih8-nix-2.13.3";
+  aarch64-linux = "/nix/store/7f191d125akld27gc6jl0r13l8pl7x0h-nix-2.13.3";
+  x86_64-darwin = "/nix/store/1wn9jkvi2zqfjnjgg7lnp30r2q2y8whd-nix-2.13.3";
+  aarch64-darwin = "/nix/store/8w0v2mffa10chrf1h66cbvbpw86qmh85-nix-2.13.3";
 }
diff --git a/nixos/modules/installer/tools/nixos-generate-config.pl b/nixos/modules/installer/tools/nixos-generate-config.pl
index 212b2b3cd23a2..946e73dac5864 100644
--- a/nixos/modules/installer/tools/nixos-generate-config.pl
+++ b/nixos/modules/installer/tools/nixos-generate-config.pl
@@ -127,9 +127,6 @@ if (-e "/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors") {
 push @kernelModules, "kvm-intel" if hasCPUFeature "vmx";
 push @kernelModules, "kvm-amd" if hasCPUFeature "svm";
 
-push @attrs, "hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;" if cpuManufacturer "AuthenticAMD";
-push @attrs, "hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;" if cpuManufacturer "GenuineIntel";
-
 
 # Look at the PCI devices and add necessary modules.  Note that most
 # modules are auto-detected so we don't need to list them here.
@@ -324,11 +321,15 @@ if ($virt eq "systemd-nspawn") {
 }
 
 
-# Provide firmware for devices that are not detected by this script,
-# unless we're in a VM/container.
-push @imports, "(modulesPath + \"/installer/scan/not-detected.nix\")"
-    if $virt eq "none";
+# Check if we're on bare metal, not in a VM/container.
+if ($virt eq "none") {
+    # Provide firmware for devices that are not detected by this script.
+    push @imports, "(modulesPath + \"/installer/scan/not-detected.nix\")";
 
+    # Update the microcode.
+    push @attrs, "hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;" if cpuManufacturer "AuthenticAMD";
+    push @attrs, "hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;" if cpuManufacturer "GenuineIntel";
+}
 
 # For a device name like /dev/sda1, find a more stable path like
 # /dev/disk/by-uuid/X or /dev/disk/by-label/Y.
@@ -517,21 +518,6 @@ EOF
     }
 }
 
-# For lack of a better way to determine it, guess whether we should use a
-# bigger font for the console from the display mode on the first
-# framebuffer. A way based on the physical size/actual DPI reported by
-# the monitor would be nice, but I don't know how to do this without X :)
-my $fb_modes_file = "/sys/class/graphics/fb0/modes";
-if (-f $fb_modes_file && -r $fb_modes_file) {
-    my $modes = read_file($fb_modes_file);
-    $modes =~ m/([0-9]+)x([0-9]+)/;
-    my $console_width = $1, my $console_height = $2;
-    if ($console_width > 1920) {
-        push @attrs, "# high-resolution display";
-        push @attrs, 'hardware.video.hidpi.enable = lib.mkDefault true;';
-    }
-}
-
 
 # Generate the hardware configuration file.
 
diff --git a/nixos/modules/installer/tools/tools.nix b/nixos/modules/installer/tools/tools.nix
index d1b16d042d86a..08278d3943f39 100644
--- a/nixos/modules/installer/tools/tools.nix
+++ b/nixos/modules/installer/tools/tools.nix
@@ -159,10 +159,7 @@ in
       $desktopConfiguration
         # Configure keymap in X11
         # services.xserver.layout = "us";
-        # services.xserver.xkbOptions = {
-        #   "eurosign:e";
-        #   "caps:escape" # map caps to escape.
-        # };
+        # services.xserver.xkbOptions = "eurosign:e,caps:escape";
 
         # Enable CUPS to print documents.
         # services.printing.enable = true;
@@ -180,7 +177,7 @@ in
         #   extraGroups = [ "wheel" ]; # Enable ‘sudo’ for the user.
         #   packages = with pkgs; [
         #     firefox
-        #     thunderbird
+        #     tree
         #   ];
         # };
 
diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix
index a434932497d9f..bed50b81604da 100644
--- a/nixos/modules/misc/ids.nix
+++ b/nixos/modules/misc/ids.nix
@@ -338,7 +338,7 @@ in
       lidarr = 306;
       slurm = 307;
       kapacitor = 308;
-      solr = 309;
+      # solr = 309; removed 2023-03-16
       alerta = 310;
       minetest = 311;
       rss2email = 312;
@@ -510,7 +510,6 @@ in
       #seeks = 148; # removed 2020-06-21
       prosody = 149;
       i2pd = 150;
-      systemd-coredump = 151;
       systemd-network = 152;
       systemd-resolve = 153;
       systemd-timesync = 154;
@@ -649,7 +648,7 @@ in
       lidarr = 306;
       slurm = 307;
       kapacitor = 308;
-      solr = 309;
+      # solr = 309; removed 2023-03-16
       alerta = 310;
       minetest = 311;
       rss2email = 312;
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 752c0213f050a..6ebbe3ff2a098 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -53,11 +53,13 @@
   ./hardware/cpu/intel-sgx.nix
   ./hardware/device-tree.nix
   ./hardware/digitalbitbox.nix
+  ./hardware/flipperzero.nix
   ./hardware/flirc.nix
   ./hardware/gkraken.nix
   ./hardware/gpgsmartcards.nix
   ./hardware/hackrf.nix
   ./hardware/i2c.nix
+  ./hardware/keyboard/qmk.nix
   ./hardware/keyboard/teck.nix
   ./hardware/keyboard/uhk.nix
   ./hardware/keyboard/zsa.nix
@@ -93,7 +95,6 @@
   ./hardware/video/bumblebee.nix
   ./hardware/video/capture/mwprocapture.nix
   ./hardware/video/displaylink.nix
-  ./hardware/video/hidpi.nix
   ./hardware/video/nvidia.nix
   ./hardware/video/switcheroo-control.nix
   ./hardware/video/uvcvideo/default.nix
@@ -203,6 +204,7 @@
   ./programs/nbd.nix
   ./programs/neovim.nix
   ./programs/nethoscope.nix
+  ./programs/nexttrace.nix
   ./programs/nix-index.nix
   ./programs/nix-ld.nix
   ./programs/nm-applet.nix
@@ -217,6 +219,7 @@
   ./programs/proxychains.nix
   ./programs/qdmr.nix
   ./programs/qt5ct.nix
+  ./programs/regreet.nix
   ./programs/rog-control-center.nix
   ./programs/rust-motd.nix
   ./programs/screen.nix
@@ -314,6 +317,7 @@
   ./services/audio/snapserver.nix
   ./services/audio/spotifyd.nix
   ./services/audio/squeezelite.nix
+  ./services/audio/tts.nix
   ./services/audio/ympd.nix
   ./services/backup/automysqlbackup.nix
   ./services/backup/bacula.nix
@@ -373,6 +377,8 @@
   ./services/continuous-integration/jenkins/default.nix
   ./services/continuous-integration/jenkins/job-builder.nix
   ./services/continuous-integration/jenkins/slave.nix
+  ./services/continuous-integration/woodpecker/agent.nix
+  ./services/continuous-integration/woodpecker/server.nix
   ./services/databases/aerospike.nix
   ./services/databases/cassandra.nix
   ./services/databases/clickhouse.nix
@@ -436,6 +442,7 @@
   ./services/development/blackfire.nix
   ./services/development/bloop.nix
   ./services/development/distccd.nix
+  ./services/development/gemstash.nix
   ./services/development/hoogle.nix
   ./services/development/jupyter/default.nix
   ./services/development/jupyterhub/default.nix
@@ -504,6 +511,7 @@
   ./services/hardware/usbmuxd.nix
   ./services/hardware/usbrelayd.nix
   ./services/hardware/vdr.nix
+  ./services/hardware/keyd.nix
   ./services/home-automation/evcc.nix
   ./services/home-automation/home-assistant.nix
   ./services/home-automation/zigbee2mqtt.nix
@@ -620,6 +628,7 @@
   ./services/misc/irkerd.nix
   ./services/misc/jackett.nix
   ./services/misc/jellyfin.nix
+  ./services/misc/jellyseerr.nix
   ./services/misc/klipper.nix
   ./services/misc/languagetool.nix
   ./services/misc/leaps.nix
@@ -659,6 +668,7 @@
   ./services/misc/prowlarr.nix
   ./services/misc/pykms.nix
   ./services/misc/radarr.nix
+  ./services/misc/readarr.nix
   ./services/misc/redmine.nix
   ./services/misc/ripple-data-api.nix
   ./services/misc/rippled.nix
@@ -725,6 +735,7 @@
   ./services/monitoring/nagios.nix
   ./services/monitoring/netdata.nix
   ./services/monitoring/parsedmarc.nix
+  ./services/monitoring/prometheus/alertmanager-irc-relay.nix
   ./services/monitoring/prometheus/alertmanager.nix
   ./services/monitoring/prometheus/default.nix
   ./services/monitoring/prometheus/exporters.nix
@@ -797,6 +808,7 @@
   ./services/networking/bitlbee.nix
   ./services/networking/blockbook-frontend.nix
   ./services/networking/blocky.nix
+  ./services/networking/cgit.nix
   ./services/networking/charybdis.nix
   ./services/networking/chisel-server.nix
   ./services/networking/cjdns.nix
@@ -860,6 +872,7 @@
   ./services/networking/i2pd.nix
   ./services/networking/icecream/daemon.nix
   ./services/networking/icecream/scheduler.nix
+  ./services/networking/imaginary.nix
   ./services/networking/inspircd.nix
   ./services/networking/iodine.nix
   ./services/networking/iperf3.nix
@@ -911,6 +924,7 @@
   ./services/networking/ndppd.nix
   ./services/networking/nebula.nix
   ./services/networking/netbird.nix
+  ./services/networking/networkd-dispatcher.nix
   ./services/networking/networkmanager.nix
   ./services/networking/nextdns.nix
   ./services/networking/nftables.nix
@@ -938,6 +952,7 @@
   ./services/networking/owamp.nix
   ./services/networking/pdns-recursor.nix
   ./services/networking/pdnsd.nix
+  ./services/networking/peroxide.nix
   ./services/networking/pixiecore.nix
   ./services/networking/pleroma.nix
   ./services/networking/polipo.nix
@@ -1047,7 +1062,8 @@
   ./services/search/hound.nix
   ./services/search/kibana.nix
   ./services/search/meilisearch.nix
-  ./services/search/solr.nix
+  ./services/search/opensearch.nix
+  ./services/search/qdrant.nix
   ./services/security/aesmd.nix
   ./services/security/certmgr.nix
   ./services/security/cfssl.nix
@@ -1118,9 +1134,9 @@
   ./services/web-apps/atlassian/confluence.nix
   ./services/web-apps/atlassian/crowd.nix
   ./services/web-apps/atlassian/jira.nix
-  ./services/web-apps/baget.nix
   ./services/web-apps/bookstack.nix
   ./services/web-apps/calibre-web.nix
+  ./services/web-apps/coder.nix
   ./services/web-apps/changedetection-io.nix
   ./services/web-apps/cloudlog.nix
   ./services/web-apps/code-server.nix
@@ -1162,6 +1178,7 @@
   ./services/web-apps/moodle.nix
   ./services/web-apps/netbox.nix
   ./services/web-apps/nextcloud.nix
+  ./services/web-apps/nextcloud-notify_push.nix
   ./services/web-apps/nexus.nix
   ./services/web-apps/nifi.nix
   ./services/web-apps/node-red.nix
@@ -1259,6 +1276,7 @@
   ./services/x11/window-managers/bspwm.nix
   ./services/x11/window-managers/katriawm.nix
   ./services/x11/window-managers/metacity.nix
+  ./services/x11/window-managers/nimdow.nix
   ./services/x11/window-managers/none.nix
   ./services/x11/window-managers/twm.nix
   ./services/x11/window-managers/windowlab.nix
diff --git a/nixos/modules/profiles/base.nix b/nixos/modules/profiles/base.nix
index 518a1f8d0b304..9f32f85a61ec2 100644
--- a/nixos/modules/profiles/base.nix
+++ b/nixos/modules/profiles/base.nix
@@ -1,5 +1,5 @@
 # This module defines the software packages included in the "minimal"
-# installation CD.  It might be useful elsewhere.
+# installation CD. It might be useful elsewhere.
 
 { config, lib, pkgs, ... }:
 
@@ -17,7 +17,6 @@
     pkgs.ddrescue
     pkgs.ccrypt
     pkgs.cryptsetup # needed for dm-crypt volumes
-    pkgs.mkpasswd # for generating password files
 
     # Some text editors.
     (pkgs.vim.customize {
@@ -32,7 +31,6 @@
     pkgs.fuse
     pkgs.fuse3
     pkgs.sshfs-fuse
-    pkgs.rsync
     pkgs.socat
     pkgs.screen
     pkgs.tcpdump
@@ -45,22 +43,14 @@
     pkgs.usbutils
     pkgs.nvme-cli
 
-    # Tools to create / manipulate filesystems.
-    pkgs.ntfsprogs # for resizing NTFS partitions
-    pkgs.dosfstools
-    pkgs.mtools
-    pkgs.xfsprogs.bin
-    pkgs.jfsutils
-    pkgs.f2fs-tools
-
     # Some compression/archiver tools.
     pkgs.unzip
     pkgs.zip
   ];
 
-  # Include support for various filesystems.
+  # Include support for various filesystems and tools to create / manipulate them.
   boot.supportedFilesystems =
-    [ "btrfs" "reiserfs" "vfat" "f2fs" "xfs" "ntfs" "cifs" ] ++
+    [ "btrfs" "cifs" "f2fs" "jfs" "ntfs" "reiserfs" "vfat" "xfs" ] ++
     lib.optional (lib.meta.availableOn pkgs.stdenv.hostPlatform config.boot.zfs.package) "zfs";
 
   # Configure host id for ZFS to work
diff --git a/nixos/modules/programs/atop.nix b/nixos/modules/programs/atop.nix
index 2b14d7c734390..9d5843bd670e2 100644
--- a/nixos/modules/programs/atop.nix
+++ b/nixos/modules/programs/atop.nix
@@ -142,6 +142,7 @@ in
               # convert remainings logs and start eventually
               atop.serviceConfig.ExecStartPre = pkgs.writeShellScript "atop-update-log-format" ''
                 set -e -u
+                shopt -s nullglob
                 for logfile in "$LOGPATH"/atop_*
                 do
                   ${atop}/bin/atopconvert "$logfile" "$logfile".new
@@ -150,6 +151,8 @@ in
                   if ! ${pkgs.diffutils}/bin/cmp -s "$logfile" "$logfile".new
                   then
                     ${pkgs.coreutils}/bin/mv -v -f "$logfile".new "$logfile"
+                  else
+                    ${pkgs.coreutils}/bin/rm -f "$logfile".new
                   fi
                 done
               '';
diff --git a/nixos/modules/programs/ccache.nix b/nixos/modules/programs/ccache.nix
index 19fb7ca3294e8..567c853e8c7de 100644
--- a/nixos/modules/programs/ccache.nix
+++ b/nixos/modules/programs/ccache.nix
@@ -17,7 +17,7 @@ in {
       type = types.listOf types.str;
       description = lib.mdDoc "Nix top-level packages to be compiled using CCache";
       default = [];
-      example = [ "wxGTK30" "ffmpeg" "libav_all" ];
+      example = [ "wxGTK32" "ffmpeg" "libav_all" ];
     };
   };
 
diff --git a/nixos/modules/programs/flashrom.nix b/nixos/modules/programs/flashrom.nix
index 294b208a37208..9f8faff14e472 100644
--- a/nixos/modules/programs/flashrom.nix
+++ b/nixos/modules/programs/flashrom.nix
@@ -22,6 +22,5 @@ in
   config = mkIf cfg.enable {
     services.udev.packages = [ cfg.package ];
     environment.systemPackages = [ cfg.package ];
-    users.groups.flashrom = { };
   };
 }
diff --git a/nixos/modules/programs/java.nix b/nixos/modules/programs/java.nix
index 4f03c1f3ff258..c5f83858d06a3 100644
--- a/nixos/modules/programs/java.nix
+++ b/nixos/modules/programs/java.nix
@@ -8,7 +8,6 @@ with lib;
 let
   cfg = config.programs.java;
 in
-
 {
 
   options = {
@@ -40,12 +39,35 @@ in
         type = types.package;
       };
 
+      binfmt = mkEnableOption (lib.mdDoc "binfmt to execute java jar's and classes");
+
     };
 
   };
 
   config = mkIf cfg.enable {
 
+    boot.binfmt.registrations = mkIf cfg.binfmt {
+      java-class = {
+        recognitionType = "extension";
+        magicOrExtension = "class";
+        interpreter = pkgs.writeShellScript "java-class-wrapper" ''
+          test -e ${cfg.package}/nix-support/setup-hook && source ${cfg.package}/nix-support/setup-hook
+          classpath=$(dirname "$1")
+          class=$(basename "''${1%%.class}")
+          $JAVA_HOME/bin/java -classpath "$classpath" "$class" "''${@:2}"
+        '';
+      };
+      java-jar = {
+        recognitionType = "extension";
+        magicOrExtension = "jar";
+        interpreter = pkgs.writeShellScript "java-jar-wrapper" ''
+          test -e ${cfg.package}/nix-support/setup-hook && source ${cfg.package}/nix-support/setup-hook
+          $JAVA_HOME/bin/java -jar "$@"
+        '';
+      };
+    };
+
     environment.systemPackages = [ cfg.package ];
 
     environment.shellInit = ''
diff --git a/nixos/modules/programs/nexttrace.nix b/nixos/modules/programs/nexttrace.nix
new file mode 100644
index 0000000000000..091d4f17f9f61
--- /dev/null
+++ b/nixos/modules/programs/nexttrace.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.programs.nexttrace;
+
+in
+{
+  options = {
+    programs.nexttrace = {
+      enable = lib.mkEnableOption (lib.mdDoc "Nexttrace to the global environment and configure a setcap wrapper for it");
+      package = lib.mkPackageOptionMD pkgs "nexttrace" { };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+
+    security.wrappers.nexttrace = {
+      owner = "root";
+      group = "root";
+      capabilities = "cap_net_raw,cap_net_admin+eip";
+      source = "${cfg.package}/bin/nexttrace";
+    };
+  };
+}
diff --git a/nixos/modules/programs/regreet.nix b/nixos/modules/programs/regreet.nix
new file mode 100644
index 0000000000000..89b93737f4a27
--- /dev/null
+++ b/nixos/modules/programs/regreet.nix
@@ -0,0 +1,75 @@
+{ lib
+, pkgs
+, config
+, ...
+}:
+let
+  cfg = config.programs.regreet;
+  settingsFormat = pkgs.formats.toml { };
+in
+{
+  options.programs.regreet = {
+    enable = lib.mkEnableOption null // {
+      description = lib.mdDoc ''
+        Enable ReGreet, a clean and customizable greeter for greetd.
+
+        To use ReGreet, {option}`services.greetd` has to be enabled and
+        {option}`services.greetd.settings.default_session` should contain the
+        appropriate configuration to launch
+        {option}`config.programs.regreet.package`. For examples, see the
+        [ReGreet Readme](https://github.com/rharish101/ReGreet#set-as-default-session).
+
+        A minimal configuration that launches ReGreet in {command}`cage` is
+        enabled by this module by default.
+      '';
+    };
+
+    package = lib.mkPackageOptionMD pkgs [ "greetd" "regreet" ] { };
+
+    settings = lib.mkOption {
+      type = lib.types.either lib.types.path settingsFormat.type;
+      default = { };
+      description = lib.mdDoc ''
+        ReGreet configuration file. Refer
+        <https://github.com/rharish101/ReGreet/blob/main/regreet.sample.toml>
+        for options.
+      '';
+    };
+
+    extraCss = lib.mkOption {
+      type = lib.types.either lib.types.path lib.types.lines;
+      default = "";
+      description = lib.mdDoc ''
+        Extra CSS rules to apply on top of the GTK theme. Refer to
+        [GTK CSS Properties](https://docs.gtk.org/gtk4/css-properties.html) for
+        modifiable properties.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    services.greetd = {
+      enable = lib.mkDefault true;
+      settings.default_session.command = lib.mkDefault "${lib.getExe pkgs.cage} -s -- ${lib.getExe cfg.package}";
+    };
+
+    environment.etc = {
+      "greetd/regreet.css" =
+        if lib.isPath cfg.extraCss
+        then {source = cfg.extraCss;}
+        else {text = cfg.extraCss;};
+
+      "greetd/regreet.toml".source =
+        if lib.isPath cfg.settings
+        then cfg.settings
+        else settingsFormat.generate "regreet.toml" cfg.settings;
+    };
+
+    systemd.tmpfiles.rules = let
+      user = config.services.greetd.settings.default_session.user;
+    in [
+      "d /var/log/regreet 0755 greeter ${user} - -"
+      "d /var/cache/regreet 0755 greeter ${user} - -"
+    ];
+  };
+}
diff --git a/nixos/modules/programs/starship.nix b/nixos/modules/programs/starship.nix
index b56c0b2561649..cacad8eafe3da 100644
--- a/nixos/modules/programs/starship.nix
+++ b/nixos/modules/programs/starship.nix
@@ -9,10 +9,27 @@ let
 
   settingsFile = settingsFormat.generate "starship.toml" cfg.settings;
 
-in {
+  initOption =
+    if cfg.interactiveOnly then
+      "promptInit"
+    else
+      "shellInit";
+
+in
+{
   options.programs.starship = {
     enable = mkEnableOption (lib.mdDoc "the Starship shell prompt");
 
+    interactiveOnly = mkOption {
+      default = true;
+      example = false;
+      type = types.bool;
+      description = lib.mdDoc ''
+        Whether to enable starship only when the shell is interactive.
+        Some plugins require this to be set to false to function correctly.
+      '';
+    };
+
     settings = mkOption {
       inherit (settingsFormat) type;
       default = { };
@@ -25,21 +42,21 @@ in {
   };
 
   config = mkIf cfg.enable {
-    programs.bash.promptInit = ''
+    programs.bash.${initOption} = ''
       if [[ $TERM != "dumb" && (-z $INSIDE_EMACS || $INSIDE_EMACS == "vterm") ]]; then
         export STARSHIP_CONFIG=${settingsFile}
         eval "$(${pkgs.starship}/bin/starship init bash)"
       fi
     '';
 
-    programs.fish.promptInit = ''
+    programs.fish.${initOption} = ''
       if test "$TERM" != "dumb" -a \( -z "$INSIDE_EMACS" -o "$INSIDE_EMACS" = "vterm" \)
         set -x STARSHIP_CONFIG ${settingsFile}
         eval (${pkgs.starship}/bin/starship init fish)
       end
     '';
 
-    programs.zsh.promptInit = ''
+    programs.zsh.${initOption} = ''
       if [[ $TERM != "dumb" && (-z $INSIDE_EMACS || $INSIDE_EMACS == "vterm") ]]; then
         export STARSHIP_CONFIG=${settingsFile}
         eval "$(${pkgs.starship}/bin/starship init zsh)"
diff --git a/nixos/modules/programs/waybar.nix b/nixos/modules/programs/waybar.nix
index 4697d0f7a6222..2c49ae1408139 100644
--- a/nixos/modules/programs/waybar.nix
+++ b/nixos/modules/programs/waybar.nix
@@ -2,17 +2,22 @@
 
 with lib;
 
+let
+  cfg = config.programs.waybar;
+in
 {
   options.programs.waybar = {
     enable = mkEnableOption (lib.mdDoc "waybar");
+    package = mkPackageOptionMD pkgs "waybar" { };
   };
 
-  config = mkIf config.programs.waybar.enable {
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
     systemd.user.services.waybar = {
       description = "Waybar as systemd service";
       wantedBy = [ "graphical-session.target" ];
       partOf = [ "graphical-session.target" ];
-      script = "${pkgs.waybar}/bin/waybar";
+      script = "${cfg.package}/bin/waybar";
     };
   };
 
diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix
index d8a18cfcc6dc1..2849b23fa2cb5 100644
--- a/nixos/modules/rename.nix
+++ b/nixos/modules/rename.nix
@@ -44,6 +44,7 @@ with lib;
         The hidepid module was removed, since the underlying machinery
         is broken when using cgroups-v2.
     '')
+    (mkRemovedOptionModule [ "services" "baget" "enable" ] "The baget module was removed due to the upstream package being unmaintained.")
     (mkRemovedOptionModule [ "services" "beegfs" ] "The BeeGFS module has been removed")
     (mkRemovedOptionModule [ "services" "beegfsEnable" ] "The BeeGFS module has been removed")
     (mkRemovedOptionModule [ "services" "cgmanager" "enable"] "cgmanager was deprecated by lxc and therefore removed from nixpkgs.")
diff --git a/nixos/modules/security/doas.nix b/nixos/modules/security/doas.nix
index 4d15ed9a80259..115ca33efb5c5 100644
--- a/nixos/modules/security/doas.nix
+++ b/nixos/modules/security/doas.nix
@@ -19,7 +19,7 @@ let
   ];
 
   mkArgs = rule:
-    if (isNull rule.args) then ""
+    if (rule.args == null) then ""
     else if (length rule.args == 0) then "args"
     else "args ${concatStringsSep " " rule.args}";
 
@@ -27,9 +27,9 @@ let
     let
       opts = mkOpts rule;
 
-      as = optionalString (!isNull rule.runAs) "as ${rule.runAs}";
+      as = optionalString (rule.runAs != null) "as ${rule.runAs}";
 
-      cmd = optionalString (!isNull rule.cmd) "cmd ${rule.cmd}";
+      cmd = optionalString (rule.cmd != null) "cmd ${rule.cmd}";
 
       args = mkArgs rule;
     in
@@ -75,7 +75,9 @@ in
         {file}`/etc/doas.conf` file. More specific rules should
         come after more general ones in order to yield the expected behavior.
         You can use `mkBefore` and/or `mkAfter` to ensure
-        this is the case when configuration options are merged.
+        this is the case when configuration options are merged. Be aware that
+        this option cannot be used to override the behaviour allowing
+        passwordless operation for root.
       '';
       example = literalExpression ''
         [
@@ -224,7 +226,9 @@ in
       type = with types; lines;
       default = "";
       description = lib.mdDoc ''
-        Extra configuration text appended to {file}`doas.conf`.
+        Extra configuration text appended to {file}`doas.conf`. Be aware that
+        this option cannot be used to override the behaviour allowing
+        passwordless operation for root.
       '';
     };
   };
@@ -266,14 +270,14 @@ in
             # completely replace the contents of this file, use
             # `environment.etc."doas.conf"`.
 
-            # "root" is allowed to do anything.
-            permit nopass keepenv root
-
             # extraRules
             ${concatStringsSep "\n" (lists.flatten (map mkRule cfg.extraRules))}
 
             # extraConfig
             ${cfg.extraConfig}
+
+            # "root" is allowed to do anything.
+            permit nopass keepenv root
           '';
           preferLocalBuild = true;
         }
diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix
index 4224722f8792c..9b00b91e12818 100644
--- a/nixos/modules/security/pam.nix
+++ b/nixos/modules/security/pam.nix
@@ -793,7 +793,7 @@ let
     };
   }));
 
-  motd = if isNull config.users.motdFile
+  motd = if config.users.motdFile == null
          then pkgs.writeText "motd" config.users.motd
          else config.users.motdFile;
 
@@ -1233,7 +1233,7 @@ in
   config = {
     assertions = [
       {
-        assertion = isNull config.users.motd || isNull config.users.motdFile;
+        assertion = config.users.motd == null || config.users.motdFile == null;
         message = ''
           Only one of users.motd and users.motdFile can be set.
         '';
diff --git a/nixos/modules/services/audio/tts.nix b/nixos/modules/services/audio/tts.nix
new file mode 100644
index 0000000000000..1a355c8ee39f5
--- /dev/null
+++ b/nixos/modules/services/audio/tts.nix
@@ -0,0 +1,151 @@
+{ config
+, lib
+, pkgs
+, ...
+}:
+
+let
+  cfg = config.services.tts;
+in
+
+{
+  options.services.tts = let
+    inherit (lib) literalExpression mkOption mdDoc mkEnableOption types;
+  in  {
+    servers = mkOption {
+      type = types.attrsOf (types.submodule (
+        { ... }: {
+          options = {
+            enable = mkEnableOption (mdDoc "Coqui TTS server");
+
+            port = mkOption {
+              type = types.port;
+              example = 5000;
+              description = mdDoc ''
+                Port to bind the TTS server to.
+              '';
+            };
+
+            model = mkOption {
+              type = types.nullOr types.str;
+              default = "tts_models/en/ljspeech/tacotron2-DDC";
+              example = null;
+              description = mdDoc ''
+                Name of the model to download and use for speech synthesis.
+
+                Check `tts-server --list_models` for possible values.
+
+                Set to `null` to use a custom model.
+              '';
+            };
+
+            useCuda = mkOption {
+              type = types.bool;
+              default = false;
+              example = true;
+              description = mdDoc ''
+                Whether to offload computation onto a CUDA compatible GPU.
+              '';
+            };
+
+            extraArgs = mkOption {
+              type = types.listOf types.str;
+              default = [];
+              description = mdDoc ''
+                Extra arguments to pass to the server commandline.
+              '';
+            };
+          };
+        }
+      ));
+      default = {};
+      example = literalExpression ''
+        {
+          english = {
+            port = 5300;
+            model = "tts_models/en/ljspeech/tacotron2-DDC";
+          };
+          german = {
+            port = 5301;
+            model = "tts_models/de/thorsten/tacotron2-DDC";
+          };
+          dutch = {
+            port = 5302;
+            model = "tts_models/nl/mai/tacotron2-DDC";
+          };
+        }
+      '';
+      description = mdDoc ''
+        TTS server instances.
+      '';
+    };
+  };
+
+  config = let
+    inherit (lib) mkIf mapAttrs' nameValuePair optionalString concatMapStringsSep escapeShellArgs;
+  in mkIf (cfg.servers != {}) {
+    systemd.services = mapAttrs' (server: options:
+      nameValuePair "tts-${server}" {
+        description = "Coqui TTS server instance ${server}";
+        after = [
+          "network-online.target"
+        ];
+        wantedBy = [
+          "multi-user.target"
+        ];
+        path = with pkgs; [
+          espeak-ng
+        ];
+        environment.HOME = "/var/lib/tts";
+        serviceConfig = {
+          DynamicUser = true;
+          User = "tts";
+          StateDirectory = "tts";
+          ExecStart = "${pkgs.tts}/bin/tts-server --port ${toString options.port}"
+            + optionalString (options.model != null) " --model_name ${options.model}"
+            + optionalString (options.useCuda) " --use_cuda"
+            + (concatMapStringsSep " " escapeShellArgs options.extraArgs);
+          CapabilityBoundingSet = "";
+          DeviceAllow = if options.useCuda then [
+            # https://docs.nvidia.com/dgx/pdf/dgx-os-5-user-guide.pdf
+            "/dev/nvidia1"
+            "/dev/nvidia2"
+            "/dev/nvidia3"
+            "/dev/nvidia4"
+            "/dev/nvidia-caps/nvidia-cap1"
+            "/dev/nvidia-caps/nvidia-cap2"
+            "/dev/nvidiactl"
+            "/dev/nvidia-modeset"
+            "/dev/nvidia-uvm"
+            "/dev/nvidia-uvm-tools"
+          ] else "";
+          DevicePolicy = "closed";
+          LockPersonality = true;
+          # jit via numba->llvmpipe
+          MemoryDenyWriteExecute = false;
+          PrivateDevices = true;
+          PrivateUsers = true;
+          ProtectHome = true;
+          ProtectHostname = true;
+          ProtectKernelLogs = true;
+          ProtectKernelModules = true;
+          ProtectKernelTunables = true;
+          ProtectControlGroups = true;
+          ProtectProc = "invisible";
+          ProcSubset = "pid";
+          RestrictAddressFamilies = [
+            "AF_INET"
+            "AF_INET6"
+          ];
+          RestrictNamespaces = true;
+          RestrictRealtime = true;
+          SystemCallArchitectures = "native";
+          SystemCallFilter = [
+            "@system-service"
+            "~@privileged"
+          ];
+          UMask = "0077";
+        };
+      }) cfg.servers;
+  };
+}
diff --git a/nixos/modules/services/backup/borgbackup.md b/nixos/modules/services/backup/borgbackup.md
index e86ae593bbd62..39141f6ec8587 100644
--- a/nixos/modules/services/backup/borgbackup.md
+++ b/nixos/modules/services/backup/borgbackup.md
@@ -128,7 +128,7 @@ To backup your home directory to borgbase you have to:
   - Initialize the repository on the server. Eg.
 
         sudo borg init --encryption=repokey-blake2  \
-            -rsh "ssh -i /run/keys/id_ed25519_borgbase" \
+            --rsh "ssh -i /run/keys/id_ed25519_borgbase" \
             zzz2aaaaa@zzz2aaaaa.repo.borgbase.com:repo
 
   - Add it to your NixOS configuration, e.g.
diff --git a/nixos/modules/services/backup/borgmatic.nix b/nixos/modules/services/backup/borgmatic.nix
index 73c4acda39363..e7cd6ae4bb573 100644
--- a/nixos/modules/services/backup/borgmatic.nix
+++ b/nixos/modules/services/backup/borgmatic.nix
@@ -5,44 +5,58 @@ with lib;
 let
   cfg = config.services.borgmatic;
   settingsFormat = pkgs.formats.yaml { };
+
+  cfgType = with types; submodule {
+    freeformType = settingsFormat.type;
+    options.location = {
+      source_directories = mkOption {
+        type = listOf str;
+        description = mdDoc ''
+          List of source directories to backup (required). Globs and
+          tildes are expanded.
+        '';
+        example = [ "/home" "/etc" "/var/log/syslog*" ];
+      };
+      repositories = mkOption {
+        type = listOf str;
+        description = mdDoc ''
+          Paths to local or remote repositories (required). Tildes are
+          expanded. Multiple repositories are backed up to in
+          sequence. Borg placeholders can be used. See the output of
+          "borg help placeholders" for details. See ssh_command for
+          SSH options like identity file or port. If systemd service
+          is used, then add local repository paths in the systemd
+          service file to the ReadWritePaths list.
+        '';
+        example = [
+          "ssh://user@backupserver/./sourcehostname.borg"
+          "ssh://user@backupserver/./{fqdn}"
+          "/var/local/backups/local.borg"
+        ];
+      };
+    };
+  };
+
   cfgfile = settingsFormat.generate "config.yaml" cfg.settings;
-in {
+in
+{
   options.services.borgmatic = {
-    enable = mkEnableOption (lib.mdDoc "borgmatic");
+    enable = mkEnableOption (mdDoc "borgmatic");
 
     settings = mkOption {
-      description = lib.mdDoc ''
+      description = mdDoc ''
         See https://torsion.org/borgmatic/docs/reference/configuration/
       '';
-      type = types.submodule {
-        freeformType = settingsFormat.type;
-        options.location = {
-          source_directories = mkOption {
-            type = types.listOf types.str;
-            description = lib.mdDoc ''
-              List of source directories to backup (required). Globs and
-              tildes are expanded.
-            '';
-            example = [ "/home" "/etc" "/var/log/syslog*" ];
-          };
-          repositories = mkOption {
-            type = types.listOf types.str;
-            description = lib.mdDoc ''
-              Paths to local or remote repositories (required). Tildes are
-              expanded. Multiple repositories are backed up to in
-              sequence. Borg placeholders can be used. See the output of
-              "borg help placeholders" for details. See ssh_command for
-              SSH options like identity file or port. If systemd service
-              is used, then add local repository paths in the systemd
-              service file to the ReadWritePaths list.
-            '';
-            example = [
-              "user@backupserver:sourcehostname.borg"
-              "user@backupserver:{fqdn}"
-            ];
-          };
-        };
-      };
+      default = null;
+      type = types.nullOr cfgType;
+    };
+
+    configurations = mkOption {
+      description = mdDoc ''
+        Set of borgmatic configurations, see https://torsion.org/borgmatic/docs/reference/configuration/
+      '';
+      default = { };
+      type = types.attrsOf cfgType;
     };
   };
 
@@ -50,9 +64,13 @@ in {
 
     environment.systemPackages = [ pkgs.borgmatic ];
 
-    environment.etc."borgmatic/config.yaml".source = cfgfile;
+    environment.etc = (optionalAttrs (cfg.settings != null) { "borgmatic/config.yaml".source = cfgfile; }) //
+      mapAttrs'
+        (name: value: nameValuePair
+          "borgmatic.d/${name}.yaml"
+          { source = settingsFormat.generate "${name}.yaml" value; })
+        cfg.configurations;
 
     systemd.packages = [ pkgs.borgmatic ];
-
   };
 }
diff --git a/nixos/modules/services/backup/btrbk.nix b/nixos/modules/services/backup/btrbk.nix
index b6eb68cc43f12..b838c174553d2 100644
--- a/nixos/modules/services/backup/btrbk.nix
+++ b/nixos/modules/services/backup/btrbk.nix
@@ -47,7 +47,12 @@ let
     then [ "${name} ${value}" ]
     else concatLists (mapAttrsToList (genSection name) value);
 
-  addDefaults = settings: { backend = "btrfs-progs-sudo"; } // settings;
+  sudo_doas =
+    if config.security.sudo.enable then "sudo"
+    else if config.security.doas.enable then "doas"
+    else throw "The btrbk nixos module needs either sudo or doas enabled in the configuration";
+
+  addDefaults = settings: { backend = "btrfs-progs-${sudo_doas}"; } // settings;
 
   mkConfigFile = name: settings: pkgs.writeTextFile {
     name = "btrbk-${name}.conf";
@@ -152,20 +157,41 @@ in
   };
   config = mkIf (sshEnabled || serviceEnabled) {
     environment.systemPackages = [ pkgs.btrbk ] ++ cfg.extraPackages;
-    security.sudo.extraRules = [
-      {
-        users = [ "btrbk" ];
-        commands = [
-          { command = "${pkgs.btrfs-progs}/bin/btrfs"; options = [ "NOPASSWD" ]; }
-          { command = "${pkgs.coreutils}/bin/mkdir"; options = [ "NOPASSWD" ]; }
-          { command = "${pkgs.coreutils}/bin/readlink"; options = [ "NOPASSWD" ]; }
-          # for ssh, they are not the same than the one hard coded in ${pkgs.btrbk}
-          { command = "/run/current-system/bin/btrfs"; options = [ "NOPASSWD" ]; }
-          { command = "/run/current-system/sw/bin/mkdir"; options = [ "NOPASSWD" ]; }
-          { command = "/run/current-system/sw/bin/readlink"; options = [ "NOPASSWD" ]; }
+    security.sudo = mkIf (sudo_doas == "sudo") {
+      extraRules = [
+        {
+            users = [ "btrbk" ];
+            commands = [
+            { command = "${pkgs.btrfs-progs}/bin/btrfs"; options = [ "NOPASSWD" ]; }
+            { command = "${pkgs.coreutils}/bin/mkdir"; options = [ "NOPASSWD" ]; }
+            { command = "${pkgs.coreutils}/bin/readlink"; options = [ "NOPASSWD" ]; }
+            # for ssh, they are not the same than the one hard coded in ${pkgs.btrbk}
+            { command = "/run/current-system/bin/btrfs"; options = [ "NOPASSWD" ]; }
+            { command = "/run/current-system/sw/bin/mkdir"; options = [ "NOPASSWD" ]; }
+            { command = "/run/current-system/sw/bin/readlink"; options = [ "NOPASSWD" ]; }
+            ];
+        }
+      ];
+    };
+    security.doas = mkIf (sudo_doas == "doas") {
+      extraRules = let
+        doasCmdNoPass = cmd: { users = [ "btrbk" ]; cmd = cmd; noPass = true; };
+      in
+        [
+            (doasCmdNoPass "${pkgs.btrfs-progs}/bin/btrfs")
+            (doasCmdNoPass "${pkgs.coreutils}/bin/mkdir")
+            (doasCmdNoPass "${pkgs.coreutils}/bin/readlink")
+            # for ssh, they are not the same than the one hard coded in ${pkgs.btrbk}
+            (doasCmdNoPass "/run/current-system/bin/btrfs")
+            (doasCmdNoPass "/run/current-system/sw/bin/mkdir")
+            (doasCmdNoPass "/run/current-system/sw/bin/readlink")
+
+            # doas matches command, not binary
+            (doasCmdNoPass "btrfs")
+            (doasCmdNoPass "mkdir")
+            (doasCmdNoPass "readlink")
         ];
-      }
-    ];
+    };
     users.users.btrbk = {
       isSystemUser = true;
       # ssh needs a home directory
@@ -183,8 +209,9 @@ in
               "best-effort" = 2;
               "realtime" = 1;
             }.${cfg.ioSchedulingClass};
+            sudo_doas_flag = "--${sudo_doas}";
           in
-          ''command="${pkgs.util-linux}/bin/ionice -t -c ${toString ioniceClass} ${optionalString (cfg.niceness >= 1) "${pkgs.coreutils}/bin/nice -n ${toString cfg.niceness}"} ${pkgs.btrbk}/share/btrbk/scripts/ssh_filter_btrbk.sh --sudo ${options}" ${v.key}''
+          ''command="${pkgs.util-linux}/bin/ionice -t -c ${toString ioniceClass} ${optionalString (cfg.niceness >= 1) "${pkgs.coreutils}/bin/nice -n ${toString cfg.niceness}"} ${pkgs.btrbk}/share/btrbk/scripts/ssh_filter_btrbk.sh ${sudo_doas_flag} ${options}" ${v.key}''
         )
         cfg.sshAccess;
     };
diff --git a/nixos/modules/services/backup/restic.nix b/nixos/modules/services/backup/restic.nix
index bc24e13aa050e..ca796cf7797e6 100644
--- a/nixos/modules/services/backup/restic.nix
+++ b/nixos/modules/services/backup/restic.nix
@@ -303,8 +303,8 @@ in
               then if (backup.paths != null) then concatStringsSep " " backup.paths else ""
               else "--files-from ${filesFromTmpFile}";
             pruneCmd = optionals (builtins.length backup.pruneOpts > 0) [
-              (resticCmd + " forget --prune --cache-dir=%C/restic-backups-${name} " + (concatStringsSep " " backup.pruneOpts))
-              (resticCmd + " check --cache-dir=%C/restic-backups-${name} " + (concatStringsSep " " backup.checkOpts))
+              (resticCmd + " forget --prune " + (concatStringsSep " " backup.pruneOpts))
+              (resticCmd + " check " + (concatStringsSep " " backup.checkOpts))
             ];
             # Helper functions for rclone remotes
             rcloneRemoteName = builtins.elemAt (splitString ":" backup.repository) 1;
@@ -314,6 +314,7 @@ in
           in
           nameValuePair "restic-backups-${name}" ({
             environment = {
+              RESTIC_CACHE_DIR = "%C/restic-backups-${name}";
               RESTIC_PASSWORD_FILE = backup.passwordFile;
               RESTIC_REPOSITORY = backup.repository;
               RESTIC_REPOSITORY_FILE = backup.repositoryFile;
@@ -332,7 +333,7 @@ in
             restartIfChanged = false;
             serviceConfig = {
               Type = "oneshot";
-              ExecStart = (optionals (backupPaths != "") [ "${resticCmd} backup --cache-dir=%C/restic-backups-${name} ${concatStringsSep " " (backup.extraBackupArgs ++ excludeFlags)} ${backupPaths}" ])
+              ExecStart = (optionals (backupPaths != "") [ "${resticCmd} backup ${concatStringsSep " " (backup.extraBackupArgs ++ excludeFlags)} ${backupPaths}" ])
                 ++ pruneCmd;
               User = backup.user;
               RuntimeDirectory = "restic-backups-${name}";
diff --git a/nixos/modules/services/cluster/hadoop/hbase.nix b/nixos/modules/services/cluster/hadoop/hbase.nix
index 97951ebfe3343..a39da2a84ecad 100644
--- a/nixos/modules/services/cluster/hadoop/hbase.nix
+++ b/nixos/modules/services/cluster/hadoop/hbase.nix
@@ -5,11 +5,95 @@ let
   cfg = config.services.hadoop;
   hadoopConf = "${import ./conf.nix { inherit cfg pkgs lib; }}/";
   mkIfNotNull = x: mkIf (x != null) x;
+  # generic hbase role options
+  hbaseRoleOption = name: extraOpts: {
+    enable = mkEnableOption (mdDoc "HBase ${name}");
+
+    openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = mdDoc "Open firewall ports for HBase ${name}.";
+    };
+
+    restartIfChanged = mkOption {
+      type = types.bool;
+      default = false;
+      description = mdDoc "Restart ${name} con config change.";
+    };
+
+    extraFlags = mkOption {
+      type = with types; listOf str;
+      default = [];
+      example = literalExpression ''[ "--backup" ]'';
+      description = mdDoc "Extra flags for the ${name} service.";
+    };
+
+    environment = mkOption {
+      type = with types; attrsOf str;
+      default = {};
+      example = literalExpression ''
+        {
+          HBASE_MASTER_OPTS = "-Dcom.sun.management.jmxremote.ssl=true";
+        }
+      '';
+      description = mdDoc "Environment variables passed to ${name}.";
+    };
+  } // extraOpts;
+  # generic hbase role configs
+  hbaseRoleConfig = name: ports: (mkIf cfg.hbase."${name}".enable {
+    services.hadoop.gatewayRole = {
+      enable = true;
+      enableHbaseCli = mkDefault true;
+    };
+
+    systemd.services."hbase-${toLower name}" = {
+      description = "HBase ${name}";
+      wantedBy = [ "multi-user.target" ];
+      path = with cfg; [ hbase.package ] ++ optional
+        (with cfg.hbase.master; enable && initHDFS) package;
+      preStart = mkIf (with cfg.hbase.master; enable && initHDFS)
+        (concatStringsSep "\n" (
+          map (x: "HADOOP_USER_NAME=hdfs hdfs --config /etc/hadoop-conf ${x}")[
+            "dfsadmin -safemode wait"
+            "dfs -mkdir -p ${cfg.hbase.rootdir}"
+            "dfs -chown hbase ${cfg.hbase.rootdir}"
+          ]
+        ));
+
+      inherit (cfg.hbase."${name}") environment;
+      script = concatStringsSep " " (
+        [
+          "hbase --config /etc/hadoop-conf/"
+          "${toLower name} start"
+        ]
+        ++ cfg.hbase."${name}".extraFlags
+        ++ map (x: "--${toLower x} ${toString cfg.hbase.${name}.${x}}")
+          (filter (x: hasAttr x cfg.hbase.${name}) ["port" "infoPort"])
+      );
+
+      serviceConfig = {
+        User = "hbase";
+        SyslogIdentifier = "hbase-${toLower name}";
+        Restart = "always";
+      };
+    };
+
+    services.hadoop.hbaseSiteInternal."hbase.rootdir" = cfg.hbase.rootdir;
+
+    networking = {
+      firewall.allowedTCPPorts = mkIf cfg.hbase."${name}".openFirewall ports;
+      hosts = mkIf (with cfg.hbase.regionServer; enable && overrideHosts) {
+        "127.0.0.2" = mkForce [ ];
+        "::1" = mkForce [ ];
+      };
+    };
+
+  });
 in
 {
   options.services.hadoop = {
 
-    gatewayRole.enableHbaseCli = mkEnableOption (lib.mdDoc "HBase CLI tools");
+    gatewayRole.enableHbaseCli = mkEnableOption (mdDoc "HBase CLI tools");
 
     hbaseSiteDefault = mkOption {
       default = {
@@ -21,7 +105,7 @@ in
         "hbase.cluster.distributed" = "true";
       };
       type = types.attrsOf types.anything;
-      description = lib.mdDoc ''
+      description = mdDoc ''
         Default options for hbase-site.xml
       '';
     };
@@ -29,8 +113,12 @@ in
       default = {};
       type = with types; attrsOf anything;
       example = literalExpression ''
+        {
+          "hbase.hregion.max.filesize" = 20*1024*1024*1024;
+          "hbase.table.normalization.enabled" = "true";
+        }
       '';
-      description = lib.mdDoc ''
+      description = mdDoc ''
         Additional options and overrides for hbase-site.xml
         <https://github.com/apache/hbase/blob/rel/2.4.11/hbase-common/src/main/resources/hbase-default.xml>
       '';
@@ -39,7 +127,7 @@ in
       default = {};
       type = with types; attrsOf anything;
       internal = true;
-      description = lib.mdDoc ''
+      description = mdDoc ''
         Internal option to add configs to hbase-site.xml based on module options
       '';
     };
@@ -50,11 +138,11 @@ in
         type = types.package;
         default = pkgs.hbase;
         defaultText = literalExpression "pkgs.hbase";
-        description = lib.mdDoc "HBase package";
+        description = mdDoc "HBase package";
       };
 
       rootdir = mkOption {
-        description = lib.mdDoc ''
+        description = mdDoc ''
           This option will set "hbase.rootdir" in hbase-site.xml and determine
           the directory shared by region servers and into which HBase persists.
           The URL should be 'fully-qualified' to include the filesystem scheme.
@@ -68,7 +156,7 @@ in
         default = "/hbase";
       };
       zookeeperQuorum = mkOption {
-        description = lib.mdDoc ''
+        description = mdDoc ''
           This option will set "hbase.zookeeper.quorum" in hbase-site.xml.
           Comma separated list of servers in the ZooKeeper ensemble.
         '';
@@ -76,107 +164,36 @@ in
         example = "zk1.internal,zk2.internal,zk3.internal";
         default = null;
       };
-      master = {
-        enable = mkEnableOption (lib.mdDoc "HBase Master");
-        initHDFS = mkEnableOption (lib.mdDoc "initialization of the hbase directory on HDFS");
-
-        openFirewall = mkOption {
-          type = types.bool;
-          default = false;
-          description = lib.mdDoc ''
-            Open firewall ports for HBase master.
-          '';
+    } // (let
+      ports = port: infoPort: {
+        port = mkOption {
+          type = types.int;
+          default = port;
+          description = mdDoc "RPC port";
         };
-      };
-      regionServer = {
-        enable = mkEnableOption (lib.mdDoc "HBase RegionServer");
-
-        overrideHosts = mkOption {
-          type = types.bool;
-          default = true;
-          description = lib.mdDoc ''
-            Remove /etc/hosts entries for "127.0.0.2" and "::1" defined in nixos/modules/config/networking.nix
-            Regionservers must be able to resolve their hostnames to their IP addresses, through PTR records
-            or /etc/hosts entries.
-
-          '';
-        };
-
-        openFirewall = mkOption {
-          type = types.bool;
-          default = false;
-          description = lib.mdDoc ''
-            Open firewall ports for HBase master.
-          '';
+        infoPort = mkOption {
+          type = types.int;
+          default = infoPort;
+          description = mdDoc "web UI port";
         };
       };
-    };
-  };
-
-  config = mkMerge [
-    (mkIf cfg.hbase.master.enable {
-      services.hadoop.gatewayRole = {
-        enable = true;
-        enableHbaseCli = mkDefault true;
-      };
-
-      systemd.services.hbase-master = {
-        description = "HBase master";
-        wantedBy = [ "multi-user.target" ];
-
-        preStart = mkIf cfg.hbase.master.initHDFS ''
-          HADOOP_USER_NAME=hdfs ${cfg.package}/bin/hdfs --config ${hadoopConf} dfsadmin -safemode wait
-          HADOOP_USER_NAME=hdfs ${cfg.package}/bin/hdfs --config ${hadoopConf} dfs -mkdir -p ${cfg.hbase.rootdir}
-          HADOOP_USER_NAME=hdfs ${cfg.package}/bin/hdfs --config ${hadoopConf} dfs -chown hbase ${cfg.hbase.rootdir}
+    in mapAttrs hbaseRoleOption {
+      master.initHDFS = mkEnableOption (mdDoc "initialization of the hbase directory on HDFS");
+      regionServer.overrideHosts = mkOption {
+        type = types.bool;
+        default = true;
+        description = mdDoc ''
+          Remove /etc/hosts entries for "127.0.0.2" and "::1" defined in nixos/modules/config/networking.nix
+          Regionservers must be able to resolve their hostnames to their IP addresses, through PTR records
+          or /etc/hosts entries.
         '';
-
-        serviceConfig = {
-          User = "hbase";
-          SyslogIdentifier = "hbase-master";
-          ExecStart = "${cfg.hbase.package}/bin/hbase --config ${hadoopConf} " +
-                      "master start";
-          Restart = "always";
-        };
-      };
-
-      services.hadoop.hbaseSiteInternal."hbase.rootdir" = cfg.hbase.rootdir;
-
-      networking.firewall.allowedTCPPorts = mkIf cfg.hbase.master.openFirewall [
-        16000 16010
-      ];
-
-    })
-
-    (mkIf cfg.hbase.regionServer.enable {
-      services.hadoop.gatewayRole = {
-        enable = true;
-        enableHbaseCli = mkDefault true;
-      };
-
-      systemd.services.hbase-regionserver = {
-        description = "HBase RegionServer";
-        wantedBy = [ "multi-user.target" ];
-        serviceConfig = {
-          User = "hbase";
-          SyslogIdentifier = "hbase-regionserver";
-          ExecStart = "${cfg.hbase.package}/bin/hbase --config /etc/hadoop-conf/ " +
-                      "regionserver start";
-          Restart = "always";
-        };
       };
+      thrift = ports 9090 9095;
+      rest = ports 8080 8085;
+    });
+  };
 
-      services.hadoop.hbaseSiteInternal."hbase.rootdir" = cfg.hbase.rootdir;
-
-      networking = {
-        firewall.allowedTCPPorts = mkIf cfg.hbase.regionServer.openFirewall [
-          16020 16030
-        ];
-        hosts = mkIf cfg.hbase.regionServer.overrideHosts {
-          "127.0.0.2" = mkForce [ ];
-          "::1" = mkForce [ ];
-        };
-      };
-    })
+  config = mkMerge ([
 
     (mkIf cfg.gatewayRole.enable {
 
@@ -192,5 +209,10 @@ in
         isSystemUser = true;
       };
     })
-  ];
+  ] ++ (mapAttrsToList hbaseRoleConfig {
+    master = [ 16000 16010 ];
+    regionServer = [ 16020 16030 ];
+    thrift = with cfg.hbase.thrift; [ port infoPort ];
+    rest = with cfg.hbase.rest; [ port infoPort ];
+  }));
 }
diff --git a/nixos/modules/services/cluster/k3s/default.nix b/nixos/modules/services/cluster/k3s/default.nix
index 693f388de14a6..97c1e57f9b579 100644
--- a/nixos/modules/services/cluster/k3s/default.nix
+++ b/nixos/modules/services/cluster/k3s/default.nix
@@ -106,6 +106,14 @@ in
       description = lib.mdDoc "Only run the server. This option only makes sense for a server.";
     };
 
+    environmentFile = mkOption {
+      type = types.nullOr types.path;
+      description = lib.mdDoc ''
+        File path containing environment variables for configuring the k3s service in the format of an EnvironmentFile. See systemd.exec(5).
+      '';
+      default = null;
+    };
+
     configPath = mkOption {
       type = types.nullOr types.path;
       default = null;
@@ -154,6 +162,7 @@ in
         LimitNPROC = "infinity";
         LimitCORE = "infinity";
         TasksMax = "infinity";
+        EnvironmentFile = cfg.environmentFile;
         ExecStart = concatStringsSep " \\\n " (
           [
             "${cfg.package}/bin/k3s ${cfg.role}"
diff --git a/nixos/modules/services/cluster/kubernetes/addons/dns.nix b/nixos/modules/services/cluster/kubernetes/addons/dns.nix
index 3d41b5f008537..1c00329e6ccff 100644
--- a/nixos/modules/services/cluster/kubernetes/addons/dns.nix
+++ b/nixos/modules/services/cluster/kubernetes/addons/dns.nix
@@ -3,7 +3,7 @@
 with lib;
 
 let
-  version = "1.7.1";
+  version = "1.10.1";
   cfg = config.services.kubernetes.addons.dns;
   ports = {
     dns = 10053;
@@ -59,9 +59,9 @@ in {
       type = types.attrs;
       default = {
         imageName = "coredns/coredns";
-        imageDigest = "sha256:4a6e0769130686518325b21b0c1d0688b54e7c79244d48e1b15634e98e40c6ef";
+        imageDigest = "sha256:a0ead06651cf580044aeb0a0feba63591858fb2e43ade8c9dea45a6a89ae7e5e";
         finalImageTag = version;
-        sha256 = "02r440xcdsgi137k5lmmvp0z5w5fmk8g9mysq5pnysq1wl8sj6mw";
+        sha256 = "0wg696920smmal7552a2zdhfncndn5kfammfa8bk8l7dz9bhk0y1";
       };
     };
 
@@ -136,6 +136,11 @@ in {
             resources = [ "nodes" ];
             verbs = [ "get" ];
           }
+          {
+            apiGroups = [ "discovery.k8s.io" ];
+            resources = [ "endpointslices" ];
+            verbs = [ "list" "watch" ];
+          }
         ];
       };
 
diff --git a/nixos/modules/services/cluster/kubernetes/pki.nix b/nixos/modules/services/cluster/kubernetes/pki.nix
index 26fe0f5e9e097..38682701ea151 100644
--- a/nixos/modules/services/cluster/kubernetes/pki.nix
+++ b/nixos/modules/services/cluster/kubernetes/pki.nix
@@ -270,7 +270,7 @@ in
           '';
         })]);
 
-      environment.etc.${cfg.etcClusterAdminKubeconfig}.source = mkIf (!isNull cfg.etcClusterAdminKubeconfig)
+      environment.etc.${cfg.etcClusterAdminKubeconfig}.source = mkIf (cfg.etcClusterAdminKubeconfig != null)
         clusterAdminKubeconfig;
 
       environment.systemPackages = mkIf (top.kubelet.enable || top.proxy.enable) [
diff --git a/nixos/modules/services/continuous-integration/github-runner/options.nix b/nixos/modules/services/continuous-integration/github-runner/options.nix
index 6ce08aaaece84..ce88092137241 100644
--- a/nixos/modules/services/continuous-integration/github-runner/options.nix
+++ b/nixos/modules/services/continuous-integration/github-runner/options.nix
@@ -41,17 +41,42 @@ with lib;
   tokenFile = mkOption {
     type = types.path;
     description = lib.mdDoc ''
-      The full path to a file which contains either a runner registration token or a
-      (fine-grained) personal access token (PAT).
+      The full path to a file which contains either
+
+      * a fine-grained personal access token (PAT),
+      * a classic PAT
+      * or a runner registration token
+
+      Changing this option or the `tokenFile`’s content triggers a new runner registration.
+
+      We suggest using the fine-grained PATs. A runner registration token is valid
+      only for 1 hour after creation, so the next time the runner configuration changes
+      this will give you hard-to-debug HTTP 404 errors in the configure step.
+
       The file should contain exactly one line with the token without any newline.
+      (Use `echo -n '…token…' > …token file…` to make sure no newlines sneak in.)
+
+      If the file contains a PAT, the service creates a new registration token
+      on startup as needed.
       If a registration token is given, it can be used to re-register a runner of the same
-      name but is time-limited. If the file contains a PAT, the service creates a new
-      registration token on startup as needed. Make sure the PAT has a scope of
-      `admin:org` for organization-wide registrations or a scope of
-      `repo` for a single repository. Fine-grained PATs need read and write permission
-      to the "Administration" resources.
+      name but is time-limited as noted above.
+
+      For fine-grained PATs:
+
+      Give it "Read and Write access to organization/repository self hosted runners",
+      depending on whether it is organization wide or per-repository. You might have to
+      experiment a little, fine-grained PATs are a `beta` Github feature and still subject
+      to change; nonetheless they are the best option at the moment.
+
+      For classic PATs:
+
+      Make sure the PAT has a scope of `admin:org` for organization-wide registrations
+      or a scope of `repo` for a single repository.
+
+      For runner registration tokens:
 
-      Changing this option or the file's content triggers a new runner registration.
+      Nothing special needs to be done, but updating will break after one hour,
+      so these are not recommended.
     '';
     example = "/run/secrets/github-runner/nixos.token";
   };
diff --git a/nixos/modules/services/continuous-integration/github-runner/service.nix b/nixos/modules/services/continuous-integration/github-runner/service.nix
index db9a19815ec1b..55df83362cb60 100644
--- a/nixos/modules/services/continuous-integration/github-runner/service.nix
+++ b/nixos/modules/services/continuous-integration/github-runner/service.nix
@@ -124,6 +124,8 @@ in
               # The state directory is entirely empty which indicates a first start
               copy_tokens
             fi
+            # Always clean workDir
+            find -H "$WORK_DIRECTORY" -mindepth 1 -delete
           '';
           configureRunner = writeScript "configure" ''
             if [[ -e "${newConfigTokenPath}" ]]; then
@@ -147,7 +149,7 @@ in
               else
                 args+=(--token "$token")
               fi
-              ${cfg.package}/bin/config.sh "''${args[@]}"
+              ${cfg.package}/bin/Runner.Listener configure "''${args[@]}"
               # Move the automatically created _diag dir to the logs dir
               mkdir -p  "$STATE_DIRECTORY/_diag"
               cp    -r  "$STATE_DIRECTORY/_diag/." "$LOGS_DIRECTORY/"
@@ -159,9 +161,6 @@ in
             fi
           '';
           setupWorkDir = writeScript "setup-work-dirs" ''
-            # Cleanup previous service
-            ${pkgs.findutils}/bin/find -H "$WORK_DIRECTORY" -mindepth 1 -delete
-
             # Link _diag dir
             ln -s "$LOGS_DIRECTORY" "$WORK_DIRECTORY/_diag"
 
diff --git a/nixos/modules/services/continuous-integration/gitlab-runner.nix b/nixos/modules/services/continuous-integration/gitlab-runner.nix
index 3e6dba16e8ace..53f39f40daa5a 100644
--- a/nixos/modules/services/continuous-integration/gitlab-runner.nix
+++ b/nixos/modules/services/continuous-integration/gitlab-runner.nix
@@ -34,7 +34,7 @@ let
     text = if (cfg.configFile != null) then ''
       cp ${cfg.configFile} ${configPath}
       # make config file readable by service
-      chown -R --reference=$HOME $(dirname ${configPath})
+      chown -R --reference="$HOME" "$(dirname ${configPath})"
     '' else ''
       export CONFIG_FILE=${configPath}
 
@@ -577,7 +577,7 @@ in {
       };
     };
     # Enable periodic clear-docker-cache script
-    systemd.services.gitlab-runner-clear-docker-cache = {
+    systemd.services.gitlab-runner-clear-docker-cache = mkIf (cfg.clear-docker-cache.enable && (any (s: s.executor == "docker") (attrValues cfg.services))) {
       description = "Prune gitlab-runner docker resources";
       restartIfChanged = false;
       unitConfig.X-StopOnRemoval = false;
@@ -590,7 +590,7 @@ in {
         ${pkgs.gitlab-runner}/bin/clear-docker-cache ${toString cfg.clear-docker-cache.flags}
       '';
 
-      startAt = optional cfg.clear-docker-cache.enable cfg.clear-docker-cache.dates;
+      startAt = cfg.clear-docker-cache.dates;
     };
     # Enable docker if `docker` executor is used in any service
     virtualisation.docker.enable = mkIf (
diff --git a/nixos/modules/services/continuous-integration/hydra/default.nix b/nixos/modules/services/continuous-integration/hydra/default.nix
index 564bcd37dec5e..83078706fcaec 100644
--- a/nixos/modules/services/continuous-integration/hydra/default.nix
+++ b/nixos/modules/services/continuous-integration/hydra/default.nix
@@ -398,7 +398,7 @@ in
     systemd.services.hydra-evaluator =
       { wantedBy = [ "multi-user.target" ];
         requires = [ "hydra-init.service" ];
-        after = [ "hydra-init.service" "network.target" ];
+        after = [ "hydra-init.service" "network.target" "network-online.target" ];
         path = with pkgs; [ hydra-package nettools jq ];
         restartTriggers = [ hydraConf ];
         environment = env // {
diff --git a/nixos/modules/services/continuous-integration/woodpecker/agent.nix b/nixos/modules/services/continuous-integration/woodpecker/agent.nix
new file mode 100644
index 0000000000000..1aedec81c9651
--- /dev/null
+++ b/nixos/modules/services/continuous-integration/woodpecker/agent.nix
@@ -0,0 +1,99 @@
+{ config
+, lib
+, pkgs
+, ...
+}:
+
+let
+  cfg = config.services.woodpecker-agent;
+in
+{
+  meta.maintainers = [ lib.maintainers.janik ];
+
+  options = {
+    services.woodpecker-agent = {
+      enable = lib.mkEnableOption (lib.mdDoc "the Woodpecker-Agent, Agents execute tasks generated by a Server, every install will need one server and at least one agent");
+      package = lib.mkPackageOptionMD pkgs "woodpecker-agent" { };
+
+      environment = lib.mkOption {
+        default = { };
+        type = lib.types.attrsOf lib.types.str;
+        example = lib.literalExpression ''
+          {
+            WOODPECKER_SERVER = "localhost:9000";
+            WOODPECKER_BACKEND = "docker";
+            DOCKER_HOST = "unix:///run/podman/podman.sock";
+          }
+        '';
+        description = lib.mdDoc "woodpecker-agent config envrionment variables, for other options read the [documentation](https://woodpecker-ci.org/docs/administration/agent-config)";
+      };
+
+      extraGroups = lib.mkOption {
+        default = null;
+        type = lib.types.nullOr (lib.types.listOf lib.types.str);
+        example = [ "podman" ];
+        description = lib.mdDoc ''
+          Additional groups for the systemd service.
+        '';
+      };
+
+      environmentFile = lib.mkOption {
+        type = lib.types.nullOr lib.types.path;
+        default = null;
+        example = "/root/woodpecker-agent.env";
+        description = lib.mdDoc ''
+          File to load environment variables
+          from. This is helpful for specifying secrets.
+          Example content of environmentFile:
+          ```
+          WOODPECKER_AGENT_SECRET=your-shared-secret-goes-here
+          ```
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services = {
+      woodpecker-agent = {
+        description = "Woodpecker-Agent Service";
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network-online.target" ];
+        wants = [ "network-online.target" ];
+        serviceConfig = {
+          DynamicUser = true;
+          SupplementaryGroups = lib.optionals (cfg.extraGroups != null) cfg.extraGroups;
+          EnvironmentFile = lib.optional (cfg.environmentFile != null) cfg.environmentFile;
+          ExecStart = "${cfg.package}/bin/woodpecker-agent";
+          Restart = "on-failure";
+          RestartSec = 15;
+          CapabilityBoundingSet = "";
+          # Security
+          NoNewPrivileges = true;
+          # Sandboxing
+          ProtectSystem = "strict";
+          PrivateTmp = true;
+          PrivateDevices = true;
+          PrivateUsers = true;
+          ProtectHostname = true;
+          ProtectClock = true;
+          ProtectKernelTunables = true;
+          ProtectKernelModules = true;
+          ProtectKernelLogs = true;
+          ProtectControlGroups = true;
+          RestrictAddressFamilies = [ "AF_UNIX AF_INET AF_INET6" ];
+          LockPersonality = true;
+          MemoryDenyWriteExecute = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+          PrivateMounts = true;
+          # System Call Filtering
+          SystemCallArchitectures = "native";
+          SystemCallFilter = "~@clock @privileged @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @reboot @setuid @swap";
+        };
+        inherit (cfg) environment;
+      };
+    };
+  };
+}
+
diff --git a/nixos/modules/services/continuous-integration/woodpecker/server.nix b/nixos/modules/services/continuous-integration/woodpecker/server.nix
new file mode 100644
index 0000000000000..6b4e4732465c0
--- /dev/null
+++ b/nixos/modules/services/continuous-integration/woodpecker/server.nix
@@ -0,0 +1,98 @@
+{ config
+, lib
+, pkgs
+, ...
+}:
+
+let
+  cfg = config.services.woodpecker-server;
+in
+{
+  meta.maintainers = [ lib.maintainers.janik ];
+
+
+  options = {
+    services.woodpecker-server = {
+      enable = lib.mkEnableOption (lib.mdDoc "the Woodpecker-Server, a CI/CD application for automatic builds, deployments and tests");
+      package = lib.mkPackageOptionMD pkgs "woodpecker-server" { };
+      environment = lib.mkOption {
+        default = { };
+        type = lib.types.attrsOf lib.types.str;
+        example = lib.literalExpression
+          ''
+            {
+              WOODPECKER_HOST = "https://woodpecker.example.com";
+              WOODPECKER_OPEN = "true";
+              WOODPECKER_GITEA = "true";
+              WOODPECKER_GITEA_CLIENT = "ffffffff-ffff-ffff-ffff-ffffffffffff";
+              WOODPECKER_GITEA_URL = "https://git.example.com";
+            }
+          '';
+        description = lib.mdDoc "woodpecker-server config envrionment variables, for other options read the [documentation](https://woodpecker-ci.org/docs/administration/server-config)";
+      };
+      environmentFile = lib.mkOption {
+        type = lib.types.nullOr lib.types.path;
+        default = null;
+        example = "/root/woodpecker-server.env";
+        description = lib.mdDoc ''
+          File to load environment variables
+          from. This is helpful for specifying secrets.
+          Example content of environmentFile:
+          ```
+          WOODPECKER_AGENT_SECRET=your-shared-secret-goes-here
+          WOODPECKER_GITEA_SECRET=gto_**************************************
+          ```
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services = {
+      woodpecker-server = {
+        description = "Woodpecker-Server Service";
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network-online.target" ];
+        wants = [ "network-online.target" ];
+        serviceConfig = {
+          DynamicUser = true;
+          WorkingDirectory = "%S/woodpecker-server";
+          StateDirectory = "woodpecker-server";
+          StateDirectoryMode = "0700";
+          UMask = "0007";
+          ConfigurationDirectory = "woodpecker-server";
+          EnvironmentFile = lib.optional (cfg.environmentFile != null) cfg.environmentFile;
+          ExecStart = "${cfg.package}/bin/woodpecker-server";
+          Restart = "on-failure";
+          RestartSec = 15;
+          CapabilityBoundingSet = "";
+          # Security
+          NoNewPrivileges = true;
+          # Sandboxing
+          ProtectSystem = "strict";
+          ProtectHome = true;
+          PrivateTmp = true;
+          PrivateDevices = true;
+          PrivateUsers = true;
+          ProtectHostname = true;
+          ProtectClock = true;
+          ProtectKernelTunables = true;
+          ProtectKernelModules = true;
+          ProtectKernelLogs = true;
+          ProtectControlGroups = true;
+          RestrictAddressFamilies = [ "AF_UNIX AF_INET AF_INET6" ];
+          LockPersonality = true;
+          MemoryDenyWriteExecute = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+          PrivateMounts = true;
+          # System Call Filtering
+          SystemCallArchitectures = "native";
+          SystemCallFilter = "~@clock @privileged @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @reboot @setuid @swap";
+        };
+        inherit (cfg) environment;
+      };
+    };
+  };
+}
+
diff --git a/nixos/modules/services/databases/clickhouse.nix b/nixos/modules/services/databases/clickhouse.nix
index 04dd20b5f14d1..1f4a39765cd77 100644
--- a/nixos/modules/services/databases/clickhouse.nix
+++ b/nixos/modules/services/databases/clickhouse.nix
@@ -54,7 +54,7 @@ with lib;
         AmbientCapabilities = "CAP_SYS_NICE";
         StateDirectory = "clickhouse";
         LogsDirectory = "clickhouse";
-        ExecStart = "${cfg.package}/bin/clickhouse-server --config-file=${cfg.package}/etc/clickhouse-server/config.xml";
+        ExecStart = "${cfg.package}/bin/clickhouse-server --config-file=/etc/clickhouse-server/config.xml";
       };
     };
 
diff --git a/nixos/modules/services/desktops/pipewire/daemon/pipewire-aes67.conf.json b/nixos/modules/services/desktops/pipewire/daemon/pipewire-aes67.conf.json
new file mode 100644
index 0000000000000..aaffa93ca964c
--- /dev/null
+++ b/nixos/modules/services/desktops/pipewire/daemon/pipewire-aes67.conf.json
@@ -0,0 +1,38 @@
+{
+  "context.properties": {},
+  "context.modules": [
+    {
+      "name": "libpipewire-module-rt",
+      "args": {
+        "nice.level": -11
+      },
+      "flags": [
+        "ifexists",
+        "nofail"
+      ]
+    },
+    {
+      "name": "libpipewire-module-protocol-native"
+    },
+    {
+      "name": "libpipewire-module-client-node"
+    },
+    {
+      "name": "libpipewire-module-adapter"
+    },
+    {
+      "name": "libpipewire-module-rtp-source",
+      "args": {
+        "sap.ip": "239.255.255.255",
+        "sap.port": 9875,
+        "sess.latency.msec": 10,
+        "local.ifname": "eth0",
+        "stream.props": {
+          "media.class": "Audio/Source",
+          "node.virtual": false,
+          "device.api": "aes67"
+        }
+      }
+    }
+  ]
+}
diff --git a/nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json b/nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json
index 53fc103d22144..a47abe2213d94 100644
--- a/nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json
+++ b/nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json
@@ -3,10 +3,10 @@
     "link.max-buffers": 16,
     "core.daemon": true,
     "core.name": "pipewire-0",
-    "default.clock.min-quantum": 16,
     "vm.overrides": {
       "default.clock.min-quantum": 1024
-    }
+    },
+    "module.x11.bell": true
   },
   "context.spa-libs": {
     "audio.convert.*": "audioconvert/libspa-audioconvert",
@@ -77,6 +77,11 @@
       "flags": [
         "ifexists",
         "nofail"
+      ],
+      "condition": [
+        {
+          "module.x11.bell": true
+        }
       ]
     }
   ],
diff --git a/nixos/modules/services/development/gemstash.nix b/nixos/modules/services/development/gemstash.nix
new file mode 100644
index 0000000000000..eb7ccb98bde89
--- /dev/null
+++ b/nixos/modules/services/development/gemstash.nix
@@ -0,0 +1,103 @@
+{ lib, pkgs, config, ... }:
+with lib;
+
+let
+  settingsFormat = pkgs.formats.yaml { };
+
+  # gemstash uses a yaml config where the keys are ruby symbols,
+  # which means they start with ':'. This would be annoying to use
+  # on the nix side, so we rewrite plain names instead.
+  prefixColon = s: listToAttrs (map
+    (attrName: {
+      name = ":${attrName}";
+      value =
+        if isAttrs s.${attrName}
+        then prefixColon s."${attrName}"
+        else s."${attrName}";
+    })
+    (attrNames s));
+
+  # parse the port number out of the tcp://ip:port bind setting string
+  parseBindPort = bind: strings.toInt (last (strings.splitString ":" bind));
+
+  cfg = config.services.gemstash;
+in
+{
+  options.services.gemstash = {
+    enable = mkEnableOption (lib.mdDoc "gemstash service");
+
+    openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Whether to open the firewall for the port in {option}`services.gemstash.bind`.
+      '';
+    };
+
+    settings = mkOption {
+      default = {};
+      description = lib.mdDoc ''
+        Configuration for Gemstash. The details can be found at in
+        [gemstash documentation](https://github.com/rubygems/gemstash/blob/master/man/gemstash-configuration.5.md).
+        Each key set here is automatically prefixed with ":" to match the gemstash expectations.
+      '';
+      type = types.submodule {
+        freeformType = settingsFormat.type;
+        options = {
+          base_path = mkOption {
+            type = types.path;
+            default = "/var/lib/gemstash";
+            description = lib.mdDoc "Path to store the gem files and the sqlite database. If left unchanged, the directory will be created.";
+          };
+          bind = mkOption {
+            type = types.str;
+            default = "tcp://0.0.0.0:9292";
+            description = lib.mdDoc "Host and port combination for the server to listen on.";
+          };
+          db_adapter = mkOption {
+            type = types.nullOr (types.enum [ "sqlite3" "postgres" "mysql" "mysql2" ]);
+            default = null;
+            description = lib.mdDoc "Which database type to use. For choices other than sqlite3, the dbUrl has to be specified as well.";
+          };
+          db_url = mkOption {
+            type = types.nullOr types.str;
+            default = null;
+            description = lib.mdDoc "The database to connect to when using postgres, mysql, or mysql2.";
+          };
+        };
+      };
+    };
+  };
+
+  config =
+    mkIf cfg.enable {
+      users = {
+        users.gemstash = {
+          group = "gemstash";
+          isSystemUser = true;
+        };
+        groups.gemstash = { };
+      };
+
+      networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ (parseBindPort cfg.settings.bind) ];
+
+      systemd.services.gemstash = {
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+        serviceConfig = mkMerge [
+          {
+            ExecStart = "${pkgs.gemstash}/bin/gemstash start --no-daemonize --config-file ${settingsFormat.generate "gemstash.yaml" (prefixColon cfg.settings)}";
+            NoNewPrivileges = true;
+            User = "gemstash";
+            Group = "gemstash";
+            PrivateTmp = true;
+            RestrictSUIDSGID = true;
+            LockPersonality = true;
+          }
+          (mkIf (cfg.settings.base_path == "/var/lib/gemstash") {
+            StateDirectory = "gemstash";
+          })
+        ];
+      };
+    };
+}
diff --git a/nixos/modules/services/hardware/fwupd.nix b/nixos/modules/services/hardware/fwupd.nix
index 4e7d730d127b7..b8c2ac94845b0 100644
--- a/nixos/modules/services/hardware/fwupd.nix
+++ b/nixos/modules/services/hardware/fwupd.nix
@@ -18,6 +18,12 @@ let
         fwupd = cfg.daemonSettings;
       };
     };
+
+    "fwupd/uefi_capsule.conf" = {
+      source = format.generate "uefi_capsule.conf" {
+        uefi_capsule = cfg.uefiCapsuleSettings;
+      };
+    };
   };
 
   originalEtc =
@@ -138,6 +144,16 @@ in {
           Configurations for the fwupd daemon.
         '';
       };
+
+      uefiCapsuleSettings = mkOption {
+        type = types.submodule {
+          freeformType = format.type.nestedTypes.elemType;
+        };
+        default = {};
+        description = lib.mdDoc ''
+          UEFI capsule configurations for the fwupd daemon.
+        '';
+      };
     };
   };
 
diff --git a/nixos/modules/services/hardware/kanata.nix b/nixos/modules/services/hardware/kanata.nix
index 84265eb8f947c..bb730037277b8 100644
--- a/nixos/modules/services/hardware/kanata.nix
+++ b/nixos/modules/services/hardware/kanata.nix
@@ -8,19 +8,9 @@ let
   keyboard = {
     options = {
       devices = mkOption {
-        type = types.addCheck (types.listOf types.str)
-          (devices: (length devices) > 0);
+        type = types.listOf types.str;
         example = [ "/dev/input/by-id/usb-0000_0000-event-kbd" ];
-        # TODO replace note with tip, which has not been implemented yet in
-        # nixos/lib/make-options-doc/mergeJSON.py
-        description = mdDoc ''
-          Paths to keyboard devices.
-
-          ::: {.note}
-          To avoid unnecessary triggers of the service unit, unplug devices in
-          the order of the list.
-          :::
-        '';
+        description = mdDoc "Paths to keyboard devices.";
       };
       config = mkOption {
         type = types.lines;
@@ -44,8 +34,10 @@ let
             cap (tap-hold 100 100 caps lctl))
         '';
         description = mdDoc ''
-          Configuration other than `defcfg`. See [example config
-          files](https://github.com/jtroo/kanata) for more information.
+          Configuration other than `defcfg`.
+
+          See [example config files](https://github.com/jtroo/kanata)
+          for more information.
         '';
       };
       extraDefCfg = mkOption {
@@ -53,8 +45,12 @@ let
         default = "";
         example = "danger-enable-cmd yes";
         description = mdDoc ''
-          Configuration of `defcfg` other than `linux-dev`. See [example
-          config files](https://github.com/jtroo/kanata) for more information.
+          Configuration of `defcfg` other than `linux-dev` (generated
+          from the devices option) and
+          `linux-continue-if-no-devs-found` (hardcoded to be yes).
+
+          See [example config files](https://github.com/jtroo/kanata)
+          for more information.
         '';
       };
       extraArgs = mkOption {
@@ -67,8 +63,7 @@ let
         default = null;
         example = 6666;
         description = mdDoc ''
-          Port to run the notification server on. `null` will not run the
-          server.
+          Port to run the TCP server on. `null` will not run the server.
         '';
       };
     };
@@ -76,28 +71,23 @@ let
 
   mkName = name: "kanata-${name}";
 
-  mkDevices = devices: concatStringsSep ":" devices;
+  mkDevices = devices:
+    optionalString ((length devices) > 0) "linux-dev ${concatStringsSep ":" devices}";
 
   mkConfig = name: keyboard: pkgs.writeText "${mkName name}-config.kdb" ''
     (defcfg
       ${keyboard.extraDefCfg}
-      linux-dev ${mkDevices keyboard.devices})
+      ${mkDevices keyboard.devices}
+      linux-continue-if-no-devs-found yes)
 
     ${keyboard.config}
   '';
 
   mkService = name: keyboard: nameValuePair (mkName name) {
-    description = "kanata for ${mkDevices keyboard.devices}";
-
-    # Because path units are used to activate service units, which
-    # will start the old stopped services during "nixos-rebuild
-    # switch", stopIfChanged here is a workaround to make sure new
-    # services are running after "nixos-rebuild switch".
-    stopIfChanged = false;
-
+    wantedBy = [ "multi-user.target" ];
     serviceConfig = {
       ExecStart = ''
-        ${cfg.package}/bin/kanata \
+        ${getExe cfg.package} \
           --cfg ${mkConfig name keyboard} \
           --symlink-path ''${RUNTIME_DIRECTORY}/${name} \
           ${optionalString (keyboard.port != null) "--port ${toString keyboard.port}"} \
@@ -146,37 +136,10 @@ let
       UMask = "0077";
     };
   };
-
-  mkPathName = i: name: "${mkName name}-${toString i}";
-
-  mkPath = name: n: i: device:
-    nameValuePair (mkPathName i name) {
-      description =
-        "${toString (i+1)}/${toString n} kanata trigger for ${name}, watching ${device}";
-      wantedBy = optional (i == 0) "multi-user.target";
-      pathConfig = {
-        PathExists = device;
-        # (ab)use systemd.path to construct a trigger chain so that the
-        # service unit is only started when all paths exist
-        # however, manual of systemd.path says Unit's suffix is not ".path"
-        Unit =
-          if (i + 1) == n
-          then "${mkName name}.service"
-          else "${mkPathName (i + 1) name}.path";
-      };
-      unitConfig.StopPropagatedFrom = optional (i > 0) "${mkName name}.service";
-    };
-
-  mkPaths = name: keyboard:
-    let
-      n = length keyboard.devices;
-    in
-    imap0 (mkPath name n) keyboard.devices
-  ;
 in
 {
   options.services.kanata = {
-    enable = mkEnableOption (lib.mdDoc "kanata");
+    enable = mkEnableOption (mdDoc "kanata");
     package = mkOption {
       type = types.package;
       default = pkgs.kanata;
@@ -201,14 +164,7 @@ in
   config = mkIf cfg.enable {
     hardware.uinput.enable = true;
 
-    systemd = {
-      paths = trivial.pipe cfg.keyboards [
-        (mapAttrsToList mkPaths)
-        concatLists
-        listToAttrs
-      ];
-      services = mapAttrs' mkService cfg.keyboards;
-    };
+    systemd.services = mapAttrs' mkService cfg.keyboards;
   };
 
   meta.maintainers = with maintainers; [ linj ];
diff --git a/nixos/modules/services/hardware/keyd.nix b/nixos/modules/services/hardware/keyd.nix
new file mode 100644
index 0000000000000..64c769405fabc
--- /dev/null
+++ b/nixos/modules/services/hardware/keyd.nix
@@ -0,0 +1,112 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.keyd;
+  settingsFormat = pkgs.formats.ini { };
+in
+{
+  options = {
+    services.keyd = {
+      enable = mkEnableOption (lib.mdDoc "keyd, a key remapping daemon");
+
+      ids = mkOption {
+        type = types.listOf types.string;
+        default = [ "*" ];
+        example = [ "*" "-0123:0456" ];
+        description = lib.mdDoc ''
+          Device identifiers, as shown by {manpage}`keyd(1)`.
+        '';
+      };
+
+      settings = mkOption {
+        type = settingsFormat.type;
+        default = { };
+        example = {
+          main = {
+            capslock = "overload(control, esc)";
+            rightalt = "layer(rightalt)";
+          };
+
+          rightalt = {
+            j = "down";
+            k = "up";
+            h = "left";
+            l = "right";
+          };
+        };
+        description = lib.mdDoc ''
+          Configuration, except `ids` section, that is written to {file}`/etc/keyd/default.conf`.
+          See <https://github.com/rvaiya/keyd> how to configure.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.etc."keyd/default.conf".source = pkgs.runCommand "default.conf"
+      {
+        ids = ''
+          [ids]
+          ${concatStringsSep "\n" cfg.ids}
+        '';
+        passAsFile = [ "ids" ];
+      } ''
+      cat $idsPath <(echo) ${settingsFormat.generate "keyd-main.conf" cfg.settings} >$out
+    '';
+
+    hardware.uinput.enable = lib.mkDefault true;
+
+    systemd.services.keyd = {
+      description = "Keyd remapping daemon";
+      documentation = [ "man:keyd(1)" ];
+
+      wantedBy = [ "multi-user.target" ];
+
+      restartTriggers = [
+        config.environment.etc."keyd/default.conf".source
+      ];
+
+      # this is configurable in 2.4.2, later versions seem to remove this option.
+      # post-2.4.2 may need to set makeFlags in the derivation:
+      #
+      #     makeFlags = [ "SOCKET_PATH/run/keyd/keyd.socket" ];
+      environment.KEYD_SOCKET = "/run/keyd/keyd.sock";
+
+      serviceConfig = {
+        ExecStart = "${pkgs.keyd}/bin/keyd";
+        Restart = "always";
+
+        DynamicUser = true;
+        SupplementaryGroups = [
+          config.users.groups.input.name
+          config.users.groups.uinput.name
+        ];
+
+        RuntimeDirectory = "keyd";
+
+        # Hardening
+        CapabilityBoundingSet = "";
+        DeviceAllow = [
+          "char-input rw"
+          "/dev/uinput rw"
+        ];
+        ProtectClock = true;
+        PrivateNetwork = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        PrivateUsers = true;
+        PrivateMounts = true;
+        RestrictNamespaces = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectControlGroups = true;
+        MemoryDenyWriteExecute = true;
+        RestrictRealtime = true;
+        LockPersonality = true;
+        ProtectProc = "noaccess";
+        UMask = "0077";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/hardware/supergfxd.nix b/nixos/modules/services/hardware/supergfxd.nix
index df339e4ba011f..5ea05ac277167 100644
--- a/nixos/modules/services/hardware/supergfxd.nix
+++ b/nixos/modules/services/hardware/supergfxd.nix
@@ -32,6 +32,7 @@ in
 
     systemd.packages = [ pkgs.supergfxctl ];
     systemd.services.supergfxd.wantedBy = [ "multi-user.target" ];
+    systemd.services.supergfxd.path = [ pkgs.kmod ];
 
     services.dbus.packages = [ pkgs.supergfxctl ];
     services.udev.packages = [ pkgs.supergfxctl ];
diff --git a/nixos/modules/services/hardware/undervolt.nix b/nixos/modules/services/hardware/undervolt.nix
index c49d944cdc188..944777475401b 100644
--- a/nixos/modules/services/hardware/undervolt.nix
+++ b/nixos/modules/services/hardware/undervolt.nix
@@ -5,8 +5,8 @@ let
   cfg = config.services.undervolt;
 
   mkPLimit = limit: window:
-    if (isNull limit && isNull window) then null
-    else assert asserts.assertMsg (!isNull limit && !isNull window) "Both power limit and window must be set";
+    if (limit == null && window == null) then null
+    else assert asserts.assertMsg (limit != null && window != null) "Both power limit and window must be set";
       "${toString limit} ${toString window}";
   cliArgs = lib.cli.toGNUCommandLine {} {
     inherit (cfg)
diff --git a/nixos/modules/services/home-automation/home-assistant.nix b/nixos/modules/services/home-automation/home-assistant.nix
index 9f6f153a10568..cea8a2b14cc22 100644
--- a/nixos/modules/services/home-automation/home-assistant.nix
+++ b/nixos/modules/services/home-automation/home-assistant.nix
@@ -362,7 +362,7 @@ in {
   config = mkIf cfg.enable {
     assertions = [
       {
-        assertion = cfg.openFirewall -> !isNull cfg.config;
+        assertion = cfg.openFirewall -> cfg.config != null;
         message = "openFirewall can only be used with a declarative config";
       }
     ];
@@ -409,6 +409,7 @@ in {
         (optionalString (cfg.config != null) copyConfig) +
         (optionalString (cfg.lovelaceConfig != null) copyLovelaceConfig)
       ;
+      environment.PYTHONPATH = package.pythonPath;
       serviceConfig = let
         # List of capabilities to equip home-assistant with, depending on configured components
         capabilities = lib.unique ([
diff --git a/nixos/modules/services/mail/maddy.nix b/nixos/modules/services/mail/maddy.nix
index eeb113e204c6e..5f3a9b56292d2 100644
--- a/nixos/modules/services/mail/maddy.nix
+++ b/nixos/modules/services/mail/maddy.nix
@@ -223,22 +223,59 @@ in {
         '';
       };
 
+      ensureAccounts = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = lib.mdDoc ''
+          List of IMAP accounts which get automatically created. Note that for
+          a complete setup, user credentials for these accounts are required too
+          and can be created using the command `maddyctl creds`.
+          This option does not delete accounts which are not (anymore) listed.
+        '';
+        example = [
+          "user1@localhost"
+          "user2@localhost"
+        ];
+      };
+
     };
   };
 
   config = mkIf cfg.enable {
 
     systemd = {
+
       packages = [ pkgs.maddy ];
-      services.maddy = {
-        serviceConfig = {
-          User = cfg.user;
-          Group = cfg.group;
-          StateDirectory = [ "maddy" ];
+      services = {
+        maddy = {
+          serviceConfig = {
+            User = cfg.user;
+            Group = cfg.group;
+            StateDirectory = [ "maddy" ];
+          };
+          restartTriggers = [ config.environment.etc."maddy/maddy.conf".source ];
+          wantedBy = [ "multi-user.target" ];
+        };
+        maddy-ensure-accounts = {
+          script = ''
+            ${optionalString (cfg.ensureAccounts != []) ''
+              ${concatMapStrings (account: ''
+                if ! ${pkgs.maddy}/bin/maddyctl imap-acct list | grep "${account}"; then
+                  ${pkgs.maddy}/bin/maddyctl imap-acct create ${account}
+                fi
+              '') cfg.ensureAccounts}
+            ''}
+          '';
+          serviceConfig = {
+            Type = "oneshot";
+            User= "maddy";
+          };
+          after = [ "maddy.service" ];
+          wantedBy = [ "multi-user.target" ];
         };
-        restartTriggers = [ config.environment.etc."maddy/maddy.conf".source ];
-        wantedBy = [ "multi-user.target" ];
+
       };
+
     };
 
     environment.etc."maddy/maddy.conf" = {
diff --git a/nixos/modules/services/mail/postfix.nix b/nixos/modules/services/mail/postfix.nix
index d01734d61e87e..852340c05aa7a 100644
--- a/nixos/modules/services/mail/postfix.nix
+++ b/nixos/modules/services/mail/postfix.nix
@@ -809,7 +809,7 @@ in
       // optionalAttrs (cfg.relayHost != "") { relayhost = if cfg.lookupMX
                                                            then "${cfg.relayHost}:${toString cfg.relayPort}"
                                                            else "[${cfg.relayHost}]:${toString cfg.relayPort}"; }
-      // optionalAttrs config.networking.enableIPv6 { inet_protocols = mkDefault "all"; }
+      // optionalAttrs (!config.networking.enableIPv6) { inet_protocols = mkDefault "ipv4"; }
       // optionalAttrs (cfg.networks != null) { mynetworks = cfg.networks; }
       // optionalAttrs (cfg.networksStyle != "") { mynetworks_style = cfg.networksStyle; }
       // optionalAttrs (cfg.hostname != "") { myhostname = cfg.hostname; }
diff --git a/nixos/modules/services/mail/roundcube.nix b/nixos/modules/services/mail/roundcube.nix
index 95dc2f6aa2c92..7b6d82219298c 100644
--- a/nixos/modules/services/mail/roundcube.nix
+++ b/nixos/modules/services/mail/roundcube.nix
@@ -132,6 +132,8 @@ in
       $config['plugins'] = [${concatMapStringsSep "," (p: "'${p}'") cfg.plugins}];
       $config['des_key'] = file_get_contents('/var/lib/roundcube/des_key');
       $config['mime_types'] = '${pkgs.nginx}/conf/mime.types';
+      # Roundcube uses PHP-FPM which has `PrivateTmp = true;`
+      $config['temp_dir'] = '/tmp';
       $config['enable_spellcheck'] = ${if cfg.dicts == [] then "false" else "true"};
       # by default, spellchecking uses a third-party cloud services
       $config['spellcheck_engine'] = 'pspell';
diff --git a/nixos/modules/services/matrix/appservice-discord.nix b/nixos/modules/services/matrix/appservice-discord.nix
index 15f0f0cc0cdbf..f579c2529c0a5 100644
--- a/nixos/modules/services/matrix/appservice-discord.nix
+++ b/nixos/modules/services/matrix/appservice-discord.nix
@@ -5,7 +5,6 @@ with lib;
 let
   dataDir = "/var/lib/matrix-appservice-discord";
   registrationFile = "${dataDir}/discord-registration.yaml";
-  appDir = "${pkgs.matrix-appservice-discord}/${pkgs.matrix-appservice-discord.passthru.nodeAppDir}";
   cfg = config.services.matrix-appservice-discord;
   opt = options.services.matrix-appservice-discord;
   # TODO: switch to configGen.json once RFC42 is implemented
@@ -16,6 +15,15 @@ in {
     services.matrix-appservice-discord = {
       enable = mkEnableOption (lib.mdDoc "a bridge between Matrix and Discord");
 
+      package = mkOption {
+        type = types.package;
+        default = pkgs.matrix-appservice-discord;
+        defaultText = literalExpression "pkgs.matrix-appservice-discord";
+        description = lib.mdDoc ''
+          Which package of matrix-appservice-discord to use.
+        '';
+      };
+
       settings = mkOption rec {
         # TODO: switch to types.config.json as prescribed by RFC42 once it's implemented
         type = types.attrs;
@@ -114,7 +122,7 @@ in {
 
       preStart = ''
         if [ ! -f '${registrationFile}' ]; then
-          ${pkgs.matrix-appservice-discord}/bin/matrix-appservice-discord \
+          ${cfg.package}/bin/matrix-appservice-discord \
             --generate-registration \
             --url=${escapeShellArg cfg.url} \
             ${optionalString (cfg.localpart != null) "--localpart=${escapeShellArg cfg.localpart}"} \
@@ -135,13 +143,13 @@ in {
 
         DynamicUser = true;
         PrivateTmp = true;
-        WorkingDirectory = appDir;
+        WorkingDirectory = "${cfg.package}/${cfg.package.passthru.nodeAppDir}";
         StateDirectory = baseNameOf dataDir;
         UMask = "0027";
         EnvironmentFile = cfg.environmentFile;
 
         ExecStart = ''
-          ${pkgs.matrix-appservice-discord}/bin/matrix-appservice-discord \
+          ${cfg.package}/bin/matrix-appservice-discord \
             --file='${registrationFile}' \
             --config='${settingsFile}' \
             --port='${toString cfg.port}'
diff --git a/nixos/modules/services/matrix/dendrite.nix b/nixos/modules/services/matrix/dendrite.nix
index a5fea3da48446..a8006547fc6b4 100644
--- a/nixos/modules/services/matrix/dendrite.nix
+++ b/nixos/modules/services/matrix/dendrite.nix
@@ -288,11 +288,11 @@ in
         LimitNOFILE = 65535;
         EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
         LoadCredential = cfg.loadCredential;
-        ExecStartPre = ''
+        ExecStartPre = [''
           ${pkgs.envsubst}/bin/envsubst \
             -i ${configurationYaml} \
             -o /run/dendrite/dendrite.yaml
-        '';
+        ''];
         ExecStart = lib.strings.concatStringsSep " " ([
           "${pkgs.dendrite}/bin/dendrite-monolith-server"
           "--config /run/dendrite/dendrite.yaml"
diff --git a/nixos/modules/services/matrix/mautrix-facebook.nix b/nixos/modules/services/matrix/mautrix-facebook.nix
index e74f25df764db..e995f1aecf27d 100644
--- a/nixos/modules/services/matrix/mautrix-facebook.nix
+++ b/nixos/modules/services/matrix/mautrix-facebook.nix
@@ -96,7 +96,7 @@ in {
         type = types.nullOr types.path;
         default = null;
         description = lib.mdDoc ''
-          File containing environment variables to be passed to the mautrix-telegram service.
+          File containing environment variables to be passed to the mautrix-facebook service.
 
           Any config variable can be overridden by setting `MAUTRIX_FACEBOOK_SOME_KEY` to override the `some.key` variable.
         '';
diff --git a/nixos/modules/services/matrix/mautrix-telegram.nix b/nixos/modules/services/matrix/mautrix-telegram.nix
index 5a632fd27e80d..b64cc71d98735 100644
--- a/nixos/modules/services/matrix/mautrix-telegram.nix
+++ b/nixos/modules/services/matrix/mautrix-telegram.nix
@@ -137,7 +137,7 @@ in {
       wantedBy = [ "multi-user.target" ];
       wants = [ "network-online.target" ] ++ cfg.serviceDependencies;
       after = [ "network-online.target" ] ++ cfg.serviceDependencies;
-      path = [ pkgs.lottieconverter ];
+      path = [ pkgs.lottieconverter pkgs.ffmpeg-full ];
 
       # mautrix-telegram tries to generate a dotfile in the home directory of
       # the running user if using a postgresql database:
diff --git a/nixos/modules/services/matrix/synapse.md b/nixos/modules/services/matrix/synapse.md
index 22f3bce64a40c..cad91ebf58d56 100644
--- a/nixos/modules/services/matrix/synapse.md
+++ b/nixos/modules/services/matrix/synapse.md
@@ -27,11 +27,8 @@ please refer to the
 { pkgs, lib, config, ... }:
 let
   fqdn = "${config.networking.hostName}.${config.networking.domain}";
-  clientConfig = {
-    "m.homeserver".base_url = "https://${fqdn}";
-    "m.identity_server" = {};
-  };
-  serverConfig."m.server" = "${config.services.matrix-synapse.settings.server_name}:443";
+  clientConfig."m.homeserver".base_url = "https://${fqdn}";
+  serverConfig."m.server" = "${fqdn}:443";
   mkWellKnown = data: ''
     add_header Content-Type application/json;
     add_header Access-Control-Allow-Origin *;
diff --git a/nixos/modules/services/misc/gitea.nix b/nixos/modules/services/misc/gitea.nix
index 14bf6aebb6812..014c5b16097c9 100644
--- a/nixos/modules/services/misc/gitea.nix
+++ b/nixos/modules/services/misc/gitea.nix
@@ -5,7 +5,7 @@ with lib;
 let
   cfg = config.services.gitea;
   opt = options.services.gitea;
-  gitea = cfg.package;
+  exe = lib.getExe cfg.package;
   pg = config.services.postgresql;
   useMysql = cfg.database.type == "mysql";
   usePostgresql = cfg.database.type == "postgres";
@@ -248,7 +248,7 @@ in
 
       staticRootPath = mkOption {
         type = types.either types.str types.path;
-        default = gitea.data;
+        default = cfg.package.data;
         defaultText = literalExpression "package.data";
         example = "/var/lib/gitea/data";
         description = lib.mdDoc "Upper level of template and static files path.";
@@ -481,14 +481,14 @@ in
 
       # If we have a folder or symlink with gitea locales, remove it
       # And symlink the current gitea locales in place
-      "L+ '${cfg.stateDir}/conf/locale' - - - - ${gitea.out}/locale"
+      "L+ '${cfg.stateDir}/conf/locale' - - - - ${cfg.package.out}/locale"
     ];
 
     systemd.services.gitea = {
       description = "gitea";
       after = [ "network.target" ] ++ lib.optional usePostgresql "postgresql.service" ++ lib.optional useMysql "mysql.service";
       wantedBy = [ "multi-user.target" ];
-      path = [ gitea pkgs.git pkgs.gnupg ];
+      path = [ cfg.package pkgs.git pkgs.gnupg ];
 
       # In older versions the secret naming for JWT was kind of confusing.
       # The file jwt_secret hold the value for LFS_JWT_SECRET and JWT_SECRET
@@ -512,7 +512,7 @@ in
             cp -f ${configFile} ${runConfig}
 
             if [ ! -s ${secretKey} ]; then
-                ${gitea}/bin/gitea generate secret SECRET_KEY > ${secretKey}
+                ${exe} generate secret SECRET_KEY > ${secretKey}
             fi
 
             # Migrate LFS_JWT_SECRET filename
@@ -521,15 +521,15 @@ in
             fi
 
             if [ ! -s ${oauth2JwtSecret} ]; then
-                ${gitea}/bin/gitea generate secret JWT_SECRET > ${oauth2JwtSecret}
+                ${exe} generate secret JWT_SECRET > ${oauth2JwtSecret}
             fi
 
             if [ ! -s ${lfsJwtSecret} ]; then
-                ${gitea}/bin/gitea generate secret LFS_JWT_SECRET > ${lfsJwtSecret}
+                ${exe} generate secret LFS_JWT_SECRET > ${lfsJwtSecret}
             fi
 
             if [ ! -s ${internalToken} ]; then
-                ${gitea}/bin/gitea generate secret INTERNAL_TOKEN > ${internalToken}
+                ${exe} generate secret INTERNAL_TOKEN > ${internalToken}
             fi
 
             chmod u+w '${runConfig}'
@@ -548,15 +548,15 @@ in
         ''}
 
         # run migrations/init the database
-        ${gitea}/bin/gitea migrate
+        ${exe} migrate
 
         # update all hooks' binary paths
-        ${gitea}/bin/gitea admin regenerate hooks
+        ${exe} admin regenerate hooks
 
         # update command option in authorized_keys
         if [ -r ${cfg.stateDir}/.ssh/authorized_keys ]
         then
-          ${gitea}/bin/gitea admin regenerate keys
+          ${exe} admin regenerate keys
         fi
       '';
 
@@ -565,7 +565,7 @@ in
         User = cfg.user;
         Group = "gitea";
         WorkingDirectory = cfg.stateDir;
-        ExecStart = "${gitea}/bin/gitea web --pid /run/gitea/gitea.pid";
+        ExecStart = "${exe} web --pid /run/gitea/gitea.pid";
         Restart = "always";
         # Runtime directory and mode
         RuntimeDirectory = "gitea";
@@ -597,7 +597,7 @@ in
         PrivateMounts = true;
         # System Call Filtering
         SystemCallArchitectures = "native";
-        SystemCallFilter = "~@clock @cpu-emulation @debug @keyring @memlock @module @mount @obsolete @raw-io @reboot @setuid @swap";
+        SystemCallFilter = "~@clock @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @reboot @setuid @swap";
       };
 
       environment = {
@@ -635,7 +635,7 @@ in
     systemd.services.gitea-dump = mkIf cfg.dump.enable {
        description = "gitea dump";
        after = [ "gitea.service" ];
-       path = [ gitea ];
+       path = [ cfg.package ];
 
        environment = {
          USER = cfg.user;
@@ -646,7 +646,7 @@ in
        serviceConfig = {
          Type = "oneshot";
          User = cfg.user;
-         ExecStart = "${gitea}/bin/gitea dump --type ${cfg.dump.type}" + optionalString (cfg.dump.file != null) " --file ${cfg.dump.file}";
+         ExecStart = "${exe} dump --type ${cfg.dump.type}" + optionalString (cfg.dump.file != null) " --file ${cfg.dump.file}";
          WorkingDirectory = cfg.dump.backupDir;
        };
     };
@@ -658,5 +658,5 @@ in
       timerConfig.OnCalendar = cfg.dump.interval;
     };
   };
-  meta.maintainers = with lib.maintainers; [ srhb ma27 ];
+  meta.maintainers = with lib.maintainers; [ srhb ma27 thehedgeh0g ];
 }
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index 179359c97a3af..d278b571a6410 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -89,11 +89,6 @@ let
     };
   };
 
-  pagesArgs = [
-    "-pages-domain" gitlabConfig.production.pages.host
-    "-pages-root" "${gitlabConfig.production.shared.path}/pages"
-  ] ++ cfg.pagesExtraArgs;
-
   gitlabConfig = {
     # These are the default settings from config/gitlab.example.yml
     production = flip recursiveUpdate cfg.extraConfig {
@@ -161,6 +156,12 @@ let
       };
       extra = {};
       uploads.storage_path = cfg.statePath;
+      pages = optionalAttrs cfg.pages.enable {
+        enabled = cfg.pages.enable;
+        port = 8090;
+        host = cfg.pages.settings.pages-domain;
+        secret_file = cfg.pages.settings.api-secret-key;
+      };
     };
   };
 
@@ -246,6 +247,7 @@ in {
     (mkRenamedOptionModule [ "services" "gitlab" "backupPath" ] [ "services" "gitlab" "backup" "path" ])
     (mkRemovedOptionModule [ "services" "gitlab" "satelliteDir" ] "")
     (mkRemovedOptionModule [ "services" "gitlab" "logrotate" "extraConfig" ] "Modify services.logrotate.settings.gitlab directly instead")
+    (mkRemovedOptionModule [ "services" "gitlab" "pagesExtraArgs" ] "Use services.gitlab.pages.settings instead")
   ];
 
   options = {
@@ -667,10 +669,127 @@ in {
         };
       };
 
-      pagesExtraArgs = mkOption {
-        type = types.listOf types.str;
-        default = [ "-listen-proxy" "127.0.0.1:8090" ];
-        description = lib.mdDoc "Arguments to pass to the gitlab-pages daemon";
+      pages.enable = mkEnableOption (lib.mdDoc "the GitLab Pages service");
+
+      pages.settings = mkOption {
+        example = literalExpression ''
+          {
+            pages-domain = "example.com";
+            auth-client-id = "generated-id-xxxxxxx";
+            auth-client-secret = { _secret = "/var/keys/auth-client-secret"; };
+            auth-redirect-uri = "https://projects.example.com/auth";
+            auth-secret = { _secret = "/var/keys/auth-secret"; };
+            auth-server = "https://gitlab.example.com";
+          }
+        '';
+
+        description = lib.mdDoc ''
+          Configuration options to set in the GitLab Pages config
+          file.
+
+          Options containing secret data should be set to an attribute
+          set containing the attribute `_secret` - a string pointing
+          to a file containing the value the option should be set
+          to. See the example to get a better picture of this: in the
+          resulting configuration file, the `auth-client-secret` and
+          `auth-secret` keys will be set to the contents of the
+          {file}`/var/keys/auth-client-secret` and
+          {file}`/var/keys/auth-secret` files respectively.
+        '';
+
+        type = types.submodule {
+          freeformType = with types; attrsOf (nullOr (oneOf [ str int bool attrs ]));
+
+          options = {
+            listen-http = mkOption {
+              type = with types; listOf str;
+              apply = x: if x == [] then null else lib.concatStringsSep "," x;
+              default = [];
+              description = lib.mdDoc ''
+                The address(es) to listen on for HTTP requests.
+              '';
+            };
+
+            listen-https = mkOption {
+              type = with types; listOf str;
+              apply = x: if x == [] then null else lib.concatStringsSep "," x;
+              default = [];
+              description = lib.mdDoc ''
+                The address(es) to listen on for HTTPS requests.
+              '';
+            };
+
+            listen-proxy = mkOption {
+              type = with types; listOf str;
+              apply = x: if x == [] then null else lib.concatStringsSep "," x;
+              default = [ "127.0.0.1:8090" ];
+              description = lib.mdDoc ''
+                The address(es) to listen on for proxy requests.
+              '';
+            };
+
+            artifacts-server = mkOption {
+              type = with types; nullOr str;
+              default = "http${optionalString cfg.https "s"}://${cfg.host}/api/v4";
+              defaultText = "http(s)://<services.gitlab.host>/api/v4";
+              example = "https://gitlab.example.com/api/v4";
+              description = lib.mdDoc ''
+                API URL to proxy artifact requests to.
+              '';
+            };
+
+            gitlab-server = mkOption {
+              type = with types; nullOr str;
+              default = "http${optionalString cfg.https "s"}://${cfg.host}";
+              defaultText = "http(s)://<services.gitlab.host>";
+              example = "https://gitlab.example.com";
+              description = lib.mdDoc ''
+                Public GitLab server URL.
+              '';
+            };
+
+            internal-gitlab-server = mkOption {
+              type = with types; nullOr str;
+              default = null;
+              defaultText = "http(s)://<services.gitlab.host>";
+              example = "https://gitlab.example.internal";
+              description = lib.mdDoc ''
+                Internal GitLab server used for API requests, useful
+                if you want to send that traffic over an internal load
+                balancer. By default, the value of
+                `services.gitlab.pages.settings.gitlab-server` is
+                used.
+              '';
+            };
+
+            api-secret-key = mkOption {
+              type = with types; nullOr str;
+              default = "${cfg.statePath}/gitlab_pages_secret";
+              internal = true;
+              description = lib.mdDoc ''
+                File with secret key used to authenticate with the
+                GitLab API.
+              '';
+            };
+
+            pages-domain = mkOption {
+              type = with types; nullOr str;
+              example = "example.com";
+              description = lib.mdDoc ''
+                The domain to serve static pages on.
+              '';
+            };
+
+            pages-root = mkOption {
+              type = types.str;
+              default = "${gitlabConfig.production.shared.path}/pages";
+              defaultText = literalExpression ''config.${opt.extraConfig}.production.shared.path + "/pages"'';
+              description = lib.mdDoc ''
+                The directory where pages are stored.
+              '';
+            };
+          };
+        };
       };
 
       secrets.secretFile = mkOption {
@@ -1210,6 +1329,9 @@ in {
             umask u=rwx,g=,o=
 
             openssl rand -hex 32 > ${cfg.statePath}/gitlab_shell_secret
+            ${optionalString cfg.pages.enable ''
+                openssl rand -base64 32 > ${cfg.pages.settings.api-secret-key}
+            ''}
 
             rm -f '${cfg.statePath}/config/database.yml'
 
@@ -1359,28 +1481,66 @@ in {
       };
     };
 
-    systemd.services.gitlab-pages = mkIf (gitlabConfig.production.pages.enabled or false) {
-      description = "GitLab static pages daemon";
-      after = [ "network.target" "gitlab-config.service" ];
-      bindsTo = [ "gitlab-config.service" ];
-      wantedBy = [ "gitlab.target" ];
-      partOf = [ "gitlab.target" ];
-
-      path = [ pkgs.unzip ];
-
-      serviceConfig = {
-        Type = "simple";
-        TimeoutSec = "infinity";
-        Restart = "on-failure";
-
-        User = cfg.user;
-        Group = cfg.group;
-
-        ExecStart = "${cfg.packages.pages}/bin/gitlab-pages ${escapeShellArgs pagesArgs}";
-        WorkingDirectory = gitlabEnv.HOME;
-      };
+    services.gitlab.pages.settings = {
+      api-secret-key = "${cfg.statePath}/gitlab_pages_secret";
     };
 
+    systemd.services.gitlab-pages =
+      let
+        filteredConfig = filterAttrs (_: v: v != null) cfg.pages.settings;
+        isSecret = v: isAttrs v && v ? _secret && isString v._secret;
+        mkPagesKeyValue = lib.generators.toKeyValue {
+          mkKeyValue = lib.flip lib.generators.mkKeyValueDefault "=" rec {
+            mkValueString = v:
+              if isInt           v then toString v
+              else if isString   v then v
+              else if true  ==   v then "true"
+              else if false ==   v then "false"
+              else if isSecret   v then builtins.hashString "sha256" v._secret
+              else throw "unsupported type ${builtins.typeOf v}: ${(lib.generators.toPretty {}) v}";
+          };
+        };
+        secretPaths = lib.catAttrs "_secret" (lib.collect isSecret filteredConfig);
+        mkSecretReplacement = file: ''
+          replace-secret ${lib.escapeShellArgs [ (builtins.hashString "sha256" file) file "/run/gitlab-pages/gitlab-pages.conf" ]}
+        '';
+        secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths;
+        configFile = pkgs.writeText "gitlab-pages.conf" (mkPagesKeyValue filteredConfig);
+      in
+        mkIf cfg.pages.enable {
+          description = "GitLab static pages daemon";
+          after = [ "network.target" "gitlab-config.service" "gitlab.service" ];
+          bindsTo = [ "gitlab-config.service" "gitlab.service" ];
+          wantedBy = [ "gitlab.target" ];
+          partOf = [ "gitlab.target" ];
+
+          path = with pkgs; [
+            unzip
+            replace-secret
+          ];
+
+          serviceConfig = {
+            Type = "simple";
+            TimeoutSec = "infinity";
+            Restart = "on-failure";
+
+            User = cfg.user;
+            Group = cfg.group;
+
+            ExecStartPre = pkgs.writeShellScript "gitlab-pages-pre-start" ''
+              set -o errexit -o pipefail -o nounset
+              shopt -s dotglob nullglob inherit_errexit
+
+              install -m u=rw ${configFile} /run/gitlab-pages/gitlab-pages.conf
+              ${secretReplacements}
+            '';
+            ExecStart = "${cfg.packages.pages}/bin/gitlab-pages -config=/run/gitlab-pages/gitlab-pages.conf";
+            WorkingDirectory = gitlabEnv.HOME;
+            RuntimeDirectory = "gitlab-pages";
+            RuntimeDirectoryMode = "0700";
+          };
+        };
+
     systemd.services.gitlab-workhorse = {
       after = [ "network.target" ];
       wantedBy = [ "gitlab.target" ];
diff --git a/nixos/modules/services/misc/jellyseerr.nix b/nixos/modules/services/misc/jellyseerr.nix
new file mode 100644
index 0000000000000..31e0c5beb6733
--- /dev/null
+++ b/nixos/modules/services/misc/jellyseerr.nix
@@ -0,0 +1,62 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+let
+  cfg = config.services.jellyseerr;
+in
+{
+  meta.maintainers = [ maintainers.camillemndn ];
+
+  options.services.jellyseerr = {
+    enable = mkEnableOption (mdDoc ''Jellyseerr, a requests manager for Jellyfin'');
+
+    openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = mdDoc ''Open port in the firewall for the Jellyseerr web interface.'';
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 5055;
+      description = mdDoc ''The port which the Jellyseerr web UI should listen to.'';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.jellyseerr = {
+      description = "Jellyseerr, a requests manager for Jellyfin";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      environment.PORT = toString cfg.port;
+      serviceConfig = {
+        Type = "exec";
+        StateDirectory = "jellyseerr";
+        WorkingDirectory = "${pkgs.jellyseerr}/libexec/jellyseerr/deps/jellyseerr";
+        DynamicUser = true;
+        ExecStart = "${pkgs.jellyseerr}/bin/jellyseerr";
+        BindPaths = [ "/var/lib/jellyseerr/:${pkgs.jellyseerr}/libexec/jellyseerr/deps/jellyseerr/config/" ];
+        Restart = "on-failure";
+        ProtectHome = true;
+        ProtectSystem = "strict";
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectHostname = true;
+        ProtectClock = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectKernelLogs = true;
+        ProtectControlGroups = true;
+        NoNewPrivileges = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        RemoveIPC = true;
+        PrivateMounts = true;
+      };
+    };
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.port ];
+    };
+  };
+}
diff --git a/nixos/modules/services/misc/klipper.nix b/nixos/modules/services/misc/klipper.nix
index a2158e9461bce..9f8539980aaab 100644
--- a/nixos/modules/services/misc/klipper.nix
+++ b/nixos/modules/services/misc/klipper.nix
@@ -135,7 +135,7 @@ in
       }
       {
         assertion = (cfg.configFile != null) != (cfg.settings != null);
-        message = "You need to either specify services.klipper.settings or services.klipper.defaultConfig.";
+        message = "You need to either specify services.klipper.settings or services.klipper.configFile.";
       }
     ];
 
diff --git a/nixos/modules/services/misc/mbpfan.nix b/nixos/modules/services/misc/mbpfan.nix
index d467aa8797678..1a6b54854d1cd 100644
--- a/nixos/modules/services/misc/mbpfan.nix
+++ b/nixos/modules/services/misc/mbpfan.nix
@@ -1,5 +1,4 @@
 { config, lib, pkgs, ... }:
-
 with lib;
 
 let
@@ -16,17 +15,19 @@ in {
       type = types.package;
       default = pkgs.mbpfan;
       defaultText = literalExpression "pkgs.mbpfan";
-      description = lib.mdDoc ''
-        The package used for the mbpfan daemon.
-      '';
+      description = lib.mdDoc "The package used for the mbpfan daemon.";
     };
 
     verbose = mkOption {
       type = types.bool;
       default = false;
-      description = lib.mdDoc ''
-        If true, sets the log level to verbose.
-      '';
+      description = lib.mdDoc "If true, sets the log level to verbose.";
+    };
+
+    aggressive = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc "If true, favors higher default fan speeds.";
     };
 
     settings = mkOption {
@@ -35,24 +36,14 @@ in {
       type = types.submodule {
         freeformType = settingsFormat.type;
 
-        options.general.min_fan1_speed = mkOption {
-          type = types.nullOr types.int;
-          default = 2000;
-          description = lib.mdDoc ''
-            You can check minimum and maximum fan limits with
-            `cat /sys/devices/platform/applesmc.768/fan*_min` and
-            `cat /sys/devices/platform/applesmc.768/fan*_max` respectively.
-            Setting to null implies using default value from applesmc.
-          '';
-        };
         options.general.low_temp = mkOption {
           type = types.int;
-          default = 55;
+          default = 63;
           description = lib.mdDoc "If temperature is below this, fans will run at minimum speed.";
         };
         options.general.high_temp = mkOption {
           type = types.int;
-          default = 58;
+          default = 66;
           description = lib.mdDoc "If temperature is above this, fan speed will gradually increase.";
         };
         options.general.max_temp = mkOption {
@@ -79,10 +70,16 @@ in {
   ];
 
   config = mkIf cfg.enable {
-    boot.kernelModules = [ "coretemp" "applesmc" ];
+    services.mbpfan.settings = mkIf cfg.aggressive {
+      general.min_fan1_speed = mkDefault 2000;
+      general.low_temp = mkDefault 55;
+      general.high_temp = mkDefault 58;
+      general.max_temp = mkDefault 70;
+    };
 
-    environment.etc."mbpfan.conf".source = settingsFile;
+    boot.kernelModules = [ "coretemp" "applesmc" ];
     environment.systemPackages = [ cfg.package ];
+    environment.etc."mbpfan.conf".source = settingsFile;
 
     systemd.services.mbpfan = {
       description = "A fan manager daemon for MacBook Pro";
diff --git a/nixos/modules/services/misc/moonraker.nix b/nixos/modules/services/misc/moonraker.nix
index 62064b5d90fb3..53638ded29634 100644
--- a/nixos/modules/services/misc/moonraker.nix
+++ b/nixos/modules/services/misc/moonraker.nix
@@ -11,6 +11,8 @@ let
       else lib.concatMapStrings (s: "\n  ${generators.mkValueStringDefault {} s}") l;
     mkKeyValue = generators.mkKeyValueDefault {} ":";
   };
+
+  unifiedConfigDir = cfg.stateDir + "/config";
 in {
   options = {
     services.moonraker = {
@@ -30,11 +32,10 @@ in {
       };
 
       configDir = mkOption {
-        type = types.path;
-        default = cfg.stateDir + "/config";
-        defaultText = literalExpression ''config.${opt.stateDir} + "/config"'';
+        type = types.nullOr types.path;
+        default = null;
         description = lib.mdDoc ''
-          The directory containing client-writable configuration files.
+          Deprecated directory containing client-writable configuration files.
 
           Clients will be able to edit files in this directory via the API. This directory must be writable.
         '';
@@ -96,8 +97,18 @@ in {
   };
 
   config = mkIf cfg.enable {
-    warnings = optional (cfg.settings ? update_manager)
-      ''Enabling update_manager is not supported on NixOS and will lead to non-removable warnings in some clients.'';
+    warnings = []
+      ++ optional (cfg.settings ? update_manager)
+        ''Enabling update_manager is not supported on NixOS and will lead to non-removable warnings in some clients.''
+      ++ optional (cfg.configDir != null)
+        ''
+          services.moonraker.configDir has been deprecated upstream and will be removed.
+
+          Action: ${
+            if cfg.configDir == unifiedConfigDir then "Simply remove services.moonraker.configDir from your config."
+            else "Move files from `${cfg.configDir}` to `${unifiedConfigDir}` then remove services.moonraker.configDir from your config."
+          }
+        '';
 
     assertions = [
       {
@@ -124,20 +135,20 @@ in {
           port = cfg.port;
           klippy_uds_address = cfg.klipperSocket;
         };
+        machine = {
+          validate_service = false;
+        };
+      } // (lib.optionalAttrs (cfg.configDir != null) {
         file_manager = {
           config_path = cfg.configDir;
         };
-        database = {
-          database_path = "${cfg.stateDir}/database";
-        };
-      };
+      });
       fullConfig = recursiveUpdate cfg.settings forcedConfig;
     in format.generate "moonraker.cfg" fullConfig;
 
     systemd.tmpfiles.rules = [
       "d '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -"
-      "d '${cfg.configDir}' - ${cfg.user} ${cfg.group} - -"
-    ];
+    ] ++ lib.optional (cfg.configDir != null) "d '${cfg.configDir}' - ${cfg.user} ${cfg.group} - -";
 
     systemd.services.moonraker = {
       description = "Moonraker, an API web server for Klipper";
@@ -147,9 +158,16 @@ in {
 
       # Moonraker really wants its own config to be writable...
       script = ''
-        cp /etc/moonraker.cfg ${cfg.configDir}/moonraker-temp.cfg
-        chmod u+w ${cfg.configDir}/moonraker-temp.cfg
-        exec ${pkg}/bin/moonraker -c ${cfg.configDir}/moonraker-temp.cfg
+        config_path=${
+          # Deprecated separate config dir
+          if cfg.configDir != null then "${cfg.configDir}/moonraker-temp.cfg"
+          # Config in unified data path
+          else "${unifiedConfigDir}/moonraker-temp.cfg"
+        }
+        mkdir -p $(dirname "$config_path")
+        cp /etc/moonraker.cfg "$config_path"
+        chmod u+w "$config_path"
+        exec ${pkg}/bin/moonraker -d ${cfg.stateDir} -c "$config_path"
       '';
 
       # Needs `ip` command
@@ -184,5 +202,6 @@ in {
   meta.maintainers = with maintainers; [
     cab404
     vtuan10
+    zhaofengli
   ];
 }
diff --git a/nixos/modules/services/misc/paperless.nix b/nixos/modules/services/misc/paperless.nix
index 667f16d98f828..4199e7713304e 100644
--- a/nixos/modules/services/misc/paperless.nix
+++ b/nixos/modules/services/misc/paperless.nix
@@ -6,6 +6,7 @@ let
   pkg = cfg.package;
 
   defaultUser = "paperless";
+  nltkDir = "/var/cache/paperless/nltk";
 
   # Don't start a redis instance if the user sets a custom redis connection
   enableRedis = !hasAttr "PAPERLESS_REDIS" cfg.extraConfig;
@@ -15,6 +16,7 @@ let
     PAPERLESS_DATA_DIR = cfg.dataDir;
     PAPERLESS_MEDIA_ROOT = cfg.mediaDir;
     PAPERLESS_CONSUMPTION_DIR = cfg.consumptionDir;
+    PAPERLESS_NLTK_DIR = nltkDir;
     GUNICORN_CMD_ARGS = "--bind=${cfg.address}:${toString cfg.port}";
   } // optionalAttrs (config.time.timeZone != null) {
     PAPERLESS_TIME_ZONE = config.time.timeZone;
@@ -24,12 +26,14 @@ let
     lib.mapAttrs (_: toString) cfg.extraConfig
   );
 
-  manage = let
-    setupEnv = lib.concatStringsSep "\n" (mapAttrsToList (name: val: "export ${name}=\"${val}\"") env);
-  in pkgs.writeShellScript "manage" ''
-    ${setupEnv}
-    exec ${pkg}/bin/paperless-ngx "$@"
-  '';
+  manage =
+    let
+      setupEnv = lib.concatStringsSep "\n" (mapAttrsToList (name: val: "export ${name}=\"${val}\"") env);
+    in
+    pkgs.writeShellScript "manage" ''
+      ${setupEnv}
+      exec ${pkg}/bin/paperless-ngx "$@"
+    '';
 
   # Secure the services
   defaultServiceConfig = {
@@ -47,6 +51,7 @@ let
       cfg.dataDir
       cfg.mediaDir
     ];
+    CacheDirectory = "paperless";
     CapabilityBoundingSet = "";
     # ProtectClock adds DeviceAllow=char-rtc r
     DeviceAllow = "";
@@ -170,7 +175,7 @@ in
 
     extraConfig = mkOption {
       type = types.attrs;
-      default = {};
+      default = { };
       description = lib.mdDoc ''
         Extra paperless config options.
 
@@ -291,6 +296,33 @@ in
       };
     };
 
+    # Download NLTK corpus data
+    systemd.services.paperless-download-nltk-data = {
+      wantedBy = [ "paperless-scheduler.service" ];
+      before = [ "paperless-scheduler.service" ];
+      after = [ "network-online.target" ];
+      serviceConfig = defaultServiceConfig // {
+        User = cfg.user;
+        Type = "oneshot";
+        # Enable internet access
+        PrivateNetwork = false;
+        # Restrict write access
+        BindPaths = [];
+        BindReadOnlyPaths = [
+          "/nix/store"
+          "-/etc/resolv.conf"
+          "-/etc/nsswitch.conf"
+          "-/etc/ssl/certs"
+          "-/etc/static/ssl/certs"
+          "-/etc/hosts"
+          "-/etc/localtime"
+        ];
+        ExecStart = let pythonWithNltk = pkg.python.withPackages (ps: [ ps.nltk ]); in ''
+          ${pythonWithNltk}/bin/python -m nltk.downloader -d '${nltkDir}' punkt snowball_data stopwords
+        '';
+      };
+    };
+
     systemd.services.paperless-consumer = {
       description = "Paperless document consumer";
       # Bind to `paperless-scheduler` so that the consumer never runs
diff --git a/nixos/modules/services/misc/portunus.nix b/nixos/modules/services/misc/portunus.nix
index f60cbe3477132..5504fb942968f 100644
--- a/nixos/modules/services/misc/portunus.nix
+++ b/nixos/modules/services/misc/portunus.nix
@@ -238,7 +238,7 @@ in
           PORTUNUS_SERVER_BINARY = "${cfg.package}/bin/portunus-server";
           PORTUNUS_SERVER_GROUP = cfg.group;
           PORTUNUS_SERVER_USER = cfg.user;
-          PORTUNUS_SERVER_HTTP_LISTEN = "[::]:${toString cfg.port}";
+          PORTUNUS_SERVER_HTTP_LISTEN = "127.0.0.1:${toString cfg.port}";
           PORTUNUS_SERVER_STATE_DIR = cfg.stateDir;
           PORTUNUS_SLAPD_BINARY = "${cfg.ldap.package}/libexec/slapd";
           PORTUNUS_SLAPD_GROUP = cfg.ldap.group;
diff --git a/nixos/modules/services/misc/readarr.nix b/nixos/modules/services/misc/readarr.nix
new file mode 100644
index 0000000000000..dd4fef6e598d4
--- /dev/null
+++ b/nixos/modules/services/misc/readarr.nix
@@ -0,0 +1,88 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.readarr;
+in
+{
+  options = {
+    services.readarr = {
+      enable = mkEnableOption (lib.mdDoc "Readarr");
+
+      dataDir = mkOption {
+        type = types.str;
+        default = "/var/lib/readarr/";
+        description = lib.mdDoc "The directory where Readarr stores its data files.";
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.readarr;
+        defaultText = literalExpression "pkgs.readarr";
+        description = lib.mdDoc "The Readarr package to use";
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Open ports in the firewall for Readarr
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "readarr";
+        description = lib.mdDoc ''
+          User account under which Readarr runs.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "readarr";
+        description = lib.mdDoc ''
+          Group under which Readarr runs.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' 0700 ${cfg.user} ${cfg.group} - -"
+    ];
+
+    systemd.services.readarr = {
+      description = "Readarr";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        Type = "simple";
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart = "${cfg.package}/bin/Readarr -nobrowser -data='${cfg.dataDir}'";
+        Restart = "on-failure";
+      };
+    };
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ 8787 ];
+    };
+
+    users.users = mkIf (cfg.user == "readarr") {
+      readarr = {
+        description = "Readarr service";
+        home = cfg.dataDir;
+        group = cfg.group;
+        isSystemUser = true;
+      };
+    };
+
+    users.groups = mkIf (cfg.group == "readarr") {
+      readarr = { };
+    };
+  };
+}
diff --git a/nixos/modules/services/misc/sssd.nix b/nixos/modules/services/misc/sssd.nix
index edd5750a4a478..7c7a3b464a836 100644
--- a/nixos/modules/services/misc/sssd.nix
+++ b/nixos/modules/services/misc/sssd.nix
@@ -77,6 +77,10 @@ in {
   };
   config = mkMerge [
     (mkIf cfg.enable {
+      # For `sssctl` to work.
+      environment.etc."sssd/sssd.conf".source = settingsFile;
+      environment.etc."sssd/conf.d".source = "${dataDir}/conf.d";
+
       systemd.services.sssd = {
         description = "System Security Services Daemon";
         wantedBy    = [ "multi-user.target" ];
@@ -101,6 +105,7 @@ in {
           EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
         };
         preStart = ''
+          mkdir -p "${dataDir}/conf.d"
           [ -f ${settingsFile} ] && rm -f ${settingsFile}
           old_umask=$(umask)
           umask 0177
diff --git a/nixos/modules/services/misc/zoneminder.nix b/nixos/modules/services/misc/zoneminder.nix
index 109415a20ee63..11722979851c2 100644
--- a/nixos/modules/services/misc/zoneminder.nix
+++ b/nixos/modules/services/misc/zoneminder.nix
@@ -283,7 +283,8 @@ in {
       phpfpm = lib.mkIf useNginx {
         pools.zoneminder = {
           inherit user group;
-          phpPackage = pkgs.php.withExtensions ({ enabled, all }: enabled ++ [ all.apcu ]);
+          phpPackage = pkgs.php.withExtensions (
+            { enabled, all }: enabled ++ [ all.apcu all.sysvsem ]);
           phpOptions = ''
             date.timezone = "${config.time.timeZone}"
           '';
@@ -326,6 +327,15 @@ in {
           fi
 
           ${zoneminder}/bin/zmupdate.pl -nointeractive
+          ${zoneminder}/bin/zmupdate.pl --nointeractive -f
+
+          # Update ZM's Nix store path in the configuration table. Do nothing if the config doesn't
+          # contain ZM's Nix store path.
+          ${config.services.mysql.package}/bin/mysql -u zoneminder zm << EOF
+            UPDATE Config
+              SET Value = REGEXP_REPLACE(Value, "^/nix/store/[^-/]+-zoneminder-[^/]+", "${pkgs.zoneminder}")
+              WHERE Name = "ZM_FONT_FILE_LOCATION";
+          EOF
         '';
         serviceConfig = {
           User = user;
diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix
index ba3f89e24dd45..5a8c65b9dc3fb 100644
--- a/nixos/modules/services/monitoring/grafana.nix
+++ b/nixos/modules/services/monitoring/grafana.nix
@@ -1300,7 +1300,7 @@ in {
         SystemCallFilter = [
           "@system-service"
           "~@privileged"
-        ] ++ lib.optional (cfg.settings.server.protocol == "socket") [ "@chown" ];
+        ] ++ lib.optionals (cfg.settings.server.protocol == "socket") [ "@chown" ];
         UMask = "0027";
       };
       preStart = ''
diff --git a/nixos/modules/services/monitoring/mimir.nix b/nixos/modules/services/monitoring/mimir.nix
index 568066990f23e..edca9b7be4ff0 100644
--- a/nixos/modules/services/monitoring/mimir.nix
+++ b/nixos/modules/services/monitoring/mimir.nix
@@ -25,6 +25,13 @@ in {
         Specify a configuration file that Mimir should use.
       '';
     };
+
+    package = mkOption {
+      default = pkgs.mimir;
+      defaultText = lib.literalExpression "pkgs.mimir";
+      type = types.package;
+      description = lib.mdDoc ''Mimir package to use.'';
+    };
   };
 
   config = mkIf cfg.enable {
@@ -53,7 +60,7 @@ in {
                else cfg.configFile;
       in
       {
-        ExecStart = "${pkgs.mimir}/bin/mimir --config.file=${conf}";
+        ExecStart = "${cfg.package}/bin/mimir --config.file=${conf}";
         DynamicUser = true;
         Restart = "always";
         ProtectSystem = "full";
diff --git a/nixos/modules/services/monitoring/parsedmarc.nix b/nixos/modules/services/monitoring/parsedmarc.nix
index fdaefbe17f2d3..44fc359b6a7de 100644
--- a/nixos/modules/services/monitoring/parsedmarc.nix
+++ b/nixos/modules/services/monitoring/parsedmarc.nix
@@ -409,7 +409,7 @@ in
 
       provision = {
         enable = cfg.provision.grafana.datasource || cfg.provision.grafana.dashboard;
-        datasources =
+        datasources.settings.datasources =
           let
             esVersion = lib.getVersion config.services.elasticsearch.package;
           in
@@ -435,7 +435,7 @@ in
                 };
               }
             ];
-        dashboards = lib.mkIf cfg.provision.grafana.dashboard [{
+        dashboards.settings.providers = lib.mkIf cfg.provision.grafana.dashboard [{
           name = "parsedmarc";
           options.path = "${pkgs.python3Packages.parsedmarc.dashboard}";
         }];
diff --git a/nixos/modules/services/monitoring/prometheus/alertmanager-irc-relay.nix b/nixos/modules/services/monitoring/prometheus/alertmanager-irc-relay.nix
new file mode 100644
index 0000000000000..b81d5f6db5e08
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/alertmanager-irc-relay.nix
@@ -0,0 +1,107 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.alertmanagerIrcRelay;
+
+  configFormat = pkgs.formats.yaml { };
+  configFile = configFormat.generate "alertmanager-irc-relay.yml" cfg.settings;
+in
+{
+  options.services.prometheus.alertmanagerIrcRelay = {
+    enable = mkEnableOption (mdDoc "Alertmanager IRC Relay");
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.alertmanager-irc-relay;
+      defaultText = literalExpression "pkgs.alertmanager-irc-relay";
+      description = mdDoc "Alertmanager IRC Relay package to use.";
+    };
+
+    extraFlags = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = mdDoc "Extra command line options to pass to alertmanager-irc-relay.";
+    };
+
+    settings = mkOption {
+      type = configFormat.type;
+      example = literalExpression ''
+        {
+          http_host = "localhost";
+          http_port = 8000;
+
+          irc_host = "irc.example.com";
+          irc_port = 7000;
+          irc_nickname = "myalertbot";
+
+          irc_channels = [
+            { name = "#mychannel"; }
+          ];
+        }
+      '';
+      description = mdDoc ''
+        Configuration for Alertmanager IRC Relay as a Nix attribute set.
+        For a reference, check out the
+        [example configuration](https://github.com/google/alertmanager-irc-relay#configuring-and-running-the-bot)
+        and the
+        [source code](https://github.com/google/alertmanager-irc-relay/blob/master/config.go).
+
+        Note: The webhook's URL MUST point to the IRC channel where the message
+        should be posted. For `#mychannel` from the example, this would be
+        `http://localhost:8080/mychannel`.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.alertmanager-irc-relay = {
+      description = "Alertmanager IRC Relay";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" ];
+
+      serviceConfig = {
+        ExecStart = ''
+          ${cfg.package}/bin/alertmanager-irc-relay \
+          -config ${configFile} \
+          ${escapeShellArgs cfg.extraFlags}
+        '';
+
+        DynamicUser = true;
+        NoNewPrivileges = true;
+
+        ProtectProc = "invisible";
+        ProtectSystem = "strict";
+        ProtectHome = "tmpfs";
+
+        PrivateTmp = true;
+        PrivateDevices = true;
+        PrivateIPC = true;
+
+        ProtectHostname = true;
+        ProtectClock = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectKernelLogs = true;
+        ProtectControlGroups = true;
+
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+
+        SystemCallFilter = [
+          "@system-service"
+          "~@cpu-emulation"
+          "~@privileged"
+          "~@reboot"
+          "~@setuid"
+          "~@swap"
+        ];
+      };
+    };
+  };
+
+  meta.maintainers = [ maintainers.oxzi ];
+}
diff --git a/nixos/modules/services/monitoring/prometheus/default.nix b/nixos/modules/services/monitoring/prometheus/default.nix
index f516b75ab10fb..fb3bab7963ea8 100644
--- a/nixos/modules/services/monitoring/prometheus/default.nix
+++ b/nixos/modules/services/monitoring/prometheus/default.nix
@@ -31,7 +31,7 @@ let
     if checkConfigEnabled then
       pkgs.runCommandLocal
         "${name}-${replaceStrings [" "] [""] what}-checked"
-        { buildInputs = [ cfg.package ]; } ''
+        { buildInputs = [ cfg.package.cli ]; } ''
         ln -s ${file} $out
         promtool ${what} $out
       '' else file;
@@ -1408,7 +1408,7 @@ let
       '';
 
       action =
-        mkDefOpt (types.enum [ "replace" "keep" "drop" "hashmod" "labelmap" "labeldrop" "labelkeep" ]) "replace" ''
+        mkDefOpt (types.enum [ "replace" "lowercase" "uppercase" "keep" "drop" "hashmod" "labelmap" "labeldrop" "labelkeep" ]) "replace" ''
           Action to perform based on regex matching.
         '';
     };
diff --git a/nixos/modules/services/networking/avahi-daemon.nix b/nixos/modules/services/networking/avahi-daemon.nix
index 103f73fdaa685..3a7519c7230b3 100644
--- a/nixos/modules/services/networking/avahi-daemon.nix
+++ b/nixos/modules/services/networking/avahi-daemon.nix
@@ -5,7 +5,7 @@ with lib;
 let
   cfg = config.services.avahi;
 
-  yesNo = yes : if yes then "yes" else "no";
+  yesNo = yes: if yes then "yes" else "no";
 
   avahiDaemonConf = with cfg; pkgs.writeText "avahi-daemon.conf" ''
     [server]
@@ -17,7 +17,8 @@ let
     browse-domains=${concatStringsSep ", " browseDomains}
     use-ipv4=${yesNo ipv4}
     use-ipv6=${yesNo ipv6}
-    ${optionalString (interfaces!=null) "allow-interfaces=${concatStringsSep "," interfaces}"}
+    ${optionalString (allowInterfaces!=null) "allow-interfaces=${concatStringsSep "," allowInterfaces}"}
+    ${optionalString (denyInterfaces!=null) "deny-interfaces=${concatStringsSep "," denyInterfaces}"}
     ${optionalString (domainName!=null) "domain-name=${domainName}"}
     allow-point-to-point=${yesNo allowPointToPoint}
     ${optionalString (cacheEntriesMax!=null) "cache-entries-max=${toString cacheEntriesMax}"}
@@ -39,6 +40,10 @@ let
   '';
 in
 {
+  imports = [
+    (lib.mkRenamedOptionModule [ "services" "avahi" "interfaces" ] [ "services" "avahi" "allowInterfaces" ])
+  ];
+
   options.services.avahi = {
     enable = mkOption {
       type = types.bool;
@@ -91,7 +96,7 @@ in
       description = lib.mdDoc "Whether to use IPv6.";
     };
 
-    interfaces = mkOption {
+    allowInterfaces = mkOption {
       type = types.nullOr (types.listOf types.str);
       default = null;
       description = lib.mdDoc ''
@@ -101,6 +106,17 @@ in
       '';
     };
 
+    denyInterfaces = mkOption {
+      type = types.nullOr (types.listOf types.str);
+      default = null;
+      description = lib.mdDoc ''
+        List of network interfaces that should be ignored by the
+        {command}`avahi-daemon`. Other unspecified interfaces will be used,
+        unless {option}`allowInterfaces` is set. This option takes precedence
+        over {option}`allowInterfaces`.
+      '';
+    };
+
     openFirewall = mkOption {
       type = types.bool;
       default = true;
@@ -134,7 +150,7 @@ in
 
     extraServiceFiles = mkOption {
       type = with types; attrsOf (either str path);
-      default = {};
+      default = { };
       example = literalExpression ''
         {
           ssh = "''${pkgs.avahi}/etc/avahi/services/ssh.service";
@@ -236,7 +252,7 @@ in
       isSystemUser = true;
     };
 
-    users.groups.avahi = {};
+    users.groups.avahi = { };
 
     system.nssModules = optional cfg.nssmdns pkgs.nssmdns;
     system.nssDatabases.hosts = optionals cfg.nssmdns (mkMerge [
@@ -246,10 +262,12 @@ in
 
     environment.systemPackages = [ pkgs.avahi ];
 
-    environment.etc = (mapAttrs' (n: v: nameValuePair
-      "avahi/services/${n}.service"
-      { ${if types.path.check v then "source" else "text"} = v; }
-    ) cfg.extraServiceFiles);
+    environment.etc = (mapAttrs'
+      (n: v: nameValuePair
+        "avahi/services/${n}.service"
+        { ${if types.path.check v then "source" else "text"} = v; }
+      )
+      cfg.extraServiceFiles);
 
     systemd.sockets.avahi-daemon = {
       description = "Avahi mDNS/DNS-SD Stack Activation Socket";
diff --git a/nixos/modules/services/networking/cgit.nix b/nixos/modules/services/networking/cgit.nix
new file mode 100644
index 0000000000000..672b0b030eeee
--- /dev/null
+++ b/nixos/modules/services/networking/cgit.nix
@@ -0,0 +1,203 @@
+{ config, lib, pkgs, ...}:
+
+with lib;
+
+let
+  cfgs = config.services.cgit;
+
+  settingType = with types; oneOf [ bool int str ];
+
+  genAttrs' = names: f: listToAttrs (map f names);
+
+  regexEscape =
+    let
+      # taken from https://github.com/python/cpython/blob/05cb728d68a278d11466f9a6c8258d914135c96c/Lib/re.py#L251-L266
+      special = [
+        "(" ")" "[" "]" "{" "}" "?" "*" "+" "-" "|" "^" "$" "\\" "." "&" "~"
+        "#" " " "\t" "\n" "\r" "\v" "\f"
+      ];
+    in
+      replaceStrings special (map (c: "\\${c}") special);
+
+  stripLocation = cfg: removeSuffix "/" cfg.nginx.location;
+
+  regexLocation = cfg: regexEscape (stripLocation cfg);
+
+  mkFastcgiPass = cfg: ''
+    ${if cfg.nginx.location == "/" then ''
+      fastcgi_param PATH_INFO $uri;
+    '' else ''
+      fastcgi_split_path_info ^(${regexLocation cfg})(/.+)$;
+      fastcgi_param PATH_INFO $fastcgi_path_info;
+    ''
+    }fastcgi_pass unix:${config.services.fcgiwrap.socketAddress};
+  '';
+
+  cgitrcLine = name: value: "${name}=${
+    if value == true then
+      "1"
+    else if value == false then
+      "0"
+    else
+      toString value
+  }";
+
+  mkCgitrc = cfg: pkgs.writeText "cgitrc" ''
+    # global settings
+    ${concatStringsSep "\n" (
+        mapAttrsToList
+          cgitrcLine
+          ({ virtual-root = cfg.nginx.location; } // cfg.settings)
+      )
+    }
+    ${optionalString (cfg.scanPath != null) (cgitrcLine "scan-path" cfg.scanPath)}
+
+    # repository settings
+    ${concatStrings (
+        mapAttrsToList
+          (url: settings: ''
+            ${cgitrcLine "repo.url" url}
+            ${concatStringsSep "\n" (
+                mapAttrsToList (name: cgitrcLine "repo.${name}") settings
+              )
+            }
+          '')
+          cfg.repos
+      )
+    }
+
+    # extra config
+    ${cfg.extraConfig}
+  '';
+
+  mkCgitReposDir = cfg:
+    if cfg.scanPath != null then
+      cfg.scanPath
+    else
+      pkgs.runCommand "cgit-repos" {
+        preferLocalBuild = true;
+        allowSubstitutes = false;
+      } ''
+        mkdir -p "$out"
+        ${
+          concatStrings (
+            mapAttrsToList
+              (name: value: ''
+                ln -s ${escapeShellArg value.path} "$out"/${escapeShellArg name}
+              '')
+              cfg.repos
+          )
+        }
+      '';
+
+in
+{
+  options = {
+    services.cgit = mkOption {
+      description = mdDoc "Configure cgit instances.";
+      default = {};
+      type = types.attrsOf (types.submodule ({ config, ... }: {
+        options = {
+          enable = mkEnableOption (mdDoc "cgit");
+
+          package = mkPackageOptionMD pkgs "cgit" {};
+
+          nginx.virtualHost = mkOption {
+            description = mdDoc "VirtualHost to serve cgit on, defaults to the attribute name.";
+            type = types.str;
+            default = config._module.args.name;
+            example = "git.example.com";
+          };
+
+          nginx.location = mkOption {
+            description = mdDoc "Location to serve cgit under.";
+            type = types.str;
+            default = "/";
+            example = "/git/";
+          };
+
+          repos = mkOption {
+            description = mdDoc "cgit repository settings, see cgitrc(5)";
+            type = with types; attrsOf (attrsOf settingType);
+            default = {};
+            example = {
+              blah = {
+                path = "/var/lib/git/example";
+                desc = "An example repository";
+              };
+            };
+          };
+
+          scanPath = mkOption {
+            description = mdDoc "A path which will be scanned for repositories.";
+            type = types.nullOr types.path;
+            default = null;
+            example = "/var/lib/git";
+          };
+
+          settings = mkOption {
+            description = mdDoc "cgit configuration, see cgitrc(5)";
+            type = types.attrsOf settingType;
+            default = {};
+            example = literalExpression ''
+              {
+                enable-follow-links = true;
+                source-filter = "''${pkgs.cgit}/lib/cgit/filters/syntax-highlighting.py";
+              }
+            '';
+          };
+
+          extraConfig = mkOption {
+            description = mdDoc "These lines go to the end of cgitrc verbatim.";
+            type = types.lines;
+            default = "";
+          };
+        };
+      }));
+    };
+  };
+
+  config = mkIf (any (cfg: cfg.enable) (attrValues cfgs)) {
+    assertions = mapAttrsToList (vhost: cfg: {
+      assertion = !cfg.enable || (cfg.scanPath == null) != (cfg.repos == {});
+      message = "Exactly one of services.cgit.${vhost}.scanPath or services.cgit.${vhost}.repos must be set.";
+    }) cfgs;
+
+    services.fcgiwrap.enable = true;
+
+    services.nginx.enable = true;
+
+    services.nginx.virtualHosts = mkMerge (mapAttrsToList (_: cfg: {
+      ${cfg.nginx.virtualHost} = {
+        locations = (
+          genAttrs'
+            [ "cgit.css" "cgit.png" "favicon.ico" "robots.txt" ]
+            (name: nameValuePair "= ${stripLocation cfg}/${name}" {
+              extraConfig = ''
+                alias ${cfg.package}/cgit/${name};
+              '';
+            })
+        ) // {
+          "~ ${regexLocation cfg}/.+/(info/refs|git-upload-pack)" = {
+            fastcgiParams = rec {
+              SCRIPT_FILENAME = "${pkgs.git}/libexec/git-core/git-http-backend";
+              GIT_HTTP_EXPORT_ALL = "1";
+              GIT_PROJECT_ROOT = mkCgitReposDir cfg;
+              HOME = GIT_PROJECT_ROOT;
+            };
+            extraConfig = mkFastcgiPass cfg;
+          };
+          "${stripLocation cfg}/" = {
+            fastcgiParams = {
+              SCRIPT_FILENAME = "${cfg.package}/cgit/cgit.cgi";
+              QUERY_STRING = "$args";
+              HTTP_HOST = "$server_name";
+              CGIT_CONFIG = mkCgitrc cfg;
+            };
+            extraConfig = mkFastcgiPass cfg;
+          };
+        };
+      };
+    }) cfgs);
+  };
+}
diff --git a/nixos/modules/services/networking/firewall-nftables.nix b/nixos/modules/services/networking/firewall-nftables.nix
index 0ed3c228075d3..452dd97d89d29 100644
--- a/nixos/modules/services/networking/firewall-nftables.nix
+++ b/nixos/modules/services/networking/firewall-nftables.nix
@@ -94,7 +94,13 @@ in
           ${optionalString (ifaceSet != "") ''iifname { ${ifaceSet} } accept comment "trusted interfaces"''}
 
           # Some ICMPv6 types like NDP is untracked
-          ct state vmap { invalid : drop, established : accept, related : accept, * : jump input-allow } comment "*: new and untracked"
+          ct state vmap {
+            invalid : drop,
+            established : accept,
+            related : accept,
+            new : jump input-allow,
+            untracked: jump input-allow,
+          }
 
           ${optionalString cfg.logRefusedConnections ''
             tcp flags syn / fin,syn,rst,ack log level info prefix "refused connection: "
@@ -143,7 +149,13 @@ in
           chain forward {
             type filter hook forward priority filter; policy drop;
 
-            ct state vmap { invalid : drop, established : accept, related : accept, * : jump forward-allow } comment "*: new and untracked"
+            ct state vmap {
+              invalid : drop,
+              established : accept,
+              related : accept,
+              new : jump forward-allow,
+              untracked : jump forward-allow,
+            }
 
           }
 
diff --git a/nixos/modules/services/networking/firewall.nix b/nixos/modules/services/networking/firewall.nix
index 4e332d489e4dc..ac02a93836b84 100644
--- a/nixos/modules/services/networking/firewall.nix
+++ b/nixos/modules/services/networking/firewall.nix
@@ -269,6 +269,10 @@ in
         assertion = cfg.filterForward -> config.networking.nftables.enable;
         message = "filterForward only works with the nftables based firewall";
       }
+      {
+        assertion = cfg.autoLoadConntrackHelpers -> lib.versionOlder config.boot.kernelPackages.kernel.version "6";
+        message = "conntrack helper autoloading has been removed from kernel 6.0 and newer";
+      }
     ];
 
     networking.firewall.trustedInterfaces = [ "lo" ];
diff --git a/nixos/modules/services/networking/headscale.nix b/nixos/modules/services/networking/headscale.nix
index cc46819eed5a6..d2851e72a0dd6 100644
--- a/nixos/modules/services/networking/headscale.nix
+++ b/nixos/modules/services/networking/headscale.nix
@@ -291,25 +291,59 @@ in {
                 '';
               };
 
-              client_secret_file = mkOption {
+              client_secret_path = mkOption {
                 type = types.nullOr types.path;
                 default = null;
                 description = lib.mdDoc ''
-                  Path to OpenID Connect client secret file.
+                  Path to OpenID Connect client secret file. Expands environment variables in format ''${VAR}.
                 '';
               };
 
-              domain_map = mkOption {
+              scope = mkOption {
+                type = types.listOf types.str;
+                default = ["openid" "profile" "email"];
+                description = lib.mdDoc ''
+                  Scopes used in the OIDC flow.
+                '';
+              };
+
+              extra_params = mkOption {
                 type = types.attrsOf types.str;
-                default = {};
+                default = { };
                 description = lib.mdDoc ''
-                  Domain map is used to map incomming users (by their email) to
-                  a namespace. The key can be a string, or regex.
+                  Custom query parameters to send with the Authorize Endpoint request.
                 '';
                 example = {
-                  ".*" = "default-namespace";
+                  domain_hint = "example.com";
                 };
               };
+
+              allowed_domains = mkOption {
+                type = types.listOf types.str;
+                default = [ ];
+                description = lib.mdDoc ''
+                  Allowed principal domains. if an authenticated user's domain
+                  is not in this list authentication request will be rejected.
+                '';
+                example = [ "example.com" ];
+              };
+
+              allowed_users = mkOption {
+                type = types.listOf types.str;
+                default = [ ];
+                description = lib.mdDoc ''
+                  Users allowed to authenticate even if not in allowedDomains.
+                '';
+                example = [ "alice@example.com" ];
+              };
+
+              strip_email_domain = mkOption {
+                type = types.bool;
+                default = true;
+                description = lib.mdDoc ''
+                  Whether the domain part of the email address should be removed when generating namespaces.
+                '';
+              };
             };
 
             tls_letsencrypt_hostname = mkOption {
@@ -391,14 +425,17 @@ in {
     (mkRenamedOptionModule ["services" "headscale" "dns" "baseDomain"] ["services" "headscale" "settings" "dns_config" "base_domain"])
     (mkRenamedOptionModule ["services" "headscale" "openIdConnect" "issuer"] ["services" "headscale" "settings" "oidc" "issuer"])
     (mkRenamedOptionModule ["services" "headscale" "openIdConnect" "clientId"] ["services" "headscale" "settings" "oidc" "client_id"])
-    (mkRenamedOptionModule ["services" "headscale" "openIdConnect" "clientSecretFile"] ["services" "headscale" "settings" "oidc" "client_secret_file"])
-    (mkRenamedOptionModule ["services" "headscale" "openIdConnect" "domainMap"] ["services" "headscale" "settings" "oidc" "domain_map"])
+    (mkRenamedOptionModule ["services" "headscale" "openIdConnect" "clientSecretFile"] ["services" "headscale" "settings" "oidc" "client_secret_path"])
     (mkRenamedOptionModule ["services" "headscale" "tls" "letsencrypt" "hostname"] ["services" "headscale" "settings" "tls_letsencrypt_hostname"])
     (mkRenamedOptionModule ["services" "headscale" "tls" "letsencrypt" "challengeType"] ["services" "headscale" "settings" "tls_letsencrypt_challenge_type"])
     (mkRenamedOptionModule ["services" "headscale" "tls" "letsencrypt" "httpListen"] ["services" "headscale" "settings" "tls_letsencrypt_listen"])
     (mkRenamedOptionModule ["services" "headscale" "tls" "certFile"] ["services" "headscale" "settings" "tls_cert_path"])
     (mkRenamedOptionModule ["services" "headscale" "tls" "keyFile"] ["services" "headscale" "settings" "tls_key_path"])
     (mkRenamedOptionModule ["services" "headscale" "aclPolicyFile"] ["services" "headscale" "settings" "acl_policy_path"])
+
+    (mkRemovedOptionModule ["services" "headscale" "openIdConnect" "domainMap"] ''
+      Headscale no longer uses domain_map. If you're using an old version of headscale you can still set this option via services.headscale.settings.oidc.domain_map.
+    '')
   ];
 
   config = mkIf cfg.enable {
@@ -441,9 +478,6 @@ in {
           export HEADSCALE_DB_PASS="$(head -n1 ${escapeShellArg cfg.settings.db_password_file})"
         ''}
 
-        ${optionalString (cfg.settings.oidc.client_secret_file != null) ''
-          export HEADSCALE_OIDC_CLIENT_SECRET="$(head -n1 ${escapeShellArg cfg.settings.oidc.client_secret_file})"
-        ''}
         exec ${cfg.package}/bin/headscale serve
       '';
 
diff --git a/nixos/modules/services/networking/imaginary.nix b/nixos/modules/services/networking/imaginary.nix
new file mode 100644
index 0000000000000..a655903d1031c
--- /dev/null
+++ b/nixos/modules/services/networking/imaginary.nix
@@ -0,0 +1,113 @@
+{ lib, config, pkgs, utils, ... }:
+
+let
+  inherit (lib) mdDoc mkEnableOption mkIf mkOption types;
+
+  cfg = config.services.imaginary;
+in {
+  options.services.imaginary = {
+    enable = mkEnableOption (mdDoc "imaginary image processing microservice");
+
+    address = mkOption {
+      type = types.str;
+      default = "localhost";
+      description = mdDoc ''
+        Bind address. Corresponds to the `-a` flag.
+        Set to `""` to bind to all addresses.
+      '';
+      example = "[::1]";
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 8088;
+      description = mdDoc "Bind port. Corresponds to the `-p` flag.";
+    };
+
+    settings = mkOption {
+      description = mdDoc ''
+        Command line arguments passed to the imaginary executable, stripped of
+        the prefix `-`. See upstream's
+        [README](https://github.com/h2non/imaginary#command-line-usage) for all
+        options.
+      '';
+      type = types.submodule {
+        freeformType = with types; attrsOf (oneOf [
+          bool
+          int
+          (nonEmptyListOf str)
+          str
+        ]);
+
+        options = {
+          return-size = mkOption {
+            type = types.bool;
+            default = false;
+            description = mdDoc "Return the image size in the HTTP headers.";
+          };
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [ {
+      assertion = ! lib.hasAttr "a" cfg.settings;
+      message = "Use services.imaginary.address to specify the -a flag.";
+    } {
+      assertion = ! lib.hasAttr "p" cfg.settings;
+      message = "Use services.imaginary.port to specify the -p flag.";
+    } ];
+
+    systemd.services.imaginary = {
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = rec {
+        ExecStart = let
+          args = lib.mapAttrsToList (key: val:
+            "-" + key + "=" + lib.concatStringsSep "," (map toString (lib.toList val))
+          ) (cfg.settings // { a = cfg.address; p = cfg.port; });
+        in "${pkgs.imaginary}/bin/imaginary ${utils.escapeSystemdExecArgs args}";
+        ProtectProc = "invisible";
+        BindReadOnlyPaths = lib.optional (cfg.settings ? mount) cfg.settings.mount;
+        CapabilityBoundingSet = if cfg.port < 1024 then
+          [ "CAP_NET_BIND_SERVICE" ]
+        else
+          [ "" ];
+        AmbientCapabilities = CapabilityBoundingSet;
+        NoNewPrivileges = true;
+        DynamicUser = true;
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        TemporaryFileSystem = [ "/:ro" ];
+        PrivateTmp = true;
+        PrivateDevices = true;
+        PrivateUsers = cfg.port >= 1024;
+        ProtectHostname = true;
+        ProtectClock = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectKernelLogs = true;
+        ProtectControlGroups = true;
+        RestrictAddressFamilies = [
+          "AF_INET"
+          "AF_INET6"
+        ];
+        RestrictNamespaces = true;
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        RestrictRealtime = true;
+        PrivateMounts = true;
+        SystemCallFilter = [
+          "@system-service"
+          "~@privileged"
+        ];
+        DevicePolicy = "closed";
+      };
+    };
+  };
+
+  meta = {
+    maintainers = with lib.maintainers; [ dotlambda ];
+  };
+}
diff --git a/nixos/modules/services/networking/jicofo.nix b/nixos/modules/services/networking/jicofo.nix
index 5e97889607363..0886bbe004c46 100644
--- a/nixos/modules/services/networking/jicofo.nix
+++ b/nixos/modules/services/networking/jicofo.nix
@@ -4,6 +4,15 @@ with lib;
 
 let
   cfg = config.services.jicofo;
+
+  # HOCON is a JSON superset that some jitsi-meet components use for configuration
+  toHOCON = x: if isAttrs x && x ? __hocon_envvar then ("\${" + x.__hocon_envvar + "}")
+    else if isAttrs x && x ? __hocon_unquoted_string then x.__hocon_unquoted_string
+    else if isAttrs x then "{${ concatStringsSep "," (mapAttrsToList (k: v: ''"${k}":${toHOCON v}'') x) }}"
+    else if isList x then "[${ concatMapStringsSep "," toHOCON x }]"
+    else builtins.toJSON x;
+
+  configFile = pkgs.writeText "jicofo.conf" (toHOCON cfg.config);
 in
 {
   options.services.jicofo = with types; {
@@ -68,22 +77,34 @@ in
     };
 
     config = mkOption {
-      type = attrsOf str;
+      type = (pkgs.formats.json {}).type;
       default = { };
       example = literalExpression ''
         {
-          "org.jitsi.jicofo.auth.URL" = "XMPP:jitsi-meet.example.com";
+          jicofo.bridge.max-bridge-participants = 42;
         }
       '';
       description = lib.mdDoc ''
-        Contents of the {file}`sip-communicator.properties` configuration file for jicofo.
+        Contents of the {file}`jicofo.conf` configuration file.
       '';
     };
   };
 
   config = mkIf cfg.enable {
-    services.jicofo.config = mapAttrs (_: v: mkDefault v) {
-      "org.jitsi.jicofo.BRIDGE_MUC" = cfg.bridgeMuc;
+    services.jicofo.config = {
+      jicofo = {
+        bridge.brewery-jid = cfg.bridgeMuc;
+        xmpp = rec {
+          client = {
+            hostname = cfg.xmppHost;
+            username = cfg.userName;
+            domain = cfg.userDomain;
+            password = { __hocon_envvar = "JICOFO_AUTH_PASS"; };
+            xmpp-domain = if cfg.xmppDomain == null then cfg.xmppHost else cfg.xmppDomain;
+          };
+          service = client;
+        };
+      };
     };
 
     users.groups.jitsi-meet = {};
@@ -93,6 +114,7 @@ in
         "-Dnet.java.sip.communicator.SC_HOME_DIR_LOCATION" = "/etc/jitsi";
         "-Dnet.java.sip.communicator.SC_HOME_DIR_NAME" = "jicofo";
         "-Djava.util.logging.config.file" = "/etc/jitsi/jicofo/logging.properties";
+        "-Dconfig.file" = configFile;
       };
     in
     {
@@ -101,18 +123,13 @@ in
       after = [ "network.target" ];
 
       restartTriggers = [
-        config.environment.etc."jitsi/jicofo/sip-communicator.properties".source
+        configFile
       ];
       environment.JAVA_SYS_PROPS = concatStringsSep " " (mapAttrsToList (k: v: "${k}=${toString v}") jicofoProps);
 
       script = ''
-        ${pkgs.jicofo}/bin/jicofo \
-          --host=${cfg.xmppHost} \
-          --domain=${if cfg.xmppDomain == null then cfg.xmppHost else cfg.xmppDomain} \
-          --secret=$(cat ${cfg.componentPasswordFile}) \
-          --user_name=${cfg.userName} \
-          --user_domain=${cfg.userDomain} \
-          --user_password=$(cat ${cfg.userPasswordFile})
+        export JICOFO_AUTH_PASS="$(<${cfg.userPasswordFile})"
+        exec "${pkgs.jicofo}/bin/jicofo"
       '';
 
       serviceConfig = {
@@ -140,10 +157,7 @@ in
       };
     };
 
-    environment.etc."jitsi/jicofo/sip-communicator.properties".source =
-      pkgs.writeText "sip-communicator.properties" (
-        generators.toKeyValue {} cfg.config
-      );
+    environment.etc."jitsi/jicofo/sip-communicator.properties".text = "";
     environment.etc."jitsi/jicofo/logging.properties".source =
       mkDefault "${pkgs.jicofo}/etc/jitsi/jicofo/logging.properties-journal";
   };
diff --git a/nixos/modules/services/networking/multipath.nix b/nixos/modules/services/networking/multipath.nix
index b20ec76ddf594..bd403e109c2af 100644
--- a/nixos/modules/services/networking/multipath.nix
+++ b/nixos/modules/services/networking/multipath.nix
@@ -513,22 +513,22 @@ in {
         ${indentLines 2 devices}
         }
 
-        ${optionalString (!isNull defaults) ''
+        ${optionalString (defaults != null) ''
           defaults {
           ${indentLines 2 defaults}
           }
         ''}
-        ${optionalString (!isNull blacklist) ''
+        ${optionalString (blacklist != null) ''
           blacklist {
           ${indentLines 2 blacklist}
           }
         ''}
-        ${optionalString (!isNull blacklist_exceptions) ''
+        ${optionalString (blacklist_exceptions != null) ''
           blacklist_exceptions {
           ${indentLines 2 blacklist_exceptions}
           }
         ''}
-        ${optionalString (!isNull overrides) ''
+        ${optionalString (overrides != null) ''
           overrides {
           ${indentLines 2 overrides}
           }
diff --git a/nixos/modules/services/networking/murmur.nix b/nixos/modules/services/networking/murmur.nix
index 32498ca25ea83..9ec4f57ca43ed 100644
--- a/nixos/modules/services/networking/murmur.nix
+++ b/nixos/modules/services/networking/murmur.nix
@@ -42,6 +42,8 @@ let
     ${if cfg.sslKey  == "" then "" else "sslKey="+cfg.sslKey}
     ${if cfg.sslCa   == "" then "" else "sslCA="+cfg.sslCa}
 
+    ${lib.optionalString (cfg.dbus != null) "dbus=${cfg.dbus}"}
+
     ${cfg.extraConfig}
   '';
 in
@@ -282,6 +284,12 @@ in
           `murmur` is running.
         '';
       };
+
+      dbus = mkOption {
+        type = types.enum [ null "session" "system" ];
+        default = null;
+        description = lib.mdDoc "Enable D-Bus remote control. Set to the bus you want Murmur to connect to.";
+      };
     };
   };
 
@@ -325,5 +333,27 @@ in
         Group = "murmur";
       };
     };
+
+    # currently not included in upstream package, addition requested at
+    # https://github.com/mumble-voip/mumble/issues/6078
+    services.dbus.packages = mkIf (cfg.dbus == "system") [(pkgs.writeTextFile {
+      name = "murmur-dbus-policy";
+      text = ''
+        <!DOCTYPE busconfig PUBLIC
+          "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+          "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+        <busconfig>
+          <policy user="murmur">
+            <allow own="net.sourceforge.mumble.murmur"/>
+          </policy>
+
+          <policy context="default">
+            <allow send_destination="net.sourceforge.mumble.murmur"/>
+            <allow receive_sender="net.sourceforge.mumble.murmur"/>
+          </policy>
+        </busconfig>
+      '';
+      destination = "/share/dbus-1/system.d/murmur.conf";
+    })];
   };
 }
diff --git a/nixos/modules/services/networking/networkd-dispatcher.nix b/nixos/modules/services/networking/networkd-dispatcher.nix
new file mode 100644
index 0000000000000..c5319ca7b88a2
--- /dev/null
+++ b/nixos/modules/services/networking/networkd-dispatcher.nix
@@ -0,0 +1,98 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.networkd-dispatcher;
+
+in {
+
+  options = {
+    services.networkd-dispatcher = {
+
+      enable = mkEnableOption (mdDoc ''
+        Networkd-dispatcher service for systemd-networkd connection status
+        change. See [https://gitlab.com/craftyguy/networkd-dispatcher](upstream instructions)
+        for usage.
+      '');
+
+      rules = mkOption {
+        default = {};
+        example = lib.literalExpression ''
+          { "restart-tor" = {
+              onState = ["routable" "off"];
+              script = '''
+                #!''${pkgs.runtimeShell}
+                if [[ $IFACE == "wlan0" && $AdministrativeState == "configured" ]]; then
+                  echo "Restarting Tor ..."
+                  systemctl restart tor
+                fi
+                exit 0
+              ''';
+            };
+          };
+        '';
+        description = lib.mdDoc ''
+          Declarative configuration of networkd-dispatcher rules. See
+          [https://gitlab.com/craftyguy/networkd-dispatcher](upstream instructions)
+          for an introduction and example scripts.
+        '';
+        type = types.attrsOf (types.submodule {
+          options = {
+            onState = mkOption {
+              type = types.listOf (types.enum [
+                "routable" "dormant" "no-carrier" "off" "carrier" "degraded"
+                "configuring" "configured"
+              ]);
+              default = null;
+              description = lib.mdDoc ''
+                List of names of the systemd-networkd operational states which
+                should trigger the script. See <https://www.freedesktop.org/software/systemd/man/networkctl.html>
+                for a description of the specific state type.
+              '';
+            };
+            script = mkOption {
+              type = types.lines;
+              description = lib.mdDoc ''
+                Shell commands executed on specified operational states.
+              '';
+            };
+          };
+        });
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd = {
+      packages = [ pkgs.networkd-dispatcher ];
+      services.networkd-dispatcher = {
+        wantedBy = [ "multi-user.target" ];
+        # Override existing ExecStart definition
+        serviceConfig.ExecStart = let
+          scriptDir = pkgs.symlinkJoin {
+            name = "networkd-dispatcher-script-dir";
+            paths = lib.mapAttrsToList (name: cfg:
+              (map(state:
+                pkgs.writeTextFile {
+                  inherit name;
+                  text = cfg.script;
+                  destination = "/${state}.d/${name}";
+                  executable = true;
+                }
+              ) cfg.onState)
+            ) cfg.rules;
+          };
+        in [
+          ""
+          "${pkgs.networkd-dispatcher}/bin/networkd-dispatcher -v --script-dir ${scriptDir} $networkd_dispatcher_args"
+        ];
+      };
+    };
+
+  };
+}
+
diff --git a/nixos/modules/services/networking/nftables.nix b/nixos/modules/services/networking/nftables.nix
index bd13e8c9929a3..faff1dca89bab 100644
--- a/nixos/modules/services/networking/nftables.nix
+++ b/nixos/modules/services/networking/nftables.nix
@@ -28,6 +28,32 @@ in
           <https://wiki.nftables.org/wiki-nftables/index.php/Troubleshooting#Question_4._How_do_nftables_and_iptables_interact_when_used_on_the_same_system.3F>.
         '';
     };
+
+    networking.nftables.checkRuleset = mkOption {
+      type = types.bool;
+      default = true;
+      description = lib.mdDoc ''
+        Run `nft check` on the ruleset to spot syntax errors during build.
+        Because this is executed in a sandbox, the check might fail if it requires
+        access to any environmental factors or paths outside the Nix store.
+        To circumvent this, the ruleset file can be edited using the preCheckRuleset
+        option to work in the sandbox environment.
+      '';
+    };
+
+    networking.nftables.preCheckRuleset = mkOption {
+      type = types.lines;
+      default = "";
+      example = lib.literalExpression ''
+        sed 's/skgid meadow/skgid nogroup/g' -i ruleset.conf
+      '';
+      description = lib.mdDoc ''
+        This script gets run before the ruleset is checked. It can be used to
+        create additional files needed for the ruleset check to work, or modify
+        the ruleset for cases the build environment cannot cover.
+      '';
+    };
+
     networking.nftables.ruleset = mkOption {
       type = types.lines;
       default = "";
@@ -105,13 +131,24 @@ in
       wantedBy = [ "multi-user.target" ];
       reloadIfChanged = true;
       serviceConfig = let
-        rulesScript = pkgs.writeScript "nftables-rules" ''
-          #! ${pkgs.nftables}/bin/nft -f
-          flush ruleset
-          ${if cfg.rulesetFile != null then ''
-            include "${cfg.rulesetFile}"
-          '' else cfg.ruleset}
-        '';
+        rulesScript = pkgs.writeTextFile {
+          name =  "nftables-rules";
+          executable = true;
+          text = ''
+            #! ${pkgs.nftables}/bin/nft -f
+            flush ruleset
+            ${if cfg.rulesetFile != null then ''
+              include "${cfg.rulesetFile}"
+            '' else cfg.ruleset}
+          '';
+          checkPhase = lib.optionalString cfg.checkRuleset ''
+            cp $out ruleset.conf
+            ${cfg.preCheckRuleset}
+            export NIX_REDIRECTS=/etc/protocols=${pkgs.buildPackages.iana-etc}/etc/protocols:/etc/services=${pkgs.buildPackages.iana-etc}/etc/services
+            LD_PRELOAD="${pkgs.buildPackages.libredirect}/lib/libredirect.so ${pkgs.buildPackages.lklWithFirewall.lib}/lib/liblkl-hijack.so" \
+              ${pkgs.buildPackages.nftables}/bin/nft --check --file ruleset.conf
+          '';
+        };
       in {
         Type = "oneshot";
         RemainAfterExit = true;
diff --git a/nixos/modules/services/networking/ntp/chrony.nix b/nixos/modules/services/networking/ntp/chrony.nix
index 6c8d7b985d5f1..2d421abc8be79 100644
--- a/nixos/modules/services/networking/ntp/chrony.nix
+++ b/nixos/modules/services/networking/ntp/chrony.nix
@@ -203,7 +203,7 @@ in
           PrivateMounts = true;
           # System Call Filtering
           SystemCallArchitectures = "native";
-          SystemCallFilter = [ "~@cpu-emulation @debug @keyring @mount @obsolete @privileged @resources" "@clock" "@setuid" "capset" "chown" ] ++ lib.optional pkgs.stdenv.hostPlatform.isAarch64 "fchownat";
+          SystemCallFilter = [ "~@cpu-emulation @debug @keyring @mount @obsolete @privileged @resources" "@clock" "@setuid" "capset" "@chown" ];
         };
       };
   };
diff --git a/nixos/modules/services/networking/openvpn.nix b/nixos/modules/services/networking/openvpn.nix
index 78d312af978b8..9a5866f2afd40 100644
--- a/nixos/modules/services/networking/openvpn.nix
+++ b/nixos/modules/services/networking/openvpn.nix
@@ -14,7 +14,6 @@ let
       path = makeBinPath (getAttr "openvpn-${name}" config.systemd.services).path;
 
       upScript = ''
-        #! /bin/sh
         export PATH=${path}
 
         # For convenience in client scripts, extract the remote domain
@@ -34,7 +33,6 @@ let
       '';
 
       downScript = ''
-        #! /bin/sh
         export PATH=${path}
         ${optionalString cfg.updateResolvConf
            "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"}
@@ -47,9 +45,9 @@ let
           ${optionalString (cfg.up != "" || cfg.down != "" || cfg.updateResolvConf) "script-security 2"}
           ${cfg.config}
           ${optionalString (cfg.up != "" || cfg.updateResolvConf)
-              "up ${pkgs.writeScript "openvpn-${name}-up" upScript}"}
+              "up ${pkgs.writeShellScript "openvpn-${name}-up" upScript}"}
           ${optionalString (cfg.down != "" || cfg.updateResolvConf)
-              "down ${pkgs.writeScript "openvpn-${name}-down" downScript}"}
+              "down ${pkgs.writeShellScript "openvpn-${name}-down" downScript}"}
           ${optionalString (cfg.authUserPass != null)
               "auth-user-pass ${pkgs.writeText "openvpn-credentials-${name}" ''
                 ${cfg.authUserPass.username}
diff --git a/nixos/modules/services/networking/peroxide.nix b/nixos/modules/services/networking/peroxide.nix
new file mode 100644
index 0000000000000..6cac4bf2f89a1
--- /dev/null
+++ b/nixos/modules/services/networking/peroxide.nix
@@ -0,0 +1,131 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.peroxide;
+  settingsFormat = pkgs.formats.yaml { };
+  stateDir = "peroxide";
+in
+{
+  options.services.peroxide = {
+    enable = mkEnableOption (lib.mdDoc "enable");
+
+    package = mkPackageOptionMD pkgs "peroxide" {
+      default = [ "peroxide" ];
+    };
+
+    logLevel = mkOption {
+      # https://github.com/sirupsen/logrus#level-logging
+      type = types.enum [ "Panic" "Fatal" "Error" "Warning" "Info" "Debug" "Trace" ];
+      default = "Warning";
+      example = "Info";
+      description = lib.mdDoc "Only log messages of this priority or higher.";
+    };
+
+    settings = mkOption {
+      type = types.submodule {
+        freeformType = settingsFormat.type;
+
+        options = {
+          UserPortImap = mkOption {
+            type = types.port;
+            default = 1143;
+            description = lib.mdDoc "The port on which to listen for IMAP connections.";
+          };
+
+          UserPortSmtp = mkOption {
+            type = types.port;
+            default = 1025;
+            description = lib.mdDoc "The port on which to listen for SMTP connections.";
+          };
+
+          ServerAddress = mkOption {
+            type = types.str;
+            default = "[::0]";
+            example = "localhost";
+            description = lib.mdDoc "The address on which to listen for connections.";
+          };
+        };
+      };
+      default = { };
+      description = lib.mdDoc ''
+        Configuration for peroxide.  See
+        [config.example.yaml](https://github.com/ljanyst/peroxide/blob/master/config.example.yaml)
+        for an example configuration.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.peroxide.settings = {
+      # peroxide deletes the cache directory on startup, which requires write
+      # permission on the parent directory, so we can't use
+      # /var/cache/peroxide
+      CacheDir = "/var/cache/peroxide/cache";
+      X509Key = mkDefault "/var/lib/${stateDir}/key.pem";
+      X509Cert = mkDefault "/var/lib/${stateDir}/cert.pem";
+      CookieJar = "/var/lib/${stateDir}/cookies.json";
+      CredentialsStore = "/var/lib/${stateDir}/credentials.json";
+    };
+
+    users.users.peroxide = {
+      isSystemUser = true;
+      group = "peroxide";
+    };
+    users.groups.peroxide = { };
+
+    systemd.services.peroxide = {
+      description = "Peroxide ProtonMail bridge";
+      requires = [ "network.target" ];
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      restartTriggers = [ config.environment.etc."peroxide.conf".source ];
+
+      serviceConfig = {
+        Type = "simple";
+        User = "peroxide";
+        LogsDirectory = "peroxide";
+        LogsDirectoryMode = "0750";
+        # Specify just "peroxide" so that the user has write permission, because
+        # peroxide deletes and recreates the cache directory on startup.
+        CacheDirectory = [ "peroxide" "peroxide/cache" ];
+        CacheDirectoryMode = "0700";
+        StateDirectory = stateDir;
+        StateDirectoryMode = "0700";
+        ExecStart = "${cfg.package}/bin/peroxide -log-file=/var/log/peroxide/peroxide.log -log-level ${cfg.logLevel}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+      };
+
+      preStart = ''
+        # Create a self-signed certificate if no certificate exists.
+        if [[ ! -e "${cfg.settings.X509Key}" && ! -e "${cfg.settings.X509Cert}" ]]; then
+            ${cfg.package}/bin/peroxide-cfg -action gen-x509 \
+              -x509-org 'N/A' \
+              -x509-cn 'nixos' \
+              -x509-cert "${cfg.settings.X509Cert}" \
+              -x509-key "${cfg.settings.X509Key}"
+        fi
+      '';
+    };
+
+    # https://github.com/ljanyst/peroxide/blob/master/peroxide.logrotate
+    services.logrotate.settings.peroxide = {
+      files = "/var/log/peroxide/peroxide.log";
+      rotate = 31;
+      frequency = "daily";
+      compress = true;
+      delaycompress = true;
+      missingok = true;
+      notifempty = true;
+      su = "peroxide peroxide";
+      postrotate = "systemctl reload peroxide";
+    };
+
+    environment.etc."peroxide.conf".source = settingsFormat.generate "peroxide.conf" cfg.settings;
+    environment.systemPackages = [ cfg.package ];
+  };
+
+  meta.maintainers = with maintainers; [ aanderse aidalgol ];
+}
diff --git a/nixos/modules/services/networking/radicale.nix b/nixos/modules/services/networking/radicale.nix
index 8e4789c7ca597..00dbd6bbe386d 100644
--- a/nixos/modules/services/networking/radicale.nix
+++ b/nixos/modules/services/networking/radicale.nix
@@ -9,7 +9,7 @@ let
     listToValue = concatMapStringsSep ", " (generators.mkValueStringDefault { });
   };
 
-  pkg = if isNull cfg.package then
+  pkg = if cfg.package == null then
     pkgs.radicale
   else
     cfg.package;
@@ -117,13 +117,13 @@ in {
       }
     ];
 
-    warnings = optional (isNull cfg.package && versionOlder config.system.stateVersion "17.09") ''
+    warnings = optional (cfg.package == null && versionOlder config.system.stateVersion "17.09") ''
       The configuration and storage formats of your existing Radicale
       installation might be incompatible with the newest version.
       For upgrade instructions see
       https://radicale.org/2.1.html#documentation/migration-from-1xx-to-2xx.
       Set services.radicale.package to suppress this warning.
-    '' ++ optional (isNull cfg.package && versionOlder config.system.stateVersion "20.09") ''
+    '' ++ optional (cfg.package == null && versionOlder config.system.stateVersion "20.09") ''
       The configuration format of your existing Radicale installation might be
       incompatible with the newest version.  For upgrade instructions see
       https://github.com/Kozea/Radicale/blob/3.0.6/NEWS.md#upgrade-checklist.
diff --git a/nixos/modules/services/networking/ssh/sshd.nix b/nixos/modules/services/networking/ssh/sshd.nix
index 095c7de0b7aa1..5f225682b7779 100644
--- a/nixos/modules/services/networking/ssh/sshd.nix
+++ b/nixos/modules/services/networking/ssh/sshd.nix
@@ -19,7 +19,7 @@ let
         else if true  ==   v then "yes"
         else if false ==   v then "no"
         else if isList     v then concatStringsSep "," v
-        else throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty {}) v}";
+        else throw "unsupported type ${builtins.typeOf v}: ${(lib.generators.toPretty {}) v}";
 
   # dont use the "=" operator
   settingsFormat = (pkgs.formats.keyValue {
diff --git a/nixos/modules/services/networking/tailscale.nix b/nixos/modules/services/networking/tailscale.nix
index 233bfdf9ebf57..c81cf293ab6d9 100644
--- a/nixos/modules/services/networking/tailscale.nix
+++ b/nixos/modules/services/networking/tailscale.nix
@@ -82,8 +82,8 @@ in {
     };
 
     boot.kernel.sysctl = mkIf (cfg.useRoutingFeatures == "server" || cfg.useRoutingFeatures == "both") {
-      "net.ipv4.conf.all.forwarding" = mkDefault true;
-      "net.ipv6.conf.all.forwarding" = mkDefault true;
+      "net.ipv4.conf.all.forwarding" = mkOverride 97 true;
+      "net.ipv6.conf.all.forwarding" = mkOverride 97 true;
     };
 
     networking.firewall.checkReversePath = mkIf (cfg.useRoutingFeatures == "client" || cfg.useRoutingFeatures == "both") "loose";
diff --git a/nixos/modules/services/networking/teleport.nix b/nixos/modules/services/networking/teleport.nix
index 6433554f87dab..399af711c0e12 100644
--- a/nixos/modules/services/networking/teleport.nix
+++ b/nixos/modules/services/networking/teleport.nix
@@ -11,6 +11,14 @@ in
     services.teleport = with lib.types; {
       enable = mkEnableOption (lib.mdDoc "the Teleport service");
 
+      package = mkOption {
+        type = types.package;
+        default = pkgs.teleport;
+        defaultText = lib.literalMD "pkgs.teleport";
+        example = lib.literalMD "pkgs.teleport_11";
+        description = lib.mdDoc "The teleport package to use";
+      };
+
       settings = mkOption {
         type = settingsYaml.type;
         default = { };
@@ -74,14 +82,14 @@ in
   };
 
   config = mkIf config.services.teleport.enable {
-    environment.systemPackages = [ pkgs.teleport ];
+    environment.systemPackages = [ cfg.package ];
 
     systemd.services.teleport = {
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
       serviceConfig = {
         ExecStart = ''
-          ${pkgs.teleport}/bin/teleport start \
+          ${cfg.package}/bin/teleport start \
             ${optionalString cfg.insecure.enable "--insecure"} \
             ${optionalString cfg.diag.enable "--diag-addr=${cfg.diag.addr}:${toString cfg.diag.port}"} \
             ${optionalString (cfg.settings != { }) "--config=${settingsYaml.generate "teleport.yaml" cfg.settings}"}
diff --git a/nixos/modules/services/networking/unbound.nix b/nixos/modules/services/networking/unbound.nix
index c85dd03867f77..0426dbb0c83c3 100644
--- a/nixos/modules/services/networking/unbound.nix
+++ b/nixos/modules/services/networking/unbound.nix
@@ -286,6 +286,8 @@ in {
         LockPersonality = true;
         RestrictSUIDSGID = true;
 
+        ReadWritePaths = [ cfg.stateDir ];
+
         Restart = "on-failure";
         RestartSec = "5s";
       };
diff --git a/nixos/modules/services/networking/v2raya.nix b/nixos/modules/services/networking/v2raya.nix
index 2d697b4fb56f3..0bea73798daf1 100644
--- a/nixos/modules/services/networking/v2raya.nix
+++ b/nixos/modules/services/networking/v2raya.nix
@@ -12,27 +12,38 @@ with lib;
   config = mkIf config.services.v2raya.enable {
     environment.systemPackages = [ pkgs.v2raya ];
 
-    systemd.services.v2raya = {
-      unitConfig = {
-        Description = "v2rayA service";
-        Documentation = "https://github.com/v2rayA/v2rayA/wiki";
-        After = [ "network.target" "nss-lookup.target" "iptables.service" "ip6tables.service" ];
-        Wants = [ "network.target" ];
-      };
+    systemd.services.v2raya =
+      let
+        nftablesEnabled = config.networking.nftables.enable;
+        iptablesServices = [
+          "iptables.service"
+        ] ++ optional config.networking.enableIPv6 "ip6tables.service";
+        tableServices = if nftablesEnabled then [ "nftables.service" ] else iptablesServices;
+      in
+      {
+        unitConfig = {
+          Description = "v2rayA service";
+          Documentation = "https://github.com/v2rayA/v2rayA/wiki";
+          After = [
+            "network.target"
+            "nss-lookup.target"
+          ] ++ tableServices;
+          Wants = [ "network.target" ];
+        };
 
-      serviceConfig = {
-        User = "root";
-        ExecStart = "${getExe pkgs.v2raya} --log-disable-timestamp";
-        Environment = [ "V2RAYA_LOG_FILE=/var/log/v2raya/v2raya.log" ];
-        LimitNPROC = 500;
-        LimitNOFILE = 1000000;
-        Restart = "on-failure";
-        Type = "simple";
-      };
+        serviceConfig = {
+          User = "root";
+          ExecStart = "${getExe pkgs.v2raya} --log-disable-timestamp";
+          Environment = [ "V2RAYA_LOG_FILE=/var/log/v2raya/v2raya.log" ];
+          LimitNPROC = 500;
+          LimitNOFILE = 1000000;
+          Restart = "on-failure";
+          Type = "simple";
+        };
 
-      wantedBy = [ "multi-user.target" ];
-      path = with pkgs; [ iptables bash iproute2 ]; # required by v2rayA TProxy functionality
-    };
+        wantedBy = [ "multi-user.target" ];
+        path = with pkgs; [ iptables bash iproute2 ]; # required by v2rayA TProxy functionality
+      };
   };
 
   meta.maintainers = with maintainers; [ elliot ];
diff --git a/nixos/modules/services/networking/wireguard.nix b/nixos/modules/services/networking/wireguard.nix
index 1d6556f626be9..8b025228cc1ff 100644
--- a/nixos/modules/services/networking/wireguard.nix
+++ b/nixos/modules/services/networking/wireguard.nix
@@ -176,7 +176,7 @@ let
 
       publicKey = mkOption {
         example = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
-        type = types.str;
+        type = types.singleLineStr;
         description = lib.mdDoc "The base64 public key of the peer.";
       };
 
@@ -461,7 +461,7 @@ let
 
           ${ipPreMove} link add dev "${name}" type wireguard
           ${optionalString (values.interfaceNamespace != null && values.interfaceNamespace != values.socketNamespace) ''${ipPreMove} link set "${name}" netns "${ns}"''}
-          ${optionalString (values.mtu != null) ''${ipPreMove} link set "${name}" mtu ${toString values.mtu}''}
+          ${optionalString (values.mtu != null) ''${ipPostMove} link set "${name}" mtu ${toString values.mtu}''}
 
           ${concatMapStringsSep "\n" (ip:
             ''${ipPostMove} address add "${ip}" dev "${name}"''
diff --git a/nixos/modules/services/networking/yggdrasil.nix b/nixos/modules/services/networking/yggdrasil.nix
index fd7193154c6c7..55a6002d61af1 100644
--- a/nixos/modules/services/networking/yggdrasil.nix
+++ b/nixos/modules/services/networking/yggdrasil.nix
@@ -8,7 +8,8 @@ let
   configFileProvided = cfg.configFile != null;
 
   format = pkgs.formats.json { };
-in {
+in
+{
   imports = [
     (mkRenamedOptionModule
       [ "services" "yggdrasil" "config" ]
@@ -21,7 +22,7 @@ in {
 
       settings = mkOption {
         type = format.type;
-        default = {};
+        default = { };
         example = {
           Peers = [
             "tcp://aa.bb.cc.dd:eeeee"
@@ -45,7 +46,7 @@ in {
 
           If no keys are specified then ephemeral keys are generated
           and the Yggdrasil interface will have a random IPv6 address
-          each time the service is started, this is the default.
+          each time the service is started. This is the default.
 
           If both {option}`configFile` and {option}`settings`
           are supplied, they will be combined, with values from
@@ -61,8 +62,13 @@ in {
         default = null;
         example = "/run/keys/yggdrasil.conf";
         description = lib.mdDoc ''
-          A file which contains JSON configuration for yggdrasil.
-          See the {option}`settings` option for more information.
+          A file which contains JSON or HJSON configuration for yggdrasil. See
+          the {option}`settings` option for more information.
+
+          Note: This file must not be larger than 1 MB because it is passed to
+          the yggdrasil process via systemd‘s LoadCredential mechanism. For
+          details, see <https://systemd.io/CREDENTIALS/> and `man 5
+          systemd.exec`.
         '';
       };
 
@@ -77,20 +83,20 @@ in {
         type = bool;
         default = false;
         description = lib.mdDoc ''
-          Whether to open the UDP port used for multicast peer
-          discovery. The NixOS firewall blocks link-local
-          communication, so in order to make local peering work you
-          will also need to set `LinkLocalTCPPort` in your
-          yggdrasil configuration ({option}`settings` or
-          {option}`configFile`) to a port number other than 0,
-          and then add that port to
-          {option}`networking.firewall.allowedTCPPorts`.
+          Whether to open the UDP port used for multicast peer discovery. The
+          NixOS firewall blocks link-local communication, so in order to make
+          incoming local peering work you will also need to configure
+          `MulticastInterfaces` in your Yggdrasil configuration
+          ({option}`settings` or {option}`configFile`). You will then have to
+          add the ports that you configure there to your firewall configuration
+          ({option}`networking.firewall.allowedTCPPorts` or
+          {option}`networking.firewall.interfaces.<name>.allowedTCPPorts`).
         '';
       };
 
       denyDhcpcdInterfaces = mkOption {
         type = listOf str;
-        default = [];
+        default = [ ];
         example = [ "tap*" ];
         description = lib.mdDoc ''
           Disable the DHCP client for any interface whose name matches
@@ -118,80 +124,102 @@ in {
     };
   };
 
-  config = mkIf cfg.enable (let binYggdrasil = cfg.package + "/bin/yggdrasil";
-  in {
-    assertions = [{
-      assertion = config.networking.enableIPv6;
-      message = "networking.enableIPv6 must be true for yggdrasil to work";
-    }];
-
-    system.activationScripts.yggdrasil = mkIf cfg.persistentKeys ''
-      if [ ! -e ${keysPath} ]
-      then
-        mkdir --mode=700 -p ${builtins.dirOf keysPath}
-        ${binYggdrasil} -genconf -json \
-          | ${pkgs.jq}/bin/jq \
-              'to_entries|map(select(.key|endswith("Key")))|from_entries' \
-          > ${keysPath}
-      fi
-    '';
-
-    systemd.services.yggdrasil = {
-      description = "Yggdrasil Network Service";
-      after = [ "network-pre.target" ];
-      wants = [ "network.target" ];
-      before = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
-
-      preStart =
-        (if settingsProvided || configFileProvided || cfg.persistentKeys then
-          "echo "
-
-          + (lib.optionalString settingsProvided
-            "'${builtins.toJSON cfg.settings}'")
-          + (lib.optionalString configFileProvided "$(cat ${cfg.configFile})")
-          + (lib.optionalString cfg.persistentKeys "$(cat ${keysPath})")
-          + " | ${pkgs.jq}/bin/jq -s add | ${binYggdrasil} -normaliseconf -useconf"
-        else
-          "${binYggdrasil} -genconf") + " > /run/yggdrasil/yggdrasil.conf";
-
-      serviceConfig = {
-        ExecStart =
-          "${binYggdrasil} -useconffile /run/yggdrasil/yggdrasil.conf";
-        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
-        Restart = "always";
-
-        DynamicUser = true;
-        StateDirectory = "yggdrasil";
-        RuntimeDirectory = "yggdrasil";
-        RuntimeDirectoryMode = "0750";
-        BindReadOnlyPaths = lib.optional configFileProvided cfg.configFile
-          ++ lib.optional cfg.persistentKeys keysPath;
-        ReadWritePaths = "/run/yggdrasil";
-
-        AmbientCapabilities = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE";
-        CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE";
-        MemoryDenyWriteExecute = true;
-        ProtectControlGroups = true;
-        ProtectHome = "tmpfs";
-        ProtectKernelModules = true;
-        ProtectKernelTunables = true;
-        RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
-        RestrictNamespaces = true;
-        RestrictRealtime = true;
-        SystemCallArchitectures = "native";
-        SystemCallFilter = [ "@system-service" "~@privileged @keyring" ];
-      } // (if (cfg.group != null) then {
-        Group = cfg.group;
-      } else {});
-    };
+  config = mkIf cfg.enable (
+    let
+      binYggdrasil = "${cfg.package}/bin/yggdrasil";
+      binHjson = "${pkgs.hjson-go}/bin/hjson-cli";
+    in
+    {
+      assertions = [{
+        assertion = config.networking.enableIPv6;
+        message = "networking.enableIPv6 must be true for yggdrasil to work";
+      }];
+
+      system.activationScripts.yggdrasil = mkIf cfg.persistentKeys ''
+        if [ ! -e ${keysPath} ]
+        then
+          mkdir --mode=700 -p ${builtins.dirOf keysPath}
+          ${binYggdrasil} -genconf -json \
+            | ${pkgs.jq}/bin/jq \
+                'to_entries|map(select(.key|endswith("Key")))|from_entries' \
+            > ${keysPath}
+        fi
+      '';
+
+      systemd.services.yggdrasil = {
+        description = "Yggdrasil Network Service";
+        after = [ "network-pre.target" ];
+        wants = [ "network.target" ];
+        before = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+
+        # This script first prepares the config file, then it starts Yggdrasil.
+        # The preparation could also be done in ExecStartPre/preStart but only
+        # systemd versions >= v252 support reading credentials in ExecStartPre. As
+        # of February 2023, systemd v252 is not yet in the stable branch of NixOS.
+        #
+        # This could be changed in the future once systemd version v252 has
+        # reached NixOS but it does not have to be. Config file preparation is
+        # fast enough, it does not need elevated privileges, and `set -euo
+        # pipefail` should make sure that the service is not started if the
+        # preparation fails. Therefore, it is not necessary to move the
+        # preparation to ExecStartPre.
+        script = ''
+          set -euo pipefail
+
+          # prepare config file
+          ${(if settingsProvided || configFileProvided || cfg.persistentKeys then
+            "echo "
+
+            + (lib.optionalString settingsProvided
+              "'${builtins.toJSON cfg.settings}'")
+            + (lib.optionalString configFileProvided
+              "$(${binHjson} -c \"$CREDENTIALS_DIRECTORY/yggdrasil.conf\")")
+            + (lib.optionalString cfg.persistentKeys "$(cat ${keysPath})")
+            + " | ${pkgs.jq}/bin/jq -s add | ${binYggdrasil} -normaliseconf -useconf"
+          else
+            "${binYggdrasil} -genconf") + " > /run/yggdrasil/yggdrasil.conf"}
+
+          # start yggdrasil
+          ${binYggdrasil} -useconffile /run/yggdrasil/yggdrasil.conf
+        '';
+
+        serviceConfig = {
+          ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+          Restart = "always";
+
+          DynamicUser = true;
+          StateDirectory = "yggdrasil";
+          RuntimeDirectory = "yggdrasil";
+          RuntimeDirectoryMode = "0750";
+          BindReadOnlyPaths = lib.optional cfg.persistentKeys keysPath;
+          LoadCredential =
+            mkIf configFileProvided "yggdrasil.conf:${cfg.configFile}";
+
+          AmbientCapabilities = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE";
+          CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE";
+          MemoryDenyWriteExecute = true;
+          ProtectControlGroups = true;
+          ProtectHome = "tmpfs";
+          ProtectKernelModules = true;
+          ProtectKernelTunables = true;
+          RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
+          RestrictNamespaces = true;
+          RestrictRealtime = true;
+          SystemCallArchitectures = "native";
+          SystemCallFilter = [ "@system-service" "~@privileged @keyring" ];
+        } // (if (cfg.group != null) then {
+          Group = cfg.group;
+        } else { });
+      };
 
-    networking.dhcpcd.denyInterfaces = cfg.denyDhcpcdInterfaces;
-    networking.firewall.allowedUDPPorts = mkIf cfg.openMulticastPort [ 9001 ];
+      networking.dhcpcd.denyInterfaces = cfg.denyDhcpcdInterfaces;
+      networking.firewall.allowedUDPPorts = mkIf cfg.openMulticastPort [ 9001 ];
 
-    # Make yggdrasilctl available on the command line.
-    environment.systemPackages = [ cfg.package ];
-  });
+      # Make yggdrasilctl available on the command line.
+      environment.systemPackages = [ cfg.package ];
+    }
+  );
   meta = {
     doc = ./yggdrasil.md;
     maintainers = with lib.maintainers; [ gazally ehmry ];
diff --git a/nixos/modules/services/search/opensearch.nix b/nixos/modules/services/search/opensearch.nix
new file mode 100644
index 0000000000000..9a50e79631380
--- /dev/null
+++ b/nixos/modules/services/search/opensearch.nix
@@ -0,0 +1,248 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.opensearch;
+
+  settingsFormat = pkgs.formats.yaml {};
+
+  configDir = cfg.dataDir + "/config";
+
+  usingDefaultDataDir = cfg.dataDir == "/var/lib/opensearch";
+  usingDefaultUserAndGroup = cfg.user == "opensearch" && cfg.group == "opensearch";
+
+  opensearchYml = settingsFormat.generate "opensearch.yml" cfg.settings;
+
+  loggingConfigFilename = "log4j2.properties";
+  loggingConfigFile = pkgs.writeTextFile {
+    name = loggingConfigFilename;
+    text = cfg.logging;
+  };
+in
+{
+
+  options.services.opensearch = {
+    enable = mkEnableOption (lib.mdDoc "OpenSearch");
+
+    package = lib.mkPackageOptionMD pkgs "OpenSearch" {
+      default = [ "opensearch" ];
+    };
+
+    settings = lib.mkOption {
+      type = lib.types.submodule {
+        freeformType = settingsFormat.type;
+
+        options."network.host" = lib.mkOption {
+          type = lib.types.str;
+          default = "127.0.0.1";
+          description = lib.mdDoc ''
+            Which port this service should listen on.
+          '';
+        };
+
+        options."cluster.name" = lib.mkOption {
+          type = lib.types.str;
+          default = "opensearch";
+          description = lib.mdDoc ''
+            The name of the cluster.
+          '';
+        };
+
+        options."discovery.type" = lib.mkOption {
+          type = lib.types.str;
+          default = "single-node";
+          description = lib.mdDoc ''
+            The type of discovery to use.
+          '';
+        };
+
+        options."http.port" = lib.mkOption {
+          type = lib.types.port;
+          default = 9200;
+          description = lib.mdDoc ''
+            The port to listen on for HTTP traffic.
+          '';
+        };
+
+        options."transport.port" = lib.mkOption {
+          type = lib.types.port;
+          default = 9300;
+          description = lib.mdDoc ''
+            The port to listen on for transport traffic.
+          '';
+        };
+      };
+
+      default = {};
+
+      description = lib.mdDoc ''
+        OpenSearch configuration.
+      '';
+    };
+
+    logging = lib.mkOption {
+      description = lib.mdDoc "opensearch logging configuration.";
+
+      default = ''
+        logger.action.name = org.opensearch.action
+        logger.action.level = info
+
+        appender.console.type = Console
+        appender.console.name = console
+        appender.console.layout.type = PatternLayout
+        appender.console.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] %marker%m%n
+
+        rootLogger.level = info
+        rootLogger.appenderRef.console.ref = console
+      '';
+      type = types.str;
+    };
+
+    dataDir = lib.mkOption {
+      type = lib.types.path;
+      default = "/var/lib/opensearch";
+      apply = converge (removeSuffix "/");
+      description = lib.mdDoc ''
+        Data directory for OpenSearch. If you change this, you need to
+        manually create the directory. You also need to create the
+        `opensearch` user and group, or change
+        [](#opt-services.opensearch.user) and
+        [](#opt-services.opensearch.group) to existing ones with
+        access to the directory.
+      '';
+    };
+
+    user = lib.mkOption {
+      type = lib.types.str;
+      default = "opensearch";
+      description = lib.mdDoc ''
+        The user OpenSearch runs as. Should be left at default unless
+        you have very specific needs.
+      '';
+    };
+
+    group = lib.mkOption {
+      type = lib.types.str;
+      default = "opensearch";
+      description = lib.mdDoc ''
+        The group OpenSearch runs as. Should be left at default unless
+        you have very specific needs.
+      '';
+    };
+
+    extraCmdLineOptions = lib.mkOption {
+      description = lib.mdDoc "Extra command line options for the OpenSearch launcher.";
+      default = [ ];
+      type = lib.types.listOf lib.types.str;
+    };
+
+    extraJavaOptions = lib.mkOption {
+      description = lib.mdDoc "Extra command line options for Java.";
+      default = [ ];
+      type = lib.types.listOf lib.types.str;
+      example = [ "-Djava.net.preferIPv4Stack=true" ];
+    };
+
+    restartIfChanged = lib.mkOption {
+      type = lib.types.bool;
+      description = lib.mdDoc ''
+        Automatically restart the service on config change.
+        This can be set to false to defer restarts on a server or cluster.
+        Please consider the security implications of inadvertently running an older version,
+        and the possibility of unexpected behavior caused by inconsistent versions across a cluster when disabling this option.
+      '';
+      default = true;
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.opensearch = {
+      description = "OpenSearch Daemon";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      path = [ pkgs.inetutils ];
+      inherit (cfg) restartIfChanged;
+      environment = {
+        OPENSEARCH_HOME = cfg.dataDir;
+        OPENSEARCH_JAVA_OPTS = toString cfg.extraJavaOptions;
+        OPENSEARCH_PATH_CONF = configDir;
+      };
+      serviceConfig = {
+        ExecStartPre =
+          let
+            startPreFullPrivileges = ''
+              set -o errexit -o pipefail -o nounset -o errtrace
+              shopt -s inherit_errexit
+            '' + (optionalString (!config.boot.isContainer) ''
+              # Only set vm.max_map_count if lower than ES required minimum
+              # This avoids conflict if configured via boot.kernel.sysctl
+              if [ $(${pkgs.procps}/bin/sysctl -n vm.max_map_count) -lt 262144 ]; then
+                ${pkgs.procps}/bin/sysctl -w vm.max_map_count=262144
+              fi
+            '');
+            startPreUnprivileged = ''
+              set -o errexit -o pipefail -o nounset -o errtrace
+              shopt -s inherit_errexit
+
+              # Install plugins
+              ln -sfT ${cfg.package}/lib ${cfg.dataDir}/lib
+              ln -sfT ${cfg.package}/modules ${cfg.dataDir}/modules
+
+              # opensearch needs to create the opensearch.keystore in the config directory
+              # so this directory needs to be writable.
+              mkdir -p ${configDir}
+              chmod 0700 ${configDir}
+
+              # Note that we copy config files from the nix store instead of symbolically linking them
+              # because otherwise X-Pack Security will raise the following exception:
+              # java.security.AccessControlException:
+              # access denied ("java.io.FilePermission" "/var/lib/opensearch/config/opensearch.yml" "read")
+
+              rm -f ${configDir}/opensearch.yml
+              cp ${opensearchYml} ${configDir}/opensearch.yml
+
+              # Make sure the logging configuration for old OpenSearch versions is removed:
+              rm -f "${configDir}/logging.yml"
+              rm -f ${configDir}/${loggingConfigFilename}
+              cp ${loggingConfigFile} ${configDir}/${loggingConfigFilename}
+              mkdir -p ${configDir}/scripts
+
+              rm -f ${configDir}/jvm.options
+              cp ${cfg.package}/config/jvm.options ${configDir}/jvm.options
+
+              # redirect jvm logs to the data directory
+              mkdir -p ${cfg.dataDir}/logs
+              chmod 0700 ${cfg.dataDir}/logs
+              sed -e '#logs/gc.log#${cfg.dataDir}/logs/gc.log#' -i ${configDir}/jvm.options
+            '';
+          in [
+            "+${pkgs.writeShellScript "opensearch-start-pre-full-privileges" startPreFullPrivileges}"
+            "${pkgs.writeShellScript "opensearch-start-pre-unprivileged" startPreUnprivileged}"
+          ];
+        ExecStartPost = pkgs.writeShellScript "opensearch-start-post" ''
+          set -o errexit -o pipefail -o nounset -o errtrace
+          shopt -s inherit_errexit
+
+          # Make sure opensearch is up and running before dependents
+          # are started
+          while ! ${pkgs.curl}/bin/curl -sS -f http://${cfg.settings."network.host"}:${toString cfg.settings."http.port"} 2>/dev/null; do
+            sleep 1
+          done
+        '';
+        ExecStart = "${cfg.package}/bin/opensearch ${toString cfg.extraCmdLineOptions}";
+        User = cfg.user;
+        Group = cfg.group;
+        LimitNOFILE = "1024000";
+        Restart = "always";
+        TimeoutStartSec = "infinity";
+        DynamicUser = usingDefaultUserAndGroup && usingDefaultDataDir;
+      } // (optionalAttrs (usingDefaultDataDir) {
+        StateDirectory = "opensearch";
+        StateDirectoryMode = "0700";
+      });
+    };
+
+    environment.systemPackages = [ cfg.package ];
+  };
+}
diff --git a/nixos/modules/services/search/qdrant.nix b/nixos/modules/services/search/qdrant.nix
new file mode 100644
index 0000000000000..a843c44dbb5f9
--- /dev/null
+++ b/nixos/modules/services/search/qdrant.nix
@@ -0,0 +1,128 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+
+  cfg = config.services.qdrant;
+
+  settingsFormat = pkgs.formats.yaml { };
+  configFile = settingsFormat.generate "config.yaml" cfg.settings;
+in {
+
+  options = {
+    services.qdrant = {
+      enable = mkEnableOption (lib.mdDoc "Vector Search Engine for the next generation of AI applications");
+
+      settings = mkOption {
+        description = lib.mdDoc ''
+          Configuration for Qdrant
+          Refer to <https://github.com/qdrant/qdrant/blob/master/config/config.yaml> for details on supported values.
+        '';
+
+        type = settingsFormat.type;
+
+        example = {
+          storage = {
+            storage_path = "/var/lib/qdrant/storage";
+            snapshots_path = "/var/lib/qdrant/snapshots";
+          };
+          hsnw_index = {
+            on_disk = true;
+          };
+          service = {
+            host = "127.0.0.1";
+            http_port = 6333;
+            grpc_port = 6334;
+          };
+          telemetry_disabled = true;
+        };
+
+        defaultText = literalExpression ''
+          {
+            storage = {
+              storage_path = "/var/lib/qdrant/storage";
+              snapshots_path = "/var/lib/qdrant/snapshots";
+            };
+            hsnw_index = {
+              on_disk = true;
+            };
+            service = {
+              host = "127.0.0.1";
+              http_port = 6333;
+              grpc_port = 6334;
+            };
+            telemetry_disabled = true;
+          }
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.qdrant.settings = {
+      storage.storage_path = mkDefault "/var/lib/qdrant/storage";
+      storage.snapshots_path = mkDefault "/var/lib/qdrant/snapshots";
+      # The following default values are the same as in the default config,
+      # they are just written here for convenience.
+      storage.on_disk_payload = mkDefault true;
+      storage.wal.wal_capacity_mb = mkDefault 32;
+      storage.wal.wal_segments_ahead = mkDefault 0;
+      storage.performance.max_search_threads = mkDefault 0;
+      storage.performance.max_optimization_threads = mkDefault 1;
+      storage.optimizers.deleted_threshold = mkDefault 0.2;
+      storage.optimizers.vacuum_min_vector_number = mkDefault 1000;
+      storage.optimizers.default_segment_number = mkDefault 0;
+      storage.optimizers.max_segment_size_kb = mkDefault null;
+      storage.optimizers.memmap_threshold_kb = mkDefault null;
+      storage.optimizers.indexing_threshold_kb = mkDefault 20000;
+      storage.optimizers.flush_interval_sec = mkDefault 5;
+      storage.optimizers.max_optimization_threads = mkDefault 1;
+      storage.hnsw_index.m = mkDefault 16;
+      storage.hnsw_index.ef_construct = mkDefault 100;
+      storage.hnsw_index.full_scan_threshold_kb = mkDefault 10000;
+      storage.hnsw_index.max_indexing_threads = mkDefault 0;
+      storage.hnsw_index.on_disk = mkDefault false;
+      storage.hnsw_index.payload_m = mkDefault null;
+      service.max_request_size_mb = mkDefault 32;
+      service.max_workers = mkDefault 0;
+      service.http_port = mkDefault 6333;
+      service.grpc_port = mkDefault 6334;
+      service.enable_cors = mkDefault true;
+      cluster.enabled = mkDefault false;
+      # the following have been altered for security
+      service.host = mkDefault "127.0.0.1";
+      telemetry_disabled = mkDefault true;
+    };
+
+    systemd.services.qdrant = {
+      description = "Vector Search Engine for the next generation of AI applications";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        ExecStart = "${pkgs.qdrant}/bin/qdrant --config-path ${configFile}";
+        DynamicUser = true;
+        Restart = "on-failure";
+        StateDirectory = "qdrant";
+        CapabilityBoundingSet = "";
+        NoNewPrivileges = true;
+        PrivateTmp = true;
+        ProtectHome = true;
+        ProtectClock = true;
+        ProtectProc = "noaccess";
+        ProcSubset = "pid";
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectControlGroups = true;
+        ProtectHostname = true;
+        RestrictSUIDSGID = true;
+        RestrictRealtime = true;
+        RestrictNamespaces = true;
+        LockPersonality = true;
+        RemoveIPC = true;
+        SystemCallFilter = [ "@system-service" "~@privileged" ];
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/search/solr.nix b/nixos/modules/services/search/solr.nix
deleted file mode 100644
index 05592e9fa247d..0000000000000
--- a/nixos/modules/services/search/solr.nix
+++ /dev/null
@@ -1,110 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  cfg = config.services.solr;
-
-in
-
-{
-  options = {
-    services.solr = {
-      enable = mkEnableOption (lib.mdDoc "Solr");
-
-      package = mkOption {
-        type = types.package;
-        default = pkgs.solr;
-        defaultText = literalExpression "pkgs.solr";
-        description = lib.mdDoc "Which Solr package to use.";
-      };
-
-      port = mkOption {
-        type = types.port;
-        default = 8983;
-        description = lib.mdDoc "Port on which Solr is ran.";
-      };
-
-      stateDir = mkOption {
-        type = types.path;
-        default = "/var/lib/solr";
-        description = lib.mdDoc "The solr home directory containing config, data, and logging files.";
-      };
-
-      extraJavaOptions = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        description = lib.mdDoc "Extra command line options given to the java process running Solr.";
-      };
-
-      user = mkOption {
-        type = types.str;
-        default = "solr";
-        description = lib.mdDoc "User under which Solr is ran.";
-      };
-
-      group = mkOption {
-        type = types.str;
-        default = "solr";
-        description = lib.mdDoc "Group under which Solr is ran.";
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-
-    environment.systemPackages = [ cfg.package ];
-
-    systemd.services.solr = {
-      after = [ "network.target" "remote-fs.target" "nss-lookup.target" "systemd-journald-dev-log.socket" ];
-      wantedBy = [ "multi-user.target" ];
-
-      environment = {
-        SOLR_HOME = "${cfg.stateDir}/data";
-        LOG4J_PROPS = "${cfg.stateDir}/log4j2.xml";
-        SOLR_LOGS_DIR = "${cfg.stateDir}/logs";
-        SOLR_PORT = "${toString cfg.port}";
-      };
-      path = with pkgs; [
-        gawk
-        procps
-      ];
-      preStart = ''
-        mkdir -p "${cfg.stateDir}/data";
-        mkdir -p "${cfg.stateDir}/logs";
-
-        if ! test -e "${cfg.stateDir}/data/solr.xml"; then
-          install -D -m0640 ${cfg.package}/server/solr/solr.xml "${cfg.stateDir}/data/solr.xml"
-          install -D -m0640 ${cfg.package}/server/solr/zoo.cfg "${cfg.stateDir}/data/zoo.cfg"
-        fi
-
-        if ! test -e "${cfg.stateDir}/log4j2.xml"; then
-          install -D -m0640 ${cfg.package}/server/resources/log4j2.xml "${cfg.stateDir}/log4j2.xml"
-        fi
-      '';
-
-      serviceConfig = {
-        User = cfg.user;
-        Group = cfg.group;
-        ExecStart="${cfg.package}/bin/solr start -f -a \"${concatStringsSep " " cfg.extraJavaOptions}\"";
-        ExecStop="${cfg.package}/bin/solr stop";
-      };
-    };
-
-    users.users = optionalAttrs (cfg.user == "solr") {
-      solr = {
-        group = cfg.group;
-        home = cfg.stateDir;
-        createHome = true;
-        uid = config.ids.uids.solr;
-      };
-    };
-
-    users.groups = optionalAttrs (cfg.group == "solr") {
-      solr.gid = config.ids.gids.solr;
-    };
-
-  };
-
-}
diff --git a/nixos/modules/services/system/cachix-watch-store.nix b/nixos/modules/services/system/cachix-watch-store.nix
index ec73c0bcdcfe5..85e9509bcc82d 100644
--- a/nixos/modules/services/system/cachix-watch-store.nix
+++ b/nixos/modules/services/system/cachix-watch-store.nix
@@ -25,7 +25,7 @@ in
 
     compressionLevel = mkOption {
       type = types.nullOr types.int;
-      description = lib.mdDoc "The compression level for XZ compression (between 0 and 9)";
+      description = lib.mdDoc "The compression level for ZSTD compression (between 0 and 16)";
       default = null;
     };
 
diff --git a/nixos/modules/services/system/self-deploy.nix b/nixos/modules/services/system/self-deploy.nix
index 16a793a42253c..5f9ee06124cb4 100644
--- a/nixos/modules/services/system/self-deploy.nix
+++ b/nixos/modules/services/system/self-deploy.nix
@@ -132,7 +132,7 @@ in
 
       requires = lib.mkIf (!(isPathType cfg.repository)) [ "network-online.target" ];
 
-      environment.GIT_SSH_COMMAND = lib.mkIf (!(isNull cfg.sshKeyFile))
+      environment.GIT_SSH_COMMAND = lib.mkIf (cfg.sshKeyFile != null)
         "${pkgs.openssh}/bin/ssh -i ${lib.escapeShellArg cfg.sshKeyFile}";
 
       restartIfChanged = false;
diff --git a/nixos/modules/services/web-apps/akkoma.md b/nixos/modules/services/web-apps/akkoma.md
index fc849be0c8726..83dd1a8b35f23 100644
--- a/nixos/modules/services/web-apps/akkoma.md
+++ b/nixos/modules/services/web-apps/akkoma.md
@@ -152,7 +152,7 @@ services.akkoma.config.":pleroma".":media_preview_proxy" = {
 
 ## Frontend management {#modules-services-akkoma-frontend-management}
 
-Akkoma will be deployed with the `pleroma-fe` and `admin-fe` frontends by default. These can be
+Akkoma will be deployed with the `akkoma-fe` and `admin-fe` frontends by default. These can be
 modified by setting
 [{option}`services.akkoma.frontends`](options.html#opt-services.akkoma.frontends).
 
@@ -160,7 +160,7 @@ The following example overrides the primary frontend’s default configuration u
 derivation.
 
 ```nix
-services.akkoma.frontends.primary.package = pkgs.runCommand "pleroma-fe" {
+services.akkoma.frontends.primary.package = pkgs.runCommand "akkoma-fe" {
   config = builtins.toJSON {
     expertLevel = 1;
     collapseMessageWithSubject = false;
@@ -177,10 +177,10 @@ services.akkoma.frontends.primary.package = pkgs.runCommand "pleroma-fe" {
   passAsFile = [ "config" ];
 } ''
   mkdir $out
-  lndir ${pkgs.akkoma-frontends.pleroma-fe} $out
+  lndir ${pkgs.akkoma-frontends.akkoma-fe} $out
 
   rm $out/static/config.json
-  jq -s add ${pkgs.akkoma-frontends.pleroma-fe}/static/config.json ${config} \
+  jq -s add ${pkgs.akkoma-frontends.akkoma-fe}/static/config.json ${config} \
     >$out/static/config.json
 '';
 ```
@@ -318,8 +318,8 @@ to make packages available in the chroot.
 {option}`services.systemd.akkoma.serviceConfig.BindPaths` and
 {option}`services.systemd.akkoma.serviceConfig.BindReadOnlyPaths` permit access to outside paths
 through bind mounts. Refer to
-[{manpage}`systemd.exec(5)`](https://www.freedesktop.org/software/systemd/man/systemd.exec.html#BindPaths=)
-for details.
+[`BindPaths=`](https://www.freedesktop.org/software/systemd/man/systemd.exec.html#BindPaths=)
+of {manpage}`systemd.exec(5)` for details.
 
 ### Distributed deployment {#modules-services-akkoma-distributed-deployment}
 
diff --git a/nixos/modules/services/web-apps/akkoma.nix b/nixos/modules/services/web-apps/akkoma.nix
index fc482ff32debc..8d17752586128 100644
--- a/nixos/modules/services/web-apps/akkoma.nix
+++ b/nixos/modules/services/web-apps/akkoma.nix
@@ -51,13 +51,13 @@ let
       package = mkOption {
         type = types.package;
         description = mdDoc "Akkoma frontend package.";
-        example = literalExpression "pkgs.akkoma-frontends.pleroma-fe";
+        example = literalExpression "pkgs.akkoma-frontends.akkoma-fe";
       };
 
       name = mkOption {
         type = types.nonEmptyStr;
         description = mdDoc "Akkoma frontend name.";
-        example = "pleroma-fe";
+        example = "akkoma-fe";
       };
 
       ref = mkOption {
@@ -476,8 +476,8 @@ in {
         type = with types; attrsOf (submodule frontend);
         default = {
           primary = {
-            package = pkgs.akkoma-frontends.pleroma-fe;
-            name = "pleroma-fe";
+            package = pkgs.akkoma-frontends.akkoma-fe;
+            name = "akkoma-fe";
             ref = "stable";
           };
           admin = {
@@ -489,8 +489,8 @@ in {
         defaultText = literalExpression ''
           {
             primary = {
-              package = pkgs.akkoma-frontends.pleroma-fe;
-              name = "pleroma-fe";
+              package = pkgs.akkoma-frontends.akkoma-fe;
+              name = "akkoma-fe";
               ref = "stable";
             };
             admin = {
diff --git a/nixos/modules/services/web-apps/alps.nix b/nixos/modules/services/web-apps/alps.nix
index 1a58df2da1d29..05fb676102df4 100644
--- a/nixos/modules/services/web-apps/alps.nix
+++ b/nixos/modules/services/web-apps/alps.nix
@@ -84,7 +84,7 @@ in {
         "-addr" "${cfg.bindIP}:${toString cfg.port}"
         "-theme" "${cfg.theme}"
         "imaps://${cfg.imaps.host}:${toString cfg.imaps.port}"
-        "smpts://${cfg.smtps.host}:${toString cfg.smtps.port}"
+        "smtps://${cfg.smtps.host}:${toString cfg.smtps.port}"
       ];
     };
   };
diff --git a/nixos/modules/services/web-apps/baget.nix b/nixos/modules/services/web-apps/baget.nix
deleted file mode 100644
index e4d5a1faddb2e..0000000000000
--- a/nixos/modules/services/web-apps/baget.nix
+++ /dev/null
@@ -1,170 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  cfg = config.services.baget;
-
-  defaultConfig = {
-    "PackageDeletionBehavior" = "Unlist";
-    "AllowPackageOverwrites" = false;
-
-    "Database" = {
-      "Type" = "Sqlite";
-      "ConnectionString" = "Data Source=baget.db";
-    };
-
-    "Storage" = {
-      "Type" = "FileSystem";
-      "Path" = "";
-    };
-
-    "Search" = {
-      "Type" = "Database";
-    };
-
-    "Mirror" = {
-      "Enabled" = false;
-      "PackageSource" = "https://api.nuget.org/v3/index.json";
-    };
-
-    "Logging" = {
-      "IncludeScopes" = false;
-      "Debug" = {
-        "LogLevel" = {
-          "Default" = "Warning";
-        };
-      };
-      "Console" = {
-        "LogLevel" = {
-          "Microsoft.Hosting.Lifetime" = "Information";
-          "Default" = "Warning";
-        };
-      };
-    };
-  };
-
-  configAttrs = recursiveUpdate defaultConfig cfg.extraConfig;
-
-  configFormat = pkgs.formats.json {};
-  configFile = configFormat.generate "appsettings.json" configAttrs;
-
-in
-{
-  options.services.baget = {
-    enable = mkEnableOption (lib.mdDoc "BaGet NuGet-compatible server");
-
-    apiKeyFile = mkOption {
-      type = types.path;
-      example = "/root/baget.key";
-      description = lib.mdDoc ''
-        Private API key for BaGet.
-      '';
-    };
-
-    extraConfig = mkOption {
-      type = configFormat.type;
-      default = {};
-      example = {
-        "Database" = {
-          "Type" = "PostgreSql";
-          "ConnectionString" = "Server=/run/postgresql;Port=5432;";
-        };
-      };
-      defaultText = literalExpression ''
-        {
-          "PackageDeletionBehavior" = "Unlist";
-          "AllowPackageOverwrites" = false;
-
-          "Database" = {
-            "Type" = "Sqlite";
-            "ConnectionString" = "Data Source=baget.db";
-          };
-
-          "Storage" = {
-            "Type" = "FileSystem";
-            "Path" = "";
-          };
-
-          "Search" = {
-            "Type" = "Database";
-          };
-
-          "Mirror" = {
-            "Enabled" = false;
-            "PackageSource" = "https://api.nuget.org/v3/index.json";
-          };
-
-          "Logging" = {
-            "IncludeScopes" = false;
-            "Debug" = {
-              "LogLevel" = {
-                "Default" = "Warning";
-              };
-            };
-            "Console" = {
-              "LogLevel" = {
-                "Microsoft.Hosting.Lifetime" = "Information";
-                "Default" = "Warning";
-              };
-            };
-          };
-        }
-      '';
-      description = lib.mdDoc ''
-        Extra configuration options for BaGet. Refer to <https://loic-sharma.github.io/BaGet/configuration/> for details.
-        Default value is merged with values from here.
-      '';
-    };
-  };
-
-  # implementation
-
-  config = mkIf cfg.enable {
-
-    systemd.services.baget = {
-      description = "BaGet server";
-      wantedBy = [ "multi-user.target" ];
-      wants = [ "network-online.target" ];
-      after = [ "network.target" "network-online.target" ];
-      path = [ pkgs.jq ];
-      serviceConfig = {
-        WorkingDirectory = "/var/lib/baget";
-        DynamicUser = true;
-        StateDirectory = "baget";
-        StateDirectoryMode = "0700";
-        LoadCredential = "api_key:${cfg.apiKeyFile}";
-
-        CapabilityBoundingSet = "";
-        NoNewPrivileges = true;
-        PrivateDevices = true;
-        PrivateTmp = true;
-        PrivateUsers = true;
-        PrivateMounts = true;
-        ProtectHome = true;
-        ProtectClock = true;
-        ProtectProc = "noaccess";
-        ProcSubset = "pid";
-        ProtectKernelLogs = true;
-        ProtectKernelModules = true;
-        ProtectKernelTunables = true;
-        ProtectControlGroups = true;
-        ProtectHostname = true;
-        RestrictSUIDSGID = true;
-        RestrictRealtime = true;
-        RestrictNamespaces = true;
-        LockPersonality = true;
-        RemoveIPC = true;
-        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
-        SystemCallFilter = [ "@system-service" "~@privileged" ];
-      };
-      script = ''
-        jq --slurpfile apiKeys <(jq -R . "$CREDENTIALS_DIRECTORY/api_key") '.ApiKey = $apiKeys[0]' ${configFile} > appsettings.json
-        ln -snf ${pkgs.baget}/lib/BaGet/wwwroot wwwroot
-        exec ${pkgs.baget}/bin/BaGet
-      '';
-    };
-
-  };
-}
diff --git a/nixos/modules/services/web-apps/coder.nix b/nixos/modules/services/web-apps/coder.nix
new file mode 100644
index 0000000000000..469a29bc3aa8c
--- /dev/null
+++ b/nixos/modules/services/web-apps/coder.nix
@@ -0,0 +1,217 @@
+{ config, lib, options, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.coder;
+  name = "coder";
+in {
+  options = {
+    services.coder = {
+      enable = mkEnableOption (lib.mdDoc "Coder service");
+
+      user = mkOption {
+        type = types.str;
+        default = "coder";
+        description = lib.mdDoc ''
+          User under which the coder service runs.
+
+          ::: {.note}
+          If left as the default value this user will automatically be created
+          on system activation, otherwise it needs to be configured manually.
+          :::
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "coder";
+        description = lib.mdDoc ''
+          Group under which the coder service runs.
+
+          ::: {.note}
+          If left as the default value this group will automatically be created
+          on system activation, otherwise it needs to be configured manually.
+          :::
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.coder;
+        description = lib.mdDoc ''
+          Package to use for the service.
+        '';
+        defaultText = literalExpression "pkgs.coder";
+      };
+
+      homeDir = mkOption {
+        type = types.str;
+        description = lib.mdDoc ''
+          Home directory for coder user.
+        '';
+        default = "/var/lib/coder";
+      };
+
+      listenAddress = mkOption {
+        type = types.str;
+        description = lib.mdDoc ''
+          Listen address.
+        '';
+        default = "127.0.0.1:3000";
+      };
+
+      accessUrl = mkOption {
+        type = types.nullOr types.str;
+        description = lib.mdDoc ''
+          Access URL should be a external IP address or domain with DNS records pointing to Coder.
+        '';
+        default = null;
+        example = "https://coder.example.com";
+      };
+
+      wildcardAccessUrl = mkOption {
+        type = types.nullOr types.str;
+        description = lib.mdDoc ''
+          If you are providing TLS certificates directly to the Coder server, you must use a single certificate for the root and wildcard domains.
+        '';
+        default = null;
+        example = "*.coder.example.com";
+      };
+
+      database = {
+        createLocally = mkOption {
+          type = types.bool;
+          default = true;
+          description = lib.mdDoc ''
+            Create the database and database user locally.
+          '';
+        };
+
+        host = mkOption {
+          type = types.str;
+          default = "/run/postgresql";
+          description = lib.mdDoc ''
+            Hostname hosting the database.
+          '';
+        };
+
+        database = mkOption {
+          type = types.str;
+          default = "coder";
+          description = lib.mdDoc ''
+            Name of database.
+          '';
+        };
+
+        username = mkOption {
+          type = types.str;
+          default = "coder";
+          description = lib.mdDoc ''
+            Username for accessing the database.
+          '';
+        };
+
+        password = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = lib.mdDoc ''
+            Password for accessing the database.
+          '';
+        };
+
+        sslmode = mkOption {
+          type = types.nullOr types.str;
+          default = "disable";
+          description = lib.mdDoc ''
+            Password for accessing the database.
+          '';
+        };
+      };
+
+      tlsCert = mkOption {
+        type = types.nullOr types.path;
+        description = lib.mdDoc ''
+          The path to the TLS certificate.
+        '';
+        default = null;
+      };
+
+      tlsKey = mkOption {
+        type = types.nullOr types.path;
+        description = lib.mdDoc ''
+          The path to the TLS key.
+        '';
+        default = null;
+      };
+    };
+  };
+
+  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";
+      }
+    ];
+
+    systemd.services.coder = {
+      description = "Coder - Self-hosted developer workspaces on your infra";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      environment = {
+        CODER_ACCESS_URL = cfg.accessUrl;
+        CODER_WILDCARD_ACCESS_URL = cfg.wildcardAccessUrl;
+        CODER_PG_CONNECTION_URL = "user=${cfg.database.username} ${optionalString (cfg.database.password != null) "password=${cfg.database.password}"} database=${cfg.database.database} host=${cfg.database.host} ${optionalString (cfg.database.sslmode != null) "sslmode=${cfg.database.sslmode}"}";
+        CODER_ADDRESS = cfg.listenAddress;
+        CODER_TLS_ENABLE = optionalString (cfg.tlsCert != null) "1";
+        CODER_TLS_CERT_FILE = cfg.tlsCert;
+        CODER_TLS_KEY_FILE = cfg.tlsKey;
+      };
+
+      serviceConfig = {
+        ProtectSystem = "full";
+        PrivateTmp = "yes";
+        PrivateDevices = "yes";
+        SecureBits = "keep-caps";
+        AmbientCapabilities = "CAP_IPC_LOCK CAP_NET_BIND_SERVICE";
+        CacheDirectory = "coder";
+        CapabilityBoundingSet = "CAP_SYSLOG CAP_IPC_LOCK CAP_NET_BIND_SERVICE";
+        KillSignal = "SIGINT";
+        KillMode = "mixed";
+        NoNewPrivileges = "yes";
+        Restart = "on-failure";
+        ExecStart = "${cfg.package}/bin/coder server";
+        User = cfg.user;
+        Group = cfg.group;
+      };
+    };
+
+    services.postgresql = lib.mkIf cfg.database.createLocally {
+      enable = true;
+      ensureDatabases = [
+        cfg.database.database
+      ];
+      ensureUsers = [{
+        name = cfg.database.username;
+        ensurePermissions = {
+          "DATABASE \"${cfg.database.database}\"" = "ALL PRIVILEGES";
+        };
+        }
+      ];
+    };
+
+    users.groups = optionalAttrs (cfg.group == name) {
+      "${cfg.group}" = {};
+    };
+    users.users = optionalAttrs (cfg.user == name) {
+      ${name} = {
+        description = "Coder service user";
+        group = cfg.group;
+        home = cfg.homeDir;
+        createHome = true;
+        isSystemUser = true;
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/web-apps/discourse.nix b/nixos/modules/services/web-apps/discourse.nix
index 5565a4f45d1e6..151fb812ddea6 100644
--- a/nixos/modules/services/web-apps/discourse.nix
+++ b/nixos/modules/services/web-apps/discourse.nix
@@ -615,6 +615,7 @@ in
       s3_endpoint = null;
       s3_http_continue_timeout = null;
       s3_install_cors_rule = null;
+      s3_asset_cdn_url = null;
 
       max_user_api_reqs_per_minute = 20;
       max_user_api_reqs_per_day = 2880;
@@ -647,6 +648,9 @@ in
       multisite_config_path = "config/multisite.yml";
       enable_long_polling = null;
       long_polling_interval = null;
+      preload_link_header = false;
+      redirect_avatar_requests = false;
+      pg_force_readonly_mode = false;
     };
 
     services.redis.servers.discourse =
@@ -1011,6 +1015,7 @@ in
         notification_email = cfg.mail.notificationEmailAddress;
         contact_email = cfg.mail.contactEmailAddress;
       };
+      security.force_https = tlsEnabled;
       email = {
         manual_polling_enabled = cfg.mail.incoming.enable;
         reply_by_email_enabled = cfg.mail.incoming.enable;
diff --git a/nixos/modules/services/web-apps/dolibarr.nix b/nixos/modules/services/web-apps/dolibarr.nix
index f262099354d2c..453229c130c22 100644
--- a/nixos/modules/services/web-apps/dolibarr.nix
+++ b/nixos/modules/services/web-apps/dolibarr.nix
@@ -5,7 +5,7 @@ let
   package = pkgs.dolibarr.override { inherit (cfg) stateDir; };
 
   cfg = config.services.dolibarr;
-  vhostCfg = lib.optionalAttr (cfg.nginx != null) config.services.nginx.virtualHosts."${cfg.domain}";
+  vhostCfg = lib.optionalAttrs (cfg.nginx != null) config.services.nginx.virtualHosts."${cfg.domain}";
 
   mkConfigFile = filename: settings:
     let
@@ -16,7 +16,7 @@ let
         if (any (str: k == str) secretKeys) then v
         else if isString v then "'${v}'"
         else if isBool v then boolToString v
-        else if isNull v then "null"
+        else if v == null then "null"
         else toString v
       ;
     in
diff --git a/nixos/modules/services/web-apps/jitsi-meet.nix b/nixos/modules/services/web-apps/jitsi-meet.nix
index 28be3a3702eb6..3825b03c24496 100644
--- a/nixos/modules/services/web-apps/jitsi-meet.nix
+++ b/nixos/modules/services/web-apps/jitsi-meet.nix
@@ -411,11 +411,14 @@ in
       componentPasswordFile = "/var/lib/jitsi-meet/jicofo-component-secret";
       bridgeMuc = "jvbbrewery@internal.${cfg.hostName}";
       config = mkMerge [{
-        "org.jitsi.jicofo.ALWAYS_TRUST_MODE_ENABLED" = "true";
+        jicofo.xmpp.service.disable-certificate-verification = true;
+        jicofo.xmpp.client.disable-certificate-verification = true;
       #} (lib.mkIf cfg.jibri.enable {
        } (lib.mkIf (config.services.jibri.enable || cfg.jibri.enable) {
-        "org.jitsi.jicofo.jibri.BREWERY" = "JibriBrewery@internal.${cfg.hostName}";
-        "org.jitsi.jicofo.jibri.PENDING_TIMEOUT" = "90";
+         jicofo.jibri = {
+           brewery-jid = "JibriBrewery@internal.${cfg.hostName}";
+           pending-timeout = "90";
+         };
       })];
     };
 
diff --git a/nixos/modules/services/web-apps/limesurvey.nix b/nixos/modules/services/web-apps/limesurvey.nix
index dd51174c8b8e0..8e6b39cbdebcb 100644
--- a/nixos/modules/services/web-apps/limesurvey.nix
+++ b/nixos/modules/services/web-apps/limesurvey.nix
@@ -34,6 +34,24 @@ in
   options.services.limesurvey = {
     enable = mkEnableOption (lib.mdDoc "Limesurvey web application");
 
+    encryptionKey = mkOption {
+      type = types.str;
+      default = "E17687FC77CEE247F0E22BB3ECF27FDE8BEC310A892347EC13013ABA11AA7EB5";
+      description = lib.mdDoc ''
+        This is a 32-byte key used to encrypt variables in the database.
+        You _must_ change this from the default value.
+      '';
+    };
+
+    encryptionNonce = mkOption {
+      type = types.str;
+      default = "1ACC8555619929DB91310BE848025A427B0F364A884FFA77";
+      description = lib.mdDoc ''
+        This is a 24-byte nonce used to encrypt variables in the database.
+        You _must_ change this from the default value.
+      '';
+    };
+
     database = {
       type = mkOption {
         type = types.enum [ "mysql" "pgsql" "odbc" "mssql" ];
@@ -42,6 +60,12 @@ in
         description = lib.mdDoc "Database engine to use.";
       };
 
+      dbEngine = mkOption {
+        type = types.enum [ "MyISAM" "InnoDB" ];
+        default = "InnoDB";
+        description = lib.mdDoc "Database storage engine to use.";
+      };
+
       host = mkOption {
         type = types.str;
         default = "localhost";
@@ -180,6 +204,8 @@ in
       config = {
         tempdir = "${stateDir}/tmp";
         uploaddir = "${stateDir}/upload";
+        encryptionnonce = cfg.encryptionNonce;
+        encryptionsecretboxkey = cfg.encryptionKey;
         force_ssl = mkIf (cfg.virtualHost.addSSL || cfg.virtualHost.forceSSL || cfg.virtualHost.onlySSL) "on";
         config.defaultlang = "en";
       };
@@ -200,6 +226,8 @@ in
 
     services.phpfpm.pools.limesurvey = {
       inherit user group;
+      phpPackage = pkgs.php80;
+      phpEnv.DBENGINE = "${cfg.database.dbEngine}";
       phpEnv.LIMESURVEY_CONFIG = "${limesurveyConfig}";
       settings = {
         "listen.owner" = config.services.httpd.user;
@@ -256,11 +284,12 @@ in
       wantedBy = [ "multi-user.target" ];
       before = [ "phpfpm-limesurvey.service" ];
       after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
+      environment.DBENGINE = "${cfg.database.dbEngine}";
       environment.LIMESURVEY_CONFIG = limesurveyConfig;
       script = ''
         # update or install the database as required
-        ${pkgs.php}/bin/php ${pkg}/share/limesurvey/application/commands/console.php updatedb || \
-        ${pkgs.php}/bin/php ${pkg}/share/limesurvey/application/commands/console.php install admin password admin admin@example.com verbose
+        ${pkgs.php80}/bin/php ${pkg}/share/limesurvey/application/commands/console.php updatedb || \
+        ${pkgs.php80}/bin/php ${pkg}/share/limesurvey/application/commands/console.php install admin password admin admin@example.com verbose
       '';
       serviceConfig = {
         User = user;
diff --git a/nixos/modules/services/web-apps/nextcloud-notify_push.nix b/nixos/modules/services/web-apps/nextcloud-notify_push.nix
new file mode 100644
index 0000000000000..e36631b6093c5
--- /dev/null
+++ b/nixos/modules/services/web-apps/nextcloud-notify_push.nix
@@ -0,0 +1,96 @@
+{ config, options, lib, pkgs, ... }:
+
+let
+  cfg = config.services.nextcloud.notify_push;
+in
+{
+  options.services.nextcloud.notify_push = {
+    enable = lib.mkEnableOption (lib.mdDoc "Notify push");
+
+    package = lib.mkOption {
+      type = lib.types.package;
+      default = pkgs.nextcloud-notify_push;
+      defaultText = lib.literalMD "pkgs.nextcloud-notify_push";
+      description = lib.mdDoc "Which package to use for notify_push";
+    };
+
+    socketPath = lib.mkOption {
+      type = lib.types.str;
+      default = "/run/nextcloud-notify_push/sock";
+      description = lib.mdDoc "Socket path to use for notify_push";
+    };
+
+    logLevel = lib.mkOption {
+      type = lib.types.enum [ "error" "warn" "info" "debug" "trace" ];
+      default = "error";
+      description = lib.mdDoc "Log level";
+    };
+  } // (
+    lib.genAttrs [
+      "dbtype"
+      "dbname"
+      "dbuser"
+      "dbpassFile"
+      "dbhost"
+      "dbport"
+      "dbtableprefix"
+    ] (
+      opt: options.services.nextcloud.config.${opt} // {
+        default = config.services.nextcloud.config.${opt};
+        defaultText = "config.services.nextcloud.config.${opt}";
+      }
+    )
+  );
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.nextcloud-notify_push = let
+      nextcloudUrl = "http${lib.optionalString config.services.nextcloud.https "s"}://${config.services.nextcloud.hostName}";
+    in {
+      description = "Push daemon for Nextcloud clients";
+      documentation = [ "https://github.com/nextcloud/notify_push" ];
+      after = [ "phpfpm-nextcloud.service" ];
+      wantedBy = [ "multi-user.target" ];
+      environment = {
+        NEXTCLOUD_URL = nextcloudUrl;
+        SOCKET_PATH = cfg.socketPath;
+        DATABASE_PREFIX = cfg.dbtableprefix;
+        LOG = cfg.logLevel;
+      };
+      postStart = ''
+        ${config.services.nextcloud.occ}/bin/nextcloud-occ notify_push:setup ${nextcloudUrl}/push
+      '';
+      script = let
+        dbType = if cfg.dbtype == "pgsql" then "postgresql" else cfg.dbtype;
+        dbUser = lib.optionalString (cfg.dbuser != null) cfg.dbuser;
+        dbPass = lib.optionalString (cfg.dbpassFile != null) ":$DATABASE_PASSWORD";
+        isSocket = lib.hasPrefix "/" (toString cfg.dbhost);
+        dbHost = lib.optionalString (cfg.dbhost != null) (if
+          isSocket then
+            if dbType == "postgresql" then "?host=${cfg.dbhost}" else
+            if dbType == "mysql" then "?socket=${cfg.dbhost}" else throw "unsupported dbtype"
+          else
+            "@${cfg.dbhost}");
+        dbName = lib.optionalString (cfg.dbname != null) "/${cfg.dbname}";
+        dbUrl = "${dbType}://${dbUser}${dbPass}${lib.optionalString (!isSocket) dbHost}${dbName}${lib.optionalString isSocket dbHost}";
+      in lib.optionalString (dbPass != "") ''
+        export DATABASE_PASSWORD="$(<"${cfg.dbpassFile}")"
+      '' + ''
+        export DATABASE_URL="${dbUrl}"
+        ${cfg.package}/bin/notify_push --glob-config '${config.services.nextcloud.datadir}/config/config.php'
+      '';
+      serviceConfig = {
+        User = "nextcloud";
+        Group = "nextcloud";
+        RuntimeDirectory = [ "nextcloud-notify_push" ];
+        Restart = "on-failure";
+        RestartSec = "5s";
+      };
+    };
+
+    services.nginx.virtualHosts.${config.services.nextcloud.hostName}.locations."^~ /push/" = {
+      proxyPass = "http://unix:${cfg.socketPath}";
+      proxyWebsockets = true;
+      recommendedProxySettings = true;
+    };
+  };
+}
diff --git a/nixos/modules/services/web-apps/nextcloud.md b/nixos/modules/services/web-apps/nextcloud.md
index 014807f3da23c..7ef3cca281f9e 100644
--- a/nixos/modules/services/web-apps/nextcloud.md
+++ b/nixos/modules/services/web-apps/nextcloud.md
@@ -5,7 +5,7 @@ self-hostable cloud platform. The server setup can be automated using
 [services.nextcloud](#opt-services.nextcloud.enable). A
 desktop client is packaged at `pkgs.nextcloud-client`.
 
-The current default by NixOS is `nextcloud25` which is also the latest
+The current default by NixOS is `nextcloud26` which is also the latest
 major version available.
 
 ## Basic usage {#module-services-nextcloud-basic-usage}
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
index 50c2d68c77e48..438b3baf314d0 100644
--- a/nixos/modules/services/web-apps/nextcloud.nix
+++ b/nixos/modules/services/web-apps/nextcloud.nix
@@ -79,7 +79,7 @@ in {
       (which can be opened e.g. by running `nixos-help`).
     '')
     (mkRemovedOptionModule [ "services" "nextcloud" "disableImagemagick" ] ''
-      Use services.nextcloud.nginx.enableImagemagick instead.
+      Use services.nextcloud.enableImagemagick instead.
     '')
   ];
 
@@ -204,7 +204,7 @@ in {
     package = mkOption {
       type = types.package;
       description = lib.mdDoc "Which package to use for the Nextcloud instance.";
-      relatedPackages = [ "nextcloud24" "nextcloud25" ];
+      relatedPackages = [ "nextcloud24" "nextcloud25" "nextcloud26" ];
     };
     phpPackage = mkOption {
       type = types.package;
@@ -514,6 +514,27 @@ in {
               `http://hostname.domain/bucket` instead.
             '';
           };
+          sseCKeyFile = mkOption {
+            type = types.nullOr types.path;
+            default = null;
+            example = "/var/nextcloud-objectstore-s3-sse-c-key";
+            description = lib.mdDoc ''
+              If provided this is the full path to a file that contains the key
+              to enable [server-side encryption with customer-provided keys][1]
+              (SSE-C).
+
+              The file must contain a random 32-byte key encoded as a base64
+              string, e.g. generated with the command
+
+              ```
+              openssl rand 32 | base64
+              ```
+
+              Must be readable by user `nextcloud`.
+
+              [1]: https://docs.aws.amazon.com/AmazonS3/latest/userguide/ServerSideEncryptionCustomerKeys.html
+            '';
+          };
         };
       };
     };
@@ -652,7 +673,7 @@ in {
 
   config = mkIf cfg.enable (mkMerge [
     { warnings = let
-        latest = 25;
+        latest = 26;
         upgradeWarning = major: nixos:
           ''
             A legacy Nextcloud install (from before NixOS ${nixos}) may be installed.
@@ -667,20 +688,6 @@ in {
             `services.nextcloud.package`.
           '';
 
-        # FIXME(@Ma27) remove as soon as nextcloud properly supports
-        # mariadb >=10.6.
-        isUnsupportedMariadb =
-          # All currently supported Nextcloud versions are affected (https://github.com/nextcloud/server/issues/25436).
-          (versionOlder cfg.package.version "24")
-          # This module uses mysql
-          && (cfg.config.dbtype == "mysql")
-          # MySQL is managed via NixOS
-          && config.services.mysql.enable
-          # We're using MariaDB
-          && (getName config.services.mysql.package) == "mariadb-server"
-          # MariaDB is at least 10.6 and thus not supported
-          && (versionAtLeast (getVersion config.services.mysql.package) "10.6");
-
       in (optional (cfg.poolConfig != null) ''
           Using config.services.nextcloud.poolConfig is deprecated and will become unsupported in a future release.
           Please migrate your configuration to config.services.nextcloud.poolSettings.
@@ -688,6 +695,7 @@ in {
         ++ (optional (versionOlder cfg.package.version "23") (upgradeWarning 22 "22.05"))
         ++ (optional (versionOlder cfg.package.version "24") (upgradeWarning 23 "22.05"))
         ++ (optional (versionOlder cfg.package.version "25") (upgradeWarning 24 "22.11"))
+        ++ (optional (versionOlder cfg.package.version "26") (upgradeWarning 25 "23.05"))
         ++ (optional cfg.enableBrokenCiphersForSSE ''
           You're using PHP's openssl extension built against OpenSSL 1.1 for Nextcloud.
           This is only necessary if you're using Nextcloud's server-side encryption.
@@ -704,18 +712,7 @@ in {
           See <https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/encryption_configuration.html#disabling-encryption> on how to achieve this.
 
           For more context, here is the implementing pull request: https://github.com/NixOS/nixpkgs/pull/198470
-        '')
-        ++ (optional isUnsupportedMariadb ''
-            You seem to be using MariaDB at an unsupported version (i.e. at least 10.6)!
-            Please note that this isn't supported officially by Nextcloud. You can either
-
-            * Switch to `pkgs.mysql`
-            * Downgrade MariaDB to at least 10.5
-            * Work around Nextcloud's problems by specifying `innodb_read_only_compressed=0`
-
-            For further context, please read
-            https://help.nextcloud.com/t/update-to-next-cloud-21-0-2-has-get-an-error/117028/15
-          '');
+        '');
 
       services.nextcloud.package = with pkgs;
         mkDefault (
@@ -726,12 +723,13 @@ in {
               `pkgs.nextcloud`.
             ''
           else if versionOlder stateVersion "22.11" then nextcloud24
-          else nextcloud25
+          else if versionOlder stateVersion "23.05" then nextcloud25
+          else nextcloud26
         );
 
       services.nextcloud.phpPackage =
-        if versionOlder cfg.package.version "24" then pkgs.php80
-        else pkgs.php81;
+        if versionOlder cfg.package.version "26" then pkgs.php81
+        else pkgs.php82;
     }
 
     { assertions = [
@@ -773,6 +771,7 @@ in {
                 'use_ssl' => ${boolToString s3.useSsl},
                 ${optionalString (s3.region != null) "'region' => '${s3.region}',"}
                 'use_path_style' => ${boolToString s3.usePathStyle},
+                ${optionalString (s3.sseCKeyFile != null) "'sse_c_key' => nix_read_secret('${s3.sseCKeyFile}'),"}
               ],
             ]
           '';
@@ -958,6 +957,9 @@ in {
           '';
           serviceConfig.Type = "oneshot";
           serviceConfig.User = "nextcloud";
+          # On Nextcloud ≥ 26, it is not necessary to patch the database files to prevent
+          # an automatic creation of the database user.
+          environment.NC_setup_create_db_user = lib.mkIf (nextcloudGreaterOrEqualThan "26") "false";
         };
         nextcloud-cron = {
           after = [ "nextcloud-setup.service" ];
@@ -1009,14 +1011,6 @@ in {
           name = cfg.config.dbuser;
           ensurePermissions = { "${cfg.config.dbname}.*" = "ALL PRIVILEGES"; };
         }];
-        # FIXME(@Ma27) Nextcloud isn't compatible with mariadb 10.6,
-        # this is a workaround.
-        # See https://help.nextcloud.com/t/update-to-next-cloud-21-0-2-has-get-an-error/117028/22
-        settings = mkIf (versionOlder cfg.package.version "24") {
-          mysqld = {
-            innodb_read_only_compressed = 0;
-          };
-        };
         initialScript = pkgs.writeText "mysql-init" ''
           CREATE USER '${cfg.config.dbname}'@'localhost' IDENTIFIED BY '${builtins.readFile( cfg.config.dbpassFile )}';
           CREATE DATABASE IF NOT EXISTS ${cfg.config.dbname};
diff --git a/nixos/modules/services/web-apps/onlyoffice.nix b/nixos/modules/services/web-apps/onlyoffice.nix
index 6a8a28185b5b9..3494f2fa21f09 100644
--- a/nixos/modules/services/web-apps/onlyoffice.nix
+++ b/nixos/modules/services/web-apps/onlyoffice.nix
@@ -270,7 +270,7 @@ in
           wantedBy = [ "multi-user.target" ];
           serviceConfig = {
             ExecStart = "${cfg.package.fhs}/bin/onlyoffice-wrapper DocService/docservice /run/onlyoffice/config";
-            ExecStartPre = onlyoffice-prestart;
+            ExecStartPre = [ onlyoffice-prestart ];
             Group = "onlyoffice";
             Restart = "always";
             RuntimeDirectory = "onlyoffice";
diff --git a/nixos/modules/services/web-apps/writefreely.nix b/nixos/modules/services/web-apps/writefreely.nix
index dec00b46f335e..a7671aa717f43 100644
--- a/nixos/modules/services/web-apps/writefreely.nix
+++ b/nixos/modules/services/web-apps/writefreely.nix
@@ -10,12 +10,11 @@ let
   format = pkgs.formats.ini {
     mkKeyValue = key: value:
       let
-        value' = if builtins.isNull value then
-          ""
-        else if builtins.isBool value then
-          if value == true then "true" else "false"
-        else
-          toString value;
+        value' = lib.optionalString (value != null)
+          (if builtins.isBool value then
+            if value == true then "true" else "false"
+          else
+            toString value);
       in "${key} = ${value'}";
   };
 
diff --git a/nixos/modules/services/web-servers/minio.nix b/nixos/modules/services/web-servers/minio.nix
index 1a9eacb431b3c..21bec4f63a879 100644
--- a/nixos/modules/services/web-servers/minio.nix
+++ b/nixos/modules/services/web-servers/minio.nix
@@ -60,7 +60,7 @@ in
       '';
     };
 
-    rootCredentialsFile = mkOption  {
+    rootCredentialsFile = mkOption {
       type = types.nullOr types.path;
       default = null;
       description = lib.mdDoc ''
@@ -96,29 +96,62 @@ in
   config = mkIf cfg.enable {
     warnings = optional ((cfg.accessKey != "") || (cfg.secretKey != "")) "services.minio.`accessKey` and services.minio.`secretKey` are deprecated, please use services.minio.`rootCredentialsFile` instead.";
 
-    systemd.tmpfiles.rules = [
-      "d '${cfg.configDir}' - minio minio - -"
-    ] ++ (map (x:  "d '" + x + "' - minio minio - - ") cfg.dataDir);
-
-    systemd.services.minio = {
-      description = "Minio Object Storage";
-      after = [ "network-online.target" ];
-      wantedBy = [ "multi-user.target" ];
-      serviceConfig = {
-        ExecStart = "${cfg.package}/bin/minio server --json --address ${cfg.listenAddress} --console-address ${cfg.consoleAddress} --config-dir=${cfg.configDir} ${toString cfg.dataDir}";
-        Type = "simple";
-        User = "minio";
-        Group = "minio";
-        LimitNOFILE = 65536;
-        EnvironmentFile = if (cfg.rootCredentialsFile != null) then cfg.rootCredentialsFile
-                          else if ((cfg.accessKey != "") || (cfg.secretKey != "")) then (legacyCredentials cfg)
-                          else null;
+    systemd = lib.mkMerge [{
+      tmpfiles.rules = [
+        "d '${cfg.configDir}' - minio minio - -"
+      ] ++ (map (x: "d '" + x + "' - minio minio - - ") cfg.dataDir);
+
+      services.minio = {
+        description = "Minio Object Storage";
+        after = [ "network-online.target" ];
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          ExecStart = "${cfg.package}/bin/minio server --json --address ${cfg.listenAddress} --console-address ${cfg.consoleAddress} --config-dir=${cfg.configDir} ${toString cfg.dataDir}";
+          Type = "simple";
+          User = "minio";
+          Group = "minio";
+          LimitNOFILE = 65536;
+          EnvironmentFile =
+            if (cfg.rootCredentialsFile != null) then cfg.rootCredentialsFile
+            else if ((cfg.accessKey != "") || (cfg.secretKey != "")) then (legacyCredentials cfg)
+            else null;
+        };
+        environment = {
+          MINIO_REGION = "${cfg.region}";
+          MINIO_BROWSER = "${if cfg.browser then "on" else "off"}";
+        };
       };
-      environment = {
-        MINIO_REGION = "${cfg.region}";
-        MINIO_BROWSER = "${if cfg.browser then "on" else "off"}";
-      };
-    };
+    }
+
+      (lib.mkIf (cfg.rootCredentialsFile != null) {
+        # The service will fail if the credentials file is missing
+        services.minio.unitConfig.ConditionPathExists = cfg.rootCredentialsFile;
+
+        # The service will not restart if the credentials file has
+        # been changed. This can cause stale root credentials.
+        paths.minio-root-credentials = {
+          wantedBy = [ "multi-user.target" ];
+
+          pathConfig = {
+            PathChanged = [ cfg.rootCredentialsFile ];
+            Unit = "minio-restart.service";
+          };
+        };
+
+        services.minio-restart = {
+          description = "Restart MinIO";
+
+          script = ''
+            systemctl restart minio.service
+          '';
+
+          serviceConfig = {
+            Type = "oneshot";
+            Restart = "on-failure";
+            RestartSec = 5;
+          };
+        };
+      })];
 
     users.users.minio = {
       group = "minio";
diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix
index bc5b4be510985..905dd5bef1f71 100644
--- a/nixos/modules/services/web-servers/nginx/default.nix
+++ b/nixos/modules/services/web-servers/nginx/default.nix
@@ -112,10 +112,8 @@ let
   ''));
 
   commonHttpConfig = ''
-      # The mime type definitions included with nginx are very incomplete, so
-      # we use a list of mime types from the mailcap package, which is also
-      # used by most other Linux distributions by default.
-      include ${pkgs.mailcap}/etc/nginx/mime.types;
+      # Load mime types.
+      include ${cfg.defaultMimeTypes};
       # When recommendedOptimisation is disabled nginx fails to start because the mailmap mime.types database
       # contains 1026 entries and the default is only 1024. Setting to a higher number to remove the need to
       # overwrite it because nginx does not allow duplicated settings.
@@ -529,6 +527,18 @@ in
         '';
       };
 
+      defaultMimeTypes = mkOption {
+        type = types.path;
+        default = "${pkgs.mailcap}/etc/nginx/mime.types";
+        defaultText = literalExpression "$''{pkgs.mailcap}/etc/nginx/mime.types";
+        example = literalExpression "$''{pkgs.nginx}/conf/mime.types";
+        description = lib.mdDoc ''
+          Default MIME types for NGINX, as MIME types definitions from NGINX are very incomplete,
+          we use by default the ones bundled in the mailcap package, used by most of the other
+          Linux distributions.
+        '';
+      };
+
       package = mkOption {
         default = pkgs.nginxStable;
         defaultText = literalExpression "pkgs.nginxStable";
diff --git a/nixos/modules/services/x11/desktop-managers/pantheon.nix b/nixos/modules/services/x11/desktop-managers/pantheon.nix
index 7791a98965d1a..4d0296c8254c9 100644
--- a/nixos/modules/services/x11/desktop-managers/pantheon.nix
+++ b/nixos/modules/services/x11/desktop-managers/pantheon.nix
@@ -169,6 +169,9 @@ in
       };
       services.udev.packages = [
         pkgs.pantheon.gnome-settings-daemon
+        # Force enable KMS modifiers for devices that require them.
+        # https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1443
+        pkgs.pantheon.mutter
       ];
       systemd.packages = [
         pkgs.pantheon.gnome-settings-daemon
diff --git a/nixos/modules/services/x11/desktop-managers/phosh.nix b/nixos/modules/services/x11/desktop-managers/phosh.nix
index e889c0e34e7d8..3cfa6e044b73d 100644
--- a/nixos/modules/services/x11/desktop-managers/phosh.nix
+++ b/nixos/modules/services/x11/desktop-managers/phosh.nix
@@ -173,7 +173,7 @@ in
     systemd.services.phosh = {
       wantedBy = [ "graphical.target" ];
       serviceConfig = {
-        ExecStart = "${cfg.package}/bin/phosh";
+        ExecStart = "${cfg.package}/bin/phosh-session";
         User = cfg.user;
         Group = cfg.group;
         PAMName = "login";
diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix
index c150adcac26b2..73a864bb95fe8 100644
--- a/nixos/modules/services/x11/desktop-managers/plasma5.nix
+++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix
@@ -81,99 +81,90 @@ let
 in
 
 {
-  options.services.xserver.desktopManager.plasma5 = {
-    enable = mkOption {
-      type = types.bool;
-      default = false;
-      description = lib.mdDoc "Enable the Plasma 5 (KDE 5) desktop environment.";
-    };
-
-    phononBackend = mkOption {
-      type = types.enum [ "gstreamer" "vlc" ];
-      default = "vlc";
-      example = "gstreamer";
-      description = lib.mdDoc "Phonon audio backend to install.";
-    };
-
-    supportDDC = mkOption {
-      type = types.bool;
-      default = false;
-      description = lib.mdDoc ''
-        Support setting monitor brightness via DDC.
-
-        This is not needed for controlling brightness of the internal monitor
-        of a laptop and as it is considered experimental by upstream, it is
-        disabled by default.
-      '';
-    };
+  options = {
+    services.xserver.desktopManager.plasma5 = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc "Enable the Plasma 5 (KDE 5) desktop environment.";
+      };
 
-    useQtScaling = mkOption {
-      type = types.bool;
-      default = false;
-      description = lib.mdDoc "Enable HiDPI scaling in Qt.";
-    };
+      phononBackend = mkOption {
+        type = types.enum [ "gstreamer" "vlc" ];
+        default = "vlc";
+        example = "gstreamer";
+        description = lib.mdDoc "Phonon audio backend to install.";
+      };
 
-    runUsingSystemd = mkOption {
-      description = lib.mdDoc "Use systemd to manage the Plasma session";
-      type = types.bool;
-      default = true;
-    };
+      useQtScaling = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc "Enable HiDPI scaling in Qt.";
+      };
 
-    excludePackages = mkOption {
-      description = lib.mdDoc "List of default packages to exclude from the configuration";
-      type = types.listOf types.package;
-      default = [];
-      example = literalExpression "[ pkgs.plasma5Packages.oxygen ]";
-    };
+      runUsingSystemd = mkOption {
+        description = lib.mdDoc "Use systemd to manage the Plasma session";
+        type = types.bool;
+        default = true;
+      };
 
-    notoPackage = mkPackageOptionMD pkgs "Noto fonts" {
-      default = [ "noto-fonts" ];
-      example = "noto-fonts-lgc-plus";
-    };
+      notoPackage = mkPackageOptionMD pkgs "Noto fonts" {
+        default = [ "noto-fonts" ];
+        example = "noto-fonts-lgc-plus";
+      };
 
-    # Internally allows configuring kdeglobals globally
-    kdeglobals = mkOption {
-      internal = true;
-      default = {};
-      type = kdeConfigurationType;
-    };
+      # Internally allows configuring kdeglobals globally
+      kdeglobals = mkOption {
+        internal = true;
+        default = {};
+        type = kdeConfigurationType;
+      };
 
-    # Internally allows configuring kwin globally
-    kwinrc = mkOption {
-      internal = true;
-      default = {};
-      type = kdeConfigurationType;
-    };
+      # Internally allows configuring kwin globally
+      kwinrc = mkOption {
+        internal = true;
+        default = {};
+        type = kdeConfigurationType;
+      };
 
-    mobile.enable = mkOption {
-      type = types.bool;
-      default = false;
-      description = lib.mdDoc ''
-        Enable support for running the Plasma Mobile shell.
-      '';
-    };
+      mobile.enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Enable support for running the Plasma Mobile shell.
+        '';
+      };
 
-    mobile.installRecommendedSoftware = mkOption {
-      type = types.bool;
-      default = true;
-      description = lib.mdDoc ''
-        Installs software recommended for use with Plasma Mobile, but which
-        is not strictly required for Plasma Mobile to run.
-      '';
-    };
+      mobile.installRecommendedSoftware = mkOption {
+        type = types.bool;
+        default = true;
+        description = lib.mdDoc ''
+          Installs software recommended for use with Plasma Mobile, but which
+          is not strictly required for Plasma Mobile to run.
+        '';
+      };
 
-    bigscreen.enable = mkOption {
-      type = types.bool;
-      default = false;
-      description = lib.mdDoc ''
-        Enable support for running the Plasma Bigscreen session.
-      '';
+      bigscreen.enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Enable support for running the Plasma Bigscreen session.
+        '';
+      };
     };
+    environment.plasma5.excludePackages = mkOption {
+        description = lib.mdDoc "List of default packages to exclude from the configuration";
+        type = types.listOf types.package;
+        default = [];
+        example = literalExpression "[ pkgs.plasma5Packages.oxygen ]";
+      };
   };
 
   imports = [
     (mkRemovedOptionModule [ "services" "xserver" "desktopManager" "plasma5" "enableQt4Support" ] "Phonon no longer supports Qt 4.")
+    (mkRemovedOptionModule [ "services" "xserver" "desktopManager" "plasma5" "supportDDC" ] "DDC/CI is no longer supported upstream.")
     (mkRenamedOptionModule [ "services" "xserver" "desktopManager" "kde5" ] [ "services" "xserver" "desktopManager" "plasma5" ])
+    (mkRenamedOptionModule [ "services" "xserver" "desktopManager" "plasma5" "excludePackages" ] [ "environment" "plasma5" "excludePackages" ])
   ];
 
   config = mkMerge [
@@ -201,12 +192,6 @@ in
         };
       };
 
-      # DDC support
-      boot.kernelModules = lib.optional cfg.supportDDC "i2c_dev";
-      services.udev.extraRules = lib.optionalString cfg.supportDDC ''
-        KERNEL=="i2c-[0-9]*", TAG+="uaccess"
-      '';
-
       environment.systemPackages =
         with libsForQt5;
         with plasma5; with kdeGear; with kdeFrameworks;
@@ -301,7 +286,7 @@ in
           ];
         in
         requiredPackages
-        ++ utils.removePackagesByName optionalPackages cfg.excludePackages
+        ++ utils.removePackagesByName optionalPackages config.environment.plasma5.excludePackages
 
         # Phonon audio backend
         ++ lib.optional (cfg.phononBackend == "gstreamer") libsForQt5.phonon-backend-gstreamer
@@ -316,7 +301,8 @@ in
         ++ lib.optional config.services.colord.enable pkgs.colord-kde
         ++ lib.optional config.services.hardware.bolt.enable pkgs.plasma5Packages.plasma-thunderbolt
         ++ lib.optionals config.services.samba.enable [ kdenetwork-filesharing pkgs.samba ]
-        ++ lib.optional config.services.xserver.wacom.enable pkgs.wacomtablet;
+        ++ lib.optional config.services.xserver.wacom.enable pkgs.wacomtablet
+        ++ lib.optional config.services.flatpak.enable flatpak-kcm;
 
       # Extra services for D-Bus activation
       services.dbus.packages = [
@@ -378,12 +364,7 @@ in
 
       security.pam.services.kde = { allowNullPassword = true; };
 
-      # Doing these one by one seems silly, but we currently lack a better
-      # construct for handling common pam configs.
-      security.pam.services.gdm.enableKwallet = true;
-      security.pam.services.kdm.enableKwallet = true;
-      security.pam.services.lightdm.enableKwallet = true;
-      security.pam.services.sddm.enableKwallet = true;
+      security.pam.services.login.enableKwallet = true;
 
       systemd.user.services = {
         plasma-early-setup = mkIf cfg.runUsingSystemd {
@@ -452,13 +433,14 @@ in
             kio-extras
           ];
           optionalPackages = [
+            ark
             elisa
             gwenview
             okular
             khelpcenter
             print-manager
           ];
-      in requiredPackages ++ utils.removePackagesByName optionalPackages cfg.excludePackages;
+      in requiredPackages ++ utils.removePackagesByName optionalPackages config.environment.plasma5.excludePackages;
 
       systemd.user.services = {
         plasma-run-with-systemd = {
diff --git a/nixos/modules/services/x11/display-managers/sddm.nix b/nixos/modules/services/x11/display-managers/sddm.nix
index a3f03d7a19a6b..0ddeac0f10984 100644
--- a/nixos/modules/services/x11/display-managers/sddm.nix
+++ b/nixos/modules/services/x11/display-managers/sddm.nix
@@ -215,10 +215,12 @@ in
     };
 
     security.pam.services = {
-      sddm = {
-        allowNullPassword = true;
-        startSession = true;
-      };
+      sddm.text = ''
+        auth      substack      login
+        account   include       login
+        password  substack      login
+        session   include       login
+      '';
 
       sddm-greeter.text = ''
         auth     required       pam_succeed_if.so audit quiet_success user = sddm
diff --git a/nixos/modules/services/x11/window-managers/nimdow.nix b/nixos/modules/services/x11/window-managers/nimdow.nix
new file mode 100644
index 0000000000000..de3192876024b
--- /dev/null
+++ b/nixos/modules/services/x11/window-managers/nimdow.nix
@@ -0,0 +1,23 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.nimdow;
+in
+{
+  options = {
+    services.xserver.windowManager.nimdow.enable = mkEnableOption (lib.mdDoc "nimdow");
+  };
+
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton {
+      name = "nimdow";
+      start = ''
+        ${pkgs.nimdow}/bin/nimdow &
+        waitPID=$!
+      '';
+    };
+    environment.systemPackages = [ pkgs.nimdow ];
+  };
+}
diff --git a/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix
index 83a71dcf23e00..fcc18c9a26fd0 100644
--- a/nixos/modules/services/x11/xserver.nix
+++ b/nixos/modules/services/x11/xserver.nix
@@ -138,6 +138,26 @@ let
     concatMapStringsSep "\n" (line: prefix + line) (splitString "\n" str);
 
   indent = prefixStringLines "  ";
+
+  # A scalable variant of the X11 "core" cursor
+  #
+  # If not running a fancy desktop environment, the cursor is likely set to
+  # the default `cursor.pcf` bitmap font. This is 17px wide, so it's very
+  # small and almost invisible on 4K displays.
+  fontcursormisc_hidpi = pkgs.xorg.fontxfree86type1.overrideAttrs (old:
+    let
+      # The scaling constant is 230/96: the scalable `left_ptr` glyph at
+      # about 23 points is rendered as 17px, on a 96dpi display.
+      # Note: the XLFD font size is in decipoints.
+      size = 2.39583 * cfg.dpi;
+      sizeString = builtins.head (builtins.split "\\." (toString size));
+    in
+    {
+      postInstall = ''
+        alias='cursor -xfree86-cursor-medium-r-normal--0-${sizeString}-0-0-p-0-adobe-fontspecific'
+        echo "$alias" > $out/lib/X11/fonts/Type1/fonts.alias
+      '';
+    });
 in
 
 {
@@ -256,7 +276,7 @@ in
 
       videoDrivers = mkOption {
         type = types.listOf types.str;
-        default = [ "amdgpu" "radeon" "nouveau" "modesetting" "fbdev" ];
+        default = [ "modesetting" "fbdev" ];
         example = [
           "nvidia" "nvidiaLegacy390" "nvidiaLegacy340" "nvidiaLegacy304"
           "amdgpu-pro"
@@ -576,6 +596,15 @@ in
           Whether to terminate X upon server reset.
         '';
       };
+
+      upscaleDefaultCursor = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Upscale the default X cursor to be more visible on high-density displays.
+          Requires `config.services.xserver.dpi` to be set.
+        '';
+      };
     };
 
   };
@@ -592,7 +621,8 @@ in
                     || dmConf.sddm.enable
                     || dmConf.xpra.enable
                     || dmConf.sx.enable
-                    || dmConf.startx.enable);
+                    || dmConf.startx.enable
+                    || config.services.greetd.enable);
       in mkIf (default) (mkDefault true);
 
     # so that the service won't be enabled when only startx is used
@@ -626,6 +656,10 @@ in
                 + "${toString (length primaryHeads)} heads set to primary: "
                 + concatMapStringsSep ", " (x: x.output) primaryHeads;
       })
+      {
+        assertion = cfg.upscaleDefaultCursor -> cfg.dpi != null;
+        message = "Specify `config.services.xserver.dpi` to upscale the default cursor.";
+      }
     ];
 
     environment.etc =
@@ -850,6 +884,10 @@ in
       '';
 
     fonts.enableDefaultFonts = mkDefault true;
+    fonts.fonts = [
+      (if cfg.upscaleDefaultCursor then fontcursormisc_hidpi else pkgs.xorg.fontcursormisc)
+      pkgs.xorg.fontmiscmisc
+    ];
 
   };
 
diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix
index 240e209591752..896f8c0a69355 100644
--- a/nixos/modules/system/activation/top-level.nix
+++ b/nixos/modules/system/activation/top-level.nix
@@ -130,6 +130,13 @@ let
       pkgs.replaceDependency { inherit oldDependency newDependency drv; }
     ) baseSystemAssertWarn config.system.replaceRuntimeDependencies;
 
+  systemWithBuildDeps = system.overrideAttrs (o: {
+    systemBuildClosure = pkgs.closureInfo { rootPaths = [ system.drvPath ]; };
+    buildCommand = o.buildCommand + ''
+      ln -sn $systemBuildClosure $out/build-closure
+    '';
+  });
+
 in
 
 {
@@ -306,6 +313,27 @@ in
       '';
     };
 
+    system.includeBuildDependencies = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Whether to include the build closure of the whole system in
+        its runtime closure.  This can be useful for making changes
+        fully offline, as it includes all sources, patches, and
+        intermediate outputs required to build all the derivations
+        that the system depends on.
+
+        Note that this includes _all_ the derivations, down from the
+        included applications to their sources, the compilers used to
+        build them, and even the bootstrap compiler used to compile
+        the compilers. This increases the size of the system and the
+        time needed to download its dependencies drastically: a
+        minimal configuration with no extra services enabled grows
+        from ~670MiB in size to 13.5GiB, and takes proportionally
+        longer to download.
+      '';
+    };
+
   };
 
 
@@ -336,7 +364,7 @@ in
       ]; };
     };
 
-    system.build.toplevel = system;
+    system.build.toplevel = if config.system.includeBuildDependencies then systemWithBuildDeps else system;
 
   };
 
diff --git a/nixos/modules/system/boot/binfmt.nix b/nixos/modules/system/boot/binfmt.nix
index 7f817e5d350da..cceb02c1a73b3 100644
--- a/nixos/modules/system/boot/binfmt.nix
+++ b/nixos/modules/system/boot/binfmt.nix
@@ -134,11 +134,11 @@ let
       mask = ''\xff\xff\xff\xff'';
     };
     x86_64-windows = {
-      magicOrExtension = ".exe";
+      magicOrExtension = "exe";
       recognitionType = "extension";
     };
     i686-windows = {
-      magicOrExtension = ".exe";
+      magicOrExtension = "exe";
       recognitionType = "extension";
     };
   };
@@ -316,11 +316,13 @@ in {
       mkdir -p -m 0755 /run/binfmt
       ${lib.concatStringsSep "\n" (lib.mapAttrsToList activationSnippet config.boot.binfmt.registrations)}
     '';
-    systemd.additionalUpstreamSystemUnits = lib.mkIf (config.boot.binfmt.registrations != {}) [
-      "proc-sys-fs-binfmt_misc.automount"
-      "proc-sys-fs-binfmt_misc.mount"
-      "systemd-binfmt.service"
-    ];
-    systemd.services.systemd-binfmt.restartTriggers = [ (builtins.toJSON config.boot.binfmt.registrations) ];
+    systemd = lib.mkIf (config.boot.binfmt.registrations != {}) {
+      additionalUpstreamSystemUnits = [
+        "proc-sys-fs-binfmt_misc.automount"
+        "proc-sys-fs-binfmt_misc.mount"
+        "systemd-binfmt.service"
+      ];
+      services.systemd-binfmt.restartTriggers = [ (builtins.toJSON config.boot.binfmt.registrations) ];
+    };
   };
 }
diff --git a/nixos/modules/system/boot/loader/grub/install-grub.pl b/nixos/modules/system/boot/loader/grub/install-grub.pl
index 205f1513fd939..aea426c7fdf24 100644
--- a/nixos/modules/system/boot/loader/grub/install-grub.pl
+++ b/nixos/modules/system/boot/loader/grub/install-grub.pl
@@ -450,8 +450,9 @@ sub addEntry {
 
     # Include second initrd with secrets
     if (-e -x "$path/append-initrd-secrets") {
-        my $initrdName = basename($initrd);
-        my $initrdSecretsPath = "$bootPath/kernels/$initrdName-secrets";
+        # Name the initrd secrets after the system from which they're derived.
+        my $systemName = basename(Cwd::abs_path("$path"));
+        my $initrdSecretsPath = "$bootPath/kernels/$systemName-secrets";
 
         mkpath(dirname($initrdSecretsPath), 0, 0755);
         my $oldUmask = umask;
@@ -470,7 +471,7 @@ sub addEntry {
         if (-e $initrdSecretsPathTemp && ! -z _) {
             rename $initrdSecretsPathTemp, $initrdSecretsPath or die "failed to move initrd secrets into place: $!\n";
             $copied{$initrdSecretsPath} = 1;
-            $initrd .= " " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/kernels/$initrdName-secrets";
+            $initrd .= " " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/kernels/$systemName-secrets";
         } else {
             unlink $initrdSecretsPathTemp;
             rmdir dirname($initrdSecretsPathTemp);
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 3e3683211f1e0..c92451997203b 100755
--- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
@@ -85,18 +85,18 @@ def copy_from_profile(profile: Optional[str], generation: int, specialisation: O
     return efi_file_path
 
 
-def describe_generation(generation_dir: str) -> str:
+def describe_generation(profile: Optional[str], generation: int, specialisation: Optional[str]) -> str:
     try:
-        with open("%s/nixos-version" % generation_dir) as f:
+        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(os.path.realpath("%s/kernel" % generation_dir))
+    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(generation_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(
@@ -131,11 +131,10 @@ def write_entry(profile: Optional[str], generation: int, specialisation: Optiona
                   "or renamed a file in `boot.initrd.secrets`", file=sys.stderr)
     entry_file = "@efiSysMountPoint@/loader/entries/%s" % (
         generation_conf_filename(profile, generation, specialisation))
-    generation_dir = os.readlink(system_dir(profile, generation, specialisation))
     tmp_path = "%s.tmp" % (entry_file)
-    kernel_params = "init=%s/init " % generation_dir
+    kernel_params = "init=%s " % profile_path(profile, generation, specialisation, "init")
 
-    with open("%s/kernel-params" % (generation_dir)) as params_file:
+    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,
@@ -143,7 +142,7 @@ def write_entry(profile: Optional[str], generation: int, specialisation: Optiona
                     kernel=kernel,
                     initrd=initrd,
                     kernel_params=kernel_params,
-                    description=describe_generation(generation_dir)))
+                    description=describe_generation(profile, generation, specialisation)))
         if machine_id is not None:
             f.write("machine-id %s\n" % machine_id)
     os.rename(tmp_path, entry_file)
@@ -296,7 +295,7 @@ def main() -> None:
     remove_old_entries(gens)
     for gen in gens:
         try:
-            is_default = os.readlink(system_dir(*gen)) == args.default_config
+            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)
diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix
index 188f2f64dc848..05a667a09efc1 100644
--- a/nixos/modules/system/boot/networkd.nix
+++ b/nixos/modules/system/boot/networkd.nix
@@ -303,6 +303,48 @@ let
 
       sectionTap = checkUnitConfig "Tap" tunChecks;
 
+      sectionL2TP = checkUnitConfig "L2TP" [
+        (assertOnlyFields [
+          "TunnelId"
+          "PeerTunnelId"
+          "Remote"
+          "Local"
+          "EncapsulationType"
+          "UDPSourcePort"
+          "UDPDestinationPort"
+          "UDPChecksum"
+          "UDP6ZeroChecksumTx"
+          "UDP6ZeroChecksumRx"
+        ])
+        (assertInt "TunnelId")
+        (assertRange "TunnelId" 1 4294967295)
+        (assertInt "PeerTunnelId")
+        (assertRange "PeerTunnelId" 1 4294967295)
+        (assertValueOneOf "EncapsulationType" [ "ip" "udp" ])
+        (assertPort "UDPSourcePort")
+        (assertPort "UDPDestinationPort")
+        (assertValueOneOf "UDPChecksum" boolValues)
+        (assertValueOneOf "UDP6ZeroChecksumTx" boolValues)
+        (assertValueOneOf "UDP6ZeroChecksumRx" boolValues)
+      ];
+
+      sectionL2TPSession = checkUnitConfig "L2TPSession" [
+        (assertOnlyFields [
+          "Name"
+          "SessionId"
+          "PeerSessionId"
+          "Layer2SpecificHeader"
+        ])
+        (assertHasField "Name")
+        (assertHasField "SessionId")
+        (assertInt "SessionId")
+        (assertRange "SessionId" 1 4294967295)
+        (assertHasField "PeerSessionId")
+        (assertInt "PeerSessionId")
+        (assertRange "PeerSessionId" 1 4294967295)
+        (assertValueOneOf "Layer2SpecificHeader" [ "none" "default" ])
+      ];
+
       # NOTE The PrivateKey directive is missing on purpose here, please
       # do not add it to this list. The nix store is world-readable let's
       # refrain ourselves from providing a footgun.
@@ -918,6 +960,470 @@ let
         (assertMacAddress "MACAddress")
       ];
 
+      sectionBridge = checkUnitConfig "Bridge" [
+        (assertOnlyFields [
+          "UnicastFlood"
+          "MulticastFlood"
+          "MulticastToUnicast"
+          "NeighborSuppression"
+          "Learning"
+          "Hairpin"
+          "Isolated"
+          "UseBPDU"
+          "FastLeave"
+          "AllowPortToBeRoot"
+          "ProxyARP"
+          "ProxyARPWiFi"
+          "MulticastRouter"
+          "Cost"
+          "Priority"
+        ])
+        (assertValueOneOf "UnicastFlood" boolValues)
+        (assertValueOneOf "MulticastFlood" boolValues)
+        (assertValueOneOf "MulticastToUnicast" boolValues)
+        (assertValueOneOf "NeighborSuppression" boolValues)
+        (assertValueOneOf "Learning" boolValues)
+        (assertValueOneOf "Hairpin" boolValues)
+        (assertValueOneOf "Isolated" boolValues)
+        (assertValueOneOf "UseBPDU" boolValues)
+        (assertValueOneOf "FastLeave" boolValues)
+        (assertValueOneOf "AllowPortToBeRoot" boolValues)
+        (assertValueOneOf "ProxyARP" boolValues)
+        (assertValueOneOf "ProxyARPWiFi" boolValues)
+        (assertValueOneOf "MulticastRouter" [ "no" "query" "permanent" "temporary" ])
+        (assertInt "Cost")
+        (assertRange "Cost" 1 65535)
+        (assertInt "Priority")
+        (assertRange "Priority" 0 63)
+      ];
+
+      sectionBridgeFDB = checkUnitConfig "BridgeFDB" [
+        (assertOnlyFields [
+          "MACAddress"
+          "Destination"
+          "VLANId"
+          "VNI"
+          "AssociatedWith"
+          "OutgoingInterface"
+        ])
+        (assertHasField "MACAddress")
+        (assertInt "VLANId")
+        (assertRange "VLANId" 0 4094)
+        (assertInt "VNI")
+        (assertRange "VNI" 1 16777215)
+        (assertValueOneOf "AssociatedWith" [ "use" "self" "master" "router" ])
+      ];
+
+      sectionBridgeMDB = checkUnitConfig "BridgeMDB" [
+        (assertOnlyFields [
+          "MulticastGroupAddress"
+          "VLANId"
+        ])
+        (assertHasField "MulticastGroupAddress")
+        (assertInt "VLANId")
+        (assertRange "VLANId" 0 4094)
+      ];
+
+      sectionLLDP = checkUnitConfig "LLDP" [
+        (assertOnlyFields [
+          "MUDURL"
+        ])
+      ];
+
+      sectionCAN = checkUnitConfig "CAN" [
+        (assertOnlyFields [
+          "BitRate"
+          "SamplePoint"
+          "TimeQuantaNSec"
+          "PropagationSegment"
+          "PhaseBufferSegment1"
+          "PhaseBufferSegment2"
+          "SyncJumpWidth"
+          "DataBitRate"
+          "DataSamplePoint"
+          "DataTimeQuantaNSec"
+          "DataPropagationSegment"
+          "DataPhaseBufferSegment1"
+          "DataPhaseBufferSegment2"
+          "DataSyncJumpWidth"
+          "FDMode"
+          "FDNonISO"
+          "RestartSec"
+          "Termination"
+          "TripleSampling"
+          "BusErrorReporting"
+          "ListenOnly"
+          "Loopback"
+          "OneShot"
+          "PresumeAck"
+          "ClassicDataLengthCode"
+        ])
+        (assertInt "TimeQuantaNSec" )
+        (assertRange "TimeQuantaNSec" 0 4294967295 )
+        (assertInt "PropagationSegment" )
+        (assertRange "PropagationSegment" 0 4294967295 )
+        (assertInt "PhaseBufferSegment1" )
+        (assertRange "PhaseBufferSegment1" 0 4294967295 )
+        (assertInt "PhaseBufferSegment2" )
+        (assertRange "PhaseBufferSegment2" 0 4294967295 )
+        (assertInt "SyncJumpWidth" )
+        (assertRange "SyncJumpWidth" 0 4294967295 )
+        (assertInt "DataTimeQuantaNSec" )
+        (assertRange "DataTimeQuantaNSec" 0 4294967295 )
+        (assertInt "DataPropagationSegment" )
+        (assertRange "DataPropagationSegment" 0 4294967295 )
+        (assertInt "DataPhaseBufferSegment1" )
+        (assertRange "DataPhaseBufferSegment1" 0 4294967295 )
+        (assertInt "DataPhaseBufferSegment2" )
+        (assertRange "DataPhaseBufferSegment2" 0 4294967295 )
+        (assertInt "DataSyncJumpWidth" )
+        (assertRange "DataSyncJumpWidth" 0 4294967295 )
+        (assertValueOneOf "FDMode" boolValues)
+        (assertValueOneOf "FDNonISO" boolValues)
+        (assertValueOneOf "TripleSampling" boolValues)
+        (assertValueOneOf "BusErrorReporting" boolValues)
+        (assertValueOneOf "ListenOnly" boolValues)
+        (assertValueOneOf "Loopback" boolValues)
+        (assertValueOneOf "OneShot" boolValues)
+        (assertValueOneOf "PresumeAck" boolValues)
+        (assertValueOneOf "ClassicDataLengthCode" boolValues)
+      ];
+
+      sectionIPoIB = checkUnitConfig "IPoIB" [
+        (assertOnlyFields [
+          "Mode"
+          "IgnoreUserspaceMulticastGroup"
+        ])
+        (assertValueOneOf "Mode" [ "datagram" "connected" ])
+        (assertValueOneOf "IgnoreUserspaceMulticastGroup" boolValues)
+      ];
+
+      sectionQDisc = checkUnitConfig "QDisc" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+        ])
+        (assertValueOneOf "Parent" [ "clsact" "ingress" ])
+      ];
+
+      sectionNetworkEmulator = checkUnitConfig "NetworkEmulator" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "DelaySec"
+          "DelayJitterSec"
+          "PacketLimit"
+          "LossRate"
+          "DuplicateRate"
+        ])
+        (assertInt "PacketLimit")
+        (assertRange "PacketLimit" 0 4294967294)
+      ];
+
+      sectionTokenBucketFilter = checkUnitConfig "TokenBucketFilter" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "LatencySec"
+          "LimitBytes"
+          "BurstBytes"
+          "Rate"
+          "MPUBytes"
+          "PeakRate"
+          "MTUBytes"
+        ])
+      ];
+
+      sectionPIE = checkUnitConfig "PIE" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "PacketLimit"
+        ])
+        (assertInt "PacketLimit")
+        (assertRange "PacketLimit" 1 4294967294)
+      ];
+
+      sectionFlowQueuePIE = checkUnitConfig "FlowQueuePIE" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "PacketLimit"
+        ])
+        (assertInt "PacketLimit")
+        (assertRange "PacketLimit" 1 4294967294)
+      ];
+
+      sectionStochasticFairBlue = checkUnitConfig "StochasticFairBlue" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "PacketLimit"
+        ])
+        (assertInt "PacketLimit")
+        (assertRange "PacketLimit" 1 4294967294)
+      ];
+
+      sectionStochasticFairnessQueueing = checkUnitConfig "StochasticFairnessQueueing" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "PerturbPeriodSec"
+        ])
+        (assertInt "PerturbPeriodSec")
+      ];
+
+      sectionBFIFO = checkUnitConfig "BFIFO" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "LimitBytes"
+        ])
+      ];
+
+      sectionPFIFO = checkUnitConfig "PFIFO" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "PacketLimit"
+        ])
+        (assertInt "PacketLimit")
+        (assertRange "PacketLimit" 0 4294967294)
+      ];
+
+      sectionPFIFOHeadDrop = checkUnitConfig "PFIFOHeadDrop" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "PacketLimit"
+        ])
+        (assertInt "PacketLimit")
+        (assertRange "PacketLimit" 0 4294967294)
+      ];
+
+      sectionPFIFOFast = checkUnitConfig "PFIFOFast" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+        ])
+      ];
+
+      sectionCAKE = checkUnitConfig "CAKE" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "Bandwidth"
+          "AutoRateIngress"
+          "OverheadBytes"
+          "MPUBytes"
+          "CompensationMode"
+          "UseRawPacketSize"
+          "FlowIsolationMode"
+          "NAT"
+          "PriorityQueueingPreset"
+          "FirewallMark"
+          "Wash"
+          "SplitGSO"
+        ])
+        (assertValueOneOf "AutoRateIngress" boolValues)
+        (assertInt "OverheadBytes")
+        (assertRange "OverheadBytes" (-64) 256)
+        (assertInt "MPUBytes")
+        (assertRange "MPUBytes" 1 256)
+        (assertValueOneOf "CompensationMode" [ "none" "atm" "ptm" ])
+        (assertValueOneOf "UseRawPacketSize" boolValues)
+        (assertValueOneOf "FlowIsolationMode"
+          [
+            "none"
+            "src-host"
+            "dst-host"
+            "hosts"
+            "flows"
+            "dual-src-host"
+            "dual-dst-host"
+            "triple"
+          ])
+        (assertValueOneOf "NAT" boolValues)
+        (assertValueOneOf "PriorityQueueingPreset"
+          [
+            "besteffort"
+            "precedence"
+            "diffserv8"
+            "diffserv4"
+            "diffserv3"
+          ])
+        (assertInt "FirewallMark")
+        (assertRange "FirewallMark" 1 4294967295)
+        (assertValueOneOf "Wash" boolValues)
+        (assertValueOneOf "SplitGSO" boolValues)
+      ];
+
+      sectionControlledDelay = checkUnitConfig "ControlledDelay" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "PacketLimit"
+          "TargetSec"
+          "IntervalSec"
+          "ECN"
+          "CEThresholdSec"
+        ])
+        (assertValueOneOf "ECN" boolValues)
+      ];
+
+      sectionDeficitRoundRobinScheduler = checkUnitConfig "DeficitRoundRobinScheduler" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+        ])
+      ];
+
+      sectionDeficitRoundRobinSchedulerClass = checkUnitConfig "DeficitRoundRobinSchedulerClass" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "QuantumBytes"
+        ])
+      ];
+
+      sectionEnhancedTransmissionSelection = checkUnitConfig "EnhancedTransmissionSelection" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "Bands"
+          "StrictBands"
+          "QuantumBytes"
+          "PriorityMap"
+        ])
+        (assertInt "Bands")
+        (assertRange "Bands" 1 16)
+        (assertInt "StrictBands")
+        (assertRange "StrictBands" 1 16)
+      ];
+
+      sectionGenericRandomEarlyDetection = checkUnitConfig "GenericRandomEarlyDetection" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "VirtualQueues"
+          "DefaultVirtualQueue"
+          "GenericRIO"
+        ])
+        (assertInt "VirtualQueues")
+        (assertRange "VirtualQueues" 1 16)
+        (assertInt "DefaultVirtualQueue")
+        (assertRange "DefaultVirtualQueue" 1 16)
+        (assertValueOneOf "GenericRIO" boolValues)
+      ];
+
+      sectionFairQueueingControlledDelay = checkUnitConfig "FairQueueingControlledDelay" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "PacketLimit"
+          "MemoryLimitBytes"
+          "Flows"
+          "TargetSec"
+          "IntervalSec"
+          "QuantumBytes"
+          "ECN"
+          "CEThresholdSec"
+        ])
+        (assertInt "PacketLimit")
+        (assertInt "Flows")
+        (assertValueOneOf "ECN" boolValues)
+      ];
+
+      sectionFairQueueing = checkUnitConfig "FairQueueing" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "PacketLimit"
+          "FlowLimit"
+          "QuantumBytes"
+          "InitualQuantumBytes"
+          "MaximumRate"
+          "Buckets"
+          "OrphanMask"
+          "Pacing"
+          "CEThresholdSec"
+        ])
+        (assertInt "PacketLimit")
+        (assertInt "FlowLimit")
+        (assertInt "OrphanMask")
+        (assertValueOneOf "Pacing" boolValues)
+      ];
+
+      sectionTrivialLinkEqualizer = checkUnitConfig "TrivialLinkEqualizer" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "Id"
+        ])
+      ];
+
+      sectionHierarchyTokenBucket = checkUnitConfig "HierarchyTokenBucket" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "DefaultClass"
+          "RateToQuantum"
+        ])
+        (assertInt "RateToQuantum")
+      ];
+
+      sectionHierarchyTokenBucketClass = checkUnitConfig "HierarchyTokenBucketClass" [
+        (assertOnlyFields [
+          "Parent"
+          "ClassId"
+          "Priority"
+          "QuantumBytes"
+          "MTUBytes"
+          "OverheadBytes"
+          "Rate"
+          "CeilRate"
+          "BufferBytes"
+          "CeilBufferBytes"
+        ])
+      ];
+
+      sectionHeavyHitterFilter = checkUnitConfig "HeavyHitterFilter" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "PacketLimit"
+        ])
+        (assertInt "PacketLimit")
+        (assertRange "PacketLimit" 0 4294967294)
+      ];
+
+      sectionQuickFairQueueing = checkUnitConfig "QuickFairQueueing" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+        ])
+      ];
+
+      sectionQuickFairQueueingClass = checkUnitConfig "QuickFairQueueingClass" [
+        (assertOnlyFields [
+          "Parent"
+          "ClassId"
+          "Weight"
+          "MaxPacketBytes"
+        ])
+        (assertInt "Weight")
+        (assertRange "Weight" 1 1023)
+      ];
+
+      sectionBridgeVLAN = checkUnitConfig "BridgeVLAN" [
+        (assertOnlyFields [
+          "VLAN"
+          "EgressUntagged"
+          "PVID"
+        ])
+        (assertInt "PVID")
+        (assertRange "PVID" 0 4094)
+      ];
     };
   };
 
@@ -1012,6 +1518,21 @@ let
 
   };
 
+
+  l2tpSessionOptions = {
+    options = {
+      l2tpSessionConfig = mkOption {
+        default = {};
+        type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionL2TPSession;
+        description = lib.mdDoc ''
+          Each attribute in this set specifies an option in the
+          `[L2TPSession]` section of the unit.  See
+          {manpage}`systemd.netdev(5)` for details.
+        '';
+      };
+    };
+  };
+
   wireguardPeerOptions = {
     options = {
       wireguardPeerConfig = mkOption {
@@ -1125,6 +1646,38 @@ let
       '';
     };
 
+    l2tpConfig = mkOption {
+      default = {};
+      example = {
+        TunnelId = 10;
+        PeerTunnelId = 12;
+        Local = "static";
+        Remote = "192.168.30.101";
+        EncapsulationType = "ip";
+      };
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionL2TP;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[L2TP]` section of the unit. See
+        {manpage}`systemd.netdev(5)` for details.
+      '';
+    };
+
+    l2tpSessions = mkOption {
+      default = [];
+      example = [ { l2tpSessionConfig={
+        SessionId = 25;
+        PeerSessionId = 26;
+        Name = "l2tp-sess";
+      };}];
+      type = with types; listOf (submodule l2tpSessionOptions);
+      description = lib.mdDoc ''
+        Each item in this array specifies an option in the
+        `[L2TPSession]` section of the unit. See
+        {manpage}`systemd.netdev(5)` for details.
+      '';
+    };
+
     wireguardConfig = mkOption {
       default = {};
       example = {
@@ -1306,6 +1859,51 @@ let
     };
   };
 
+  bridgeFDBOptions = {
+    options = {
+      bridgeFDBConfig = mkOption {
+        default = {};
+        example = { MACAddress = "65:43:4a:5b:d8:5f"; Destination = "192.168.1.42"; VNI = 20; };
+        type = types.addCheck (types.attrsOf unitOption) check.network.sectionBridgeFDB;
+        description = lib.mdDoc ''
+          Each attribute in this set specifies an option in the
+          `[BridgeFDB]` section of the unit.  See
+          {manpage}`systemd.network(5)` for details.
+        '';
+      };
+    };
+  };
+
+  bridgeMDBOptions = {
+    options = {
+      bridgeMDBConfig = mkOption {
+        default = {};
+        example = { MulticastGroupAddress = "ff02::1:2:3:4"; VLANId = 10; };
+        type = types.addCheck (types.attrsOf unitOption) check.network.sectionBridgeMDB;
+        description = lib.mdDoc ''
+          Each attribute in this set specifies an option in the
+          `[BridgeMDB]` section of the unit.  See
+          {manpage}`systemd.network(5)` for details.
+        '';
+      };
+    };
+  };
+
+  bridgeVLANOptions = {
+    options = {
+      bridgeMDBConfig = mkOption {
+        default = {};
+        example = { VLAN = 20; };
+        type = types.addCheck (types.attrsOf unitOption) check.network.sectionBridgeVLAN;
+        description = lib.mdDoc ''
+          Each attribute in this set specifies an option in the
+          `[BridgeVLAN]` section of the unit.  See
+          {manpage}`systemd.network(5)` for details.
+        '';
+      };
+    };
+  };
+
   networkOptions = commonNetworkOptions // {
 
     linkConfig = mkOption {
@@ -1445,6 +2043,366 @@ let
       '';
     };
 
+    bridgeConfig = mkOption {
+      default = {};
+      example = { MulticastFlood = false; Cost = 20; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionBridge;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[Bridge]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    bridgeFDBs = mkOption {
+      default = [];
+      example = [ { bridgeFDBConfig = { MACAddress = "90:e2:ba:43:fc:71"; Destination = "192.168.100.4"; VNI = 3600; }; } ];
+      type = with types; listOf (submodule bridgeFDBOptions);
+      description = lib.mdDoc ''
+        A list of BridgeFDB sections to be added to the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    bridgeMDBs = mkOption {
+      default = [];
+      example = [ { bridgeMDBConfig = { MulticastGroupAddress = "ff02::1:2:3:4"; VLANId = 10; } ; } ];
+      type = with types; listOf (submodule bridgeMDBOptions);
+      description = lib.mdDoc ''
+        A list of BridgeMDB sections to be added to the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    lldpConfig = mkOption {
+      default = {};
+      example = { MUDURL = "https://things.example.org/product_abc123/v5"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionLLDP;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[LLDP]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    canConfig = mkOption {
+      default = {};
+      example = { };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionCAN;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[CAN]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    ipoIBConfig = mkOption {
+      default = {};
+      example = { };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionIPoIB;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[IPoIB]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    qdiscConfig = mkOption {
+      default = {};
+      example = { Parent = "ingress"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionQDisc;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[QDisc]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    networkEmulatorConfig = mkOption {
+      default = {};
+      example = { Parent = "ingress"; DelaySec = "20msec"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionNetworkEmulator;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[NetworkEmulator]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    tokenBucketFilterConfig = mkOption {
+      default = {};
+      example = { Parent = "ingress"; Rate = "100k"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionTokenBucketFilter;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[TokenBucketFilter]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    pieConfig = mkOption {
+      default = {};
+      example = { Parent = "ingress"; PacketLimit = "3847"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionPIE;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[PIE]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    flowQueuePIEConfig = mkOption {
+      default = {};
+      example = { Parent = "ingress"; PacketLimit = "3847"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionFlowQueuePIE;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[FlowQueuePIE]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    stochasticFairBlueConfig = mkOption {
+      default = {};
+      example = { Parent = "ingress"; PacketLimit = "3847"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionStochasticFairBlue;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[StochasticFairBlue]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    stochasticFairnessQueueingConfig = mkOption {
+      default = {};
+      example = { Parent = "ingress"; PerturbPeriodSec = "30"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionStochasticFairnessQueueing;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[StochasticFairnessQueueing]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    bfifoConfig = mkOption {
+      default = {};
+      example = { Parent = "ingress"; LimitBytes = "20K"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionBFIFO;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[BFIFO]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    pfifoConfig = mkOption {
+      default = {};
+      example = { Parent = "ingress"; PacketLimit = "300"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionPFIFO;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[PFIFO]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    pfifoHeadDropConfig = mkOption {
+      default = {};
+      example = { Parent = "ingress"; PacketLimit = "300"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionPFIFOHeadDrop;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[PFIFOHeadDrop]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    pfifoFastConfig = mkOption {
+      default = {};
+      example = { Parent = "ingress"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionPFIFOFast;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[PFIFOFast]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    cakeConfig = mkOption {
+      default = {};
+      example = { Bandwidth = "40M"; OverheadBytes = 8; CompensationMode = "ptm"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionCAKE;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[CAKE]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    controlledDelayConfig = mkOption {
+      default = {};
+      example = { Parent = "ingress"; TargetSec = "20msec"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionControlledDelay;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[ControlledDelay]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    deficitRoundRobinSchedulerConfig = mkOption {
+      default = {};
+      example = { Parent = "root"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionDeficitRoundRobinScheduler;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[DeficitRoundRobinScheduler]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    deficitRoundRobinSchedulerClassConfig = mkOption {
+      default = {};
+      example = { Parent = "root"; QuantumBytes = "300k"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionDeficitRoundRobinSchedulerClass;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[DeficitRoundRobinSchedulerClass]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    enhancedTransmissionSelectionConfig = mkOption {
+      default = {};
+      example = { Parent = "root"; QuantumBytes = "300k"; Bands = 3; PriorityMap = "100 200 300"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionEnhancedTransmissionSelection;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[EnhancedTransmissionSelection]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    genericRandomEarlyDetectionConfig = mkOption {
+      default = {};
+      example = { Parent = "root"; VirtualQueues = 5; DefaultVirtualQueue = 3; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionGenericRandomEarlyDetection;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[GenericRandomEarlyDetection]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    fairQueueingControlledDelayConfig = mkOption {
+      default = {};
+      example = { Parent = "root"; Flows = 5; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionFairQueueingControlledDelay;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[FairQueueingControlledDelay]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    fairQueueingConfig = mkOption {
+      default = {};
+      example = { Parent = "root"; FlowLimit = 5; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionFairQueueing;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[FairQueueing]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    trivialLinkEqualizerConfig = mkOption {
+      default = {};
+      example = { Parent = "root"; Id = 0; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionTrivialLinkEqualizer;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[TrivialLinkEqualizer]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    hierarchyTokenBucketConfig = mkOption {
+      default = {};
+      example = { Parent = "root"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionHierarchyTokenBucket;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[HierarchyTokenBucket]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    hierarchyTokenBucketClassConfig = mkOption {
+      default = {};
+      example = { Parent = "root"; Rate = "10M"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionHierarchyTokenBucketClass;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[HierarchyTokenBucketClass]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    heavyHitterFilterConfig = mkOption {
+      default = {};
+      example = { Parent = "root"; PacketLimit = 10000; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionHeavyHitterFilter;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[HeavyHitterFilter]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    quickFairQueueingConfig = mkOption {
+      default = {};
+      example = { Parent = "root"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionQuickFairQueueing;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[QuickFairQueueing]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    quickFairQueueingConfigClass = mkOption {
+      default = {};
+      example = { Parent = "root"; Weight = 133; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionQuickFairQueueingClass;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[QuickFairQueueingClass]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    bridgeVLANConfig = mkOption {
+      default = {};
+      example = { VLAN = "10-20"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionbridgeVLAN;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[BridgeVLAN]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    bridgeVLANs = mkOption {
+      default = [];
+      example = [ { bridgeVLANConfig = { VLAN = "10-20"; }; } ];
+      type = with types; listOf (submodule bridgeVLANOptions);
+      description = lib.mdDoc ''
+        A list of BridgeVLAN sections to be added to the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
     name = mkOption {
       type = types.nullOr types.str;
       default = null;
@@ -1705,6 +2663,14 @@ let
           [Tap]
           ${attrsToSection def.tapConfig}
         ''
+        + optionalString (def.l2tpConfig != { }) ''
+          [L2TP]
+          ${attrsToSection def.l2tpConfig}
+        ''
+        + flip concatMapStrings def.l2tpSessions (x: ''
+          [L2TPSession]
+          ${attrsToSection x.l2tpSessionConfig}
+        '')
         + optionalString (def.wireguardConfig != { }) ''
           [WireGuard]
           ${attrsToSection def.wireguardConfig}
@@ -1844,6 +2810,134 @@ let
           [DHCPServerStaticLease]
           ${attrsToSection x.dhcpServerStaticLeaseConfig}
         '')
+        + optionalString (def.bridgeConfig != { }) ''
+          [Bridge]
+          ${attrsToSection def.bridgeConfig}
+        ''
+        + flip concatMapStrings def.bridgeFDBs (x: ''
+          [BridgeFDB]
+          ${attrsToSection x.bridgeFDBConfig}
+        '')
+        + flip concatMapStrings def.bridgeMDBs (x: ''
+          [BridgeMDB]
+          ${attrsToSection x.bridgeMDBConfig}
+        '')
+        + optionalString (def.lldpConfig != { }) ''
+          [LLDP]
+          ${attrsToSection def.lldpConfig}
+        ''
+        + optionalString (def.canConfig != { }) ''
+          [CAN]
+          ${attrsToSection def.canConfig}
+        ''
+        + optionalString (def.ipoIBConfig != { }) ''
+          [IPoIB]
+          ${attrsToSection def.ipoIBConfig}
+        ''
+        + optionalString (def.qdiscConfig != { }) ''
+          [QDisc]
+          ${attrsToSection def.qdiscConfig}
+        ''
+        + optionalString (def.networkEmulatorConfig != { }) ''
+          [NetworkEmulator]
+          ${attrsToSection def.networkEmulatorConfig}
+        ''
+        + optionalString (def.tokenBucketFilterConfig != { }) ''
+          [TokenBucketFilter]
+          ${attrsToSection def.tockenBucketFilterConfig}
+        ''
+        + optionalString (def.pieConfig != { }) ''
+          [PIE]
+          ${attrsToSection def.pieConfig}
+        ''
+        + optionalString (def.flowQueuePIEConfig != { }) ''
+          [FlowQueuePIE]
+          ${attrsToSection def.flowQueuePIEConfig}
+        ''
+        + optionalString (def.stochasticFairBlueConfig != { }) ''
+          [StochasticFairBlue]
+          ${attrsToSection def.stochasticFairBlueConfig}
+        ''
+        + optionalString (def.stochasticFairnessQueueingConfig != { }) ''
+          [StochasticFairnessQueueing]
+          ${attrsToSection def.stochasticFairnessQueueingConfig}
+        ''
+        + optionalString (def.bfifoConfig != { }) ''
+          [BFIFO]
+          ${attrsToSection def.bfifoConfig}
+        ''
+        + optionalString (def.pfifoConfig != { }) ''
+          [PFIFO]
+          ${attrsToSection def.pfifoConfig}
+        ''
+        + optionalString (def.pfifoHeadDropConfig != { }) ''
+          [PFIFOHeadDrop]
+          ${attrsToSection def.pfifoHeadDropConfig}
+        ''
+        + optionalString (def.pfifoFastConfig != { }) ''
+          [PFIFOFast]
+          ${attrsToSection def.pfifoFastConfig}
+        ''
+        + optionalString (def.cakeConfig != { }) ''
+          [CAKE]
+          ${attrsToSection def.cakeConfig}
+        ''
+        + optionalString (def.controlledDelayConfig != { }) ''
+          [ControlledDelay]
+          ${attrsToSection def.controlledDelayConfig}
+        ''
+        + optionalString (def.deficitRoundRobinSchedulerConfig != { }) ''
+          [DeficitRoundRobinScheduler]
+          ${attrsToSection def.deficitRoundRobinSchedulerConfig}
+        ''
+        + optionalString (def.deficitRoundRobinSchedulerClassConfig != { }) ''
+          [DeficitRoundRobinSchedulerClass]
+          ${attrsToSection def.deficitRoundRobinSchedulerClassConfig}
+        ''
+        + optionalString (def.enhancedTransmissionSelectionConfig != { }) ''
+          [EnhancedTransmissionSelection]
+          ${attrsToSection def.enhancedTransmissionSelectionConfig}
+        ''
+        + optionalString (def.genericRandomEarlyDetectionConfig != { }) ''
+          [GenericRandomEarlyDetection]
+          ${attrsToSection def.genericRandomEarlyDetectionConfig}
+        ''
+        + optionalString (def.fairQueueingControlledDelayConfig != { }) ''
+          [FairQueueingControlledDelay]
+          ${attrsToSection def.fairQueueingControlledDelayConfig}
+        ''
+        + optionalString (def.fairQueueingConfig != { }) ''
+          [FairQueueing]
+          ${attrsToSection def.fairQueueingConfig}
+        ''
+        + optionalString (def.trivialLinkEqualizerConfig != { }) ''
+          [TrivialLinkEqualizer]
+          ${attrsToSection def.trivialLinkEqualizerConfig}
+        ''
+        + optionalString (def.hierarchyTokenBucketConfig != { }) ''
+          [HierarchyTokenBucket]
+          ${attrsToSection def.hierarchyTokenBucketConfig}
+        ''
+        + optionalString (def.hierarchyTokenBucketClassConfig != { }) ''
+          [HierarchyTokenBucketClass]
+          ${attrsToSection def.hierarchyTokenBucketClassConfig}
+        ''
+        + optionalString (def.heavyHitterFilterConfig != { }) ''
+          [HeavyHitterFilter]
+          ${attrsToSection def.heavyHitterFilterConfig}
+        ''
+        + optionalString (def.quickFairQueueingConfig != { }) ''
+          [QuickFairQueueing]
+          ${attrsToSection def.quickFairQueueingConfig}
+        ''
+        + optionalString (def.quickFairQueueingConfigClass != { }) ''
+          [QuickFairQueueingClass]
+          ${attrsToSection def.quickFairQueueingConfigClass}
+        ''
+        + flip concatMapStrings def.bridgeVLANs (x: ''
+          [BridgeVLAN]
+          ${attrsToSection x.bridgeVLANConfig}
+        '')
         + def.extraConfig;
     };
 
@@ -1948,7 +3042,7 @@ in
           Extra command-line arguments to pass to systemd-networkd-wait-online.
           These also affect per-interface `systemd-network-wait-online@` services.
 
-          See [{manpage}`systemd-networkd-wait-online.service(8)`](https://www.freedesktop.org/software/systemd/man/systemd-networkd-wait-online.service.html) for all available options.
+          See {manpage}`systemd-networkd-wait-online.service(8)` for all available options.
         '';
         type = with types; listOf str;
         default = [];
diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix
index 679a663362b6c..8b20f9a7e87f9 100644
--- a/nixos/modules/system/boot/systemd.nix
+++ b/nixos/modules/system/boot/systemd.nix
@@ -614,7 +614,7 @@ in
 
     # Avoid potentially degraded system state due to
     # "Userspace Out-Of-Memory (OOM) Killer was skipped because of a failed condition check (ConditionControlGroupController=v2)."
-    systemd.services.systemd-oomd.enable = mkIf (!cfg.enableUnifiedCgroupHierarchy) false;
+    systemd.oomd.enable = mkIf (!cfg.enableUnifiedCgroupHierarchy) false;
 
     services.logrotate.settings = {
       "/var/log/btmp" = mapAttrs (_: mkDefault) {
diff --git a/nixos/modules/system/boot/systemd/coredump.nix b/nixos/modules/system/boot/systemd/coredump.nix
index 2dbc95e38ee31..03ef00e5683c1 100644
--- a/nixos/modules/system/boot/systemd/coredump.nix
+++ b/nixos/modules/system/boot/systemd/coredump.nix
@@ -66,9 +66,7 @@ in {
         uid = config.ids.uids.systemd-coredump;
         group = "systemd-coredump";
       };
-      users.groups.systemd-coredump = {
-        gid = config.ids.gids.systemd-coredump;
-      };
+      users.groups.systemd-coredump = {};
     })
 
     (mkIf (!cfg.enable) {
diff --git a/nixos/modules/system/boot/systemd/initrd.nix b/nixos/modules/system/boot/systemd/initrd.nix
index 620d76aef20d8..f937581b76f6f 100644
--- a/nixos/modules/system/boot/systemd/initrd.nix
+++ b/nixos/modules/system/boot/systemd/initrd.nix
@@ -118,7 +118,7 @@ let
     name = "initrd-bin-env";
     paths = map getBin cfg.initrdBin;
     pathsToLink = ["/bin" "/sbin"];
-    postBuild = concatStringsSep "\n" (mapAttrsToList (n: v: "ln -s '${v}' $out/bin/'${n}'") cfg.extraBin);
+    postBuild = concatStringsSep "\n" (mapAttrsToList (n: v: "ln -sf '${v}' $out/bin/'${n}'") cfg.extraBin);
   };
 
   initialRamdisk = pkgs.makeInitrdNG {
@@ -158,6 +158,16 @@ in {
       '';
     };
 
+    managerEnvironment = mkOption {
+      type = with types; attrsOf (nullOr (oneOf [ str path package ]));
+      default = {};
+      example = { SYSTEMD_LOG_LEVEL = "debug"; };
+      description = lib.mdDoc ''
+        Environment variables of PID 1. These variables are
+        *not* passed to started units.
+      '';
+    };
+
     contents = mkOption {
       description = lib.mdDoc "Set of files that have to be linked into the initrd";
       example = literalExpression ''
@@ -355,8 +365,11 @@ in {
         less = "${pkgs.less}/bin/less";
         mount = "${cfg.package.util-linux}/bin/mount";
         umount = "${cfg.package.util-linux}/bin/umount";
+        fsck = "${cfg.package.util-linux}/bin/fsck";
       };
 
+      managerEnvironment.PATH = "/bin:/sbin";
+
       contents = {
         "/init".source = "${cfg.package}/lib/systemd/systemd";
         "/etc/systemd/system".source = stage1Units;
@@ -365,6 +378,7 @@ in {
           [Manager]
           DefaultEnvironment=PATH=/bin:/sbin ${optionalString (isBool cfg.emergencyAccess && cfg.emergencyAccess) "SYSTEMD_SULOGIN_FORCE=1"}
           ${cfg.extraConfig}
+          ManagerEnvironment=${lib.concatStringsSep " " (lib.mapAttrsToList (n: v: "${n}=${lib.escapeShellArg v}") cfg.managerEnvironment)}
         '';
 
         "/lib/modules".source = "${modulesClosure}/lib/modules";
@@ -427,9 +441,6 @@ in {
         # fido2 support
         "${cfg.package}/lib/cryptsetup/libcryptsetup-token-systemd-fido2.so"
         "${pkgs.libfido2}/lib/libfido2.so.1"
-
-        # the unwrapped systemd-cryptsetup executable
-        "${cfg.package}/lib/systemd/.systemd-cryptsetup-wrapped"
       ] ++ jobScripts;
 
       targets.initrd.aliases = ["default.target"];
@@ -447,21 +458,6 @@ in {
                      (v: let n = escapeSystemdPath v.where;
                          in nameValuePair "${n}.automount" (automountToUnit n v)) cfg.automounts);
 
-      # The unit in /run/systemd/generator shadows the unit in
-      # /etc/systemd/system, but will still apply drop-ins from
-      # /etc/systemd/system/foo.service.d/
-      #
-      # We need IgnoreOnIsolate, otherwise the Requires dependency of
-      # a mount unit on its makefs unit causes it to be unmounted when
-      # we isolate for switch-root. Use a dummy package so that
-      # generateUnits will generate drop-ins instead of unit files.
-      packages = [(pkgs.runCommand "dummy" {} ''
-        mkdir -p $out/etc/systemd/system
-        touch $out/etc/systemd/system/systemd-{makefs,growfs}@.service
-      '')];
-      services."systemd-makefs@" = lib.mkIf needMakefs { unitConfig.IgnoreOnIsolate = true; };
-      services."systemd-growfs@" = lib.mkIf needGrowfs { unitConfig.IgnoreOnIsolate = true; };
-
       # make sure all the /dev nodes are set up
       services.systemd-tmpfiles-setup-dev.wantedBy = ["sysinit.target"];
 
@@ -495,7 +491,7 @@ in {
 
           # If we are not booting a NixOS closure (e.g. init=/bin/sh),
           # we don't know what root to prepare so we don't do anything
-          if ! [ -x "/sysroot$closure/prepare-root" ]; then
+          if ! [ -x "/sysroot$(readlink "/sysroot$closure/prepare-root" || echo "$closure/prepare-root")" ]; then
             echo "NEW_INIT=''${initParam[1]}" > /etc/switch-root.conf
             echo "$closure does not look like a NixOS installation - not activating"
             exit 0
diff --git a/nixos/modules/system/boot/systemd/repart.nix b/nixos/modules/system/boot/systemd/repart.nix
index 33f1b247c5ede..8f3a700237700 100644
--- a/nixos/modules/system/boot/systemd/repart.nix
+++ b/nixos/modules/system/boot/systemd/repart.nix
@@ -1,7 +1,8 @@
 { config, pkgs, lib, ... }:
 
 let
-  cfg = config.boot.initrd.systemd.repart;
+  cfg = config.systemd.repart;
+  initrdCfg = config.boot.initrd.systemd.repart;
 
   writeDefinition = name: partitionConfig: pkgs.writeText
     "${name}.conf"
@@ -24,45 +25,59 @@ let
   '';
 in
 {
-  options.boot.initrd.systemd.repart = {
-    enable = lib.mkEnableOption (lib.mdDoc "systemd-repart") // {
+  options = {
+    boot.initrd.systemd.repart.enable = lib.mkEnableOption (lib.mdDoc "systemd-repart") // {
       description = lib.mdDoc ''
-        Grow and add partitions to a partition table a boot time in the initrd.
+        Grow and add partitions to a partition table at boot time in the initrd.
         systemd-repart only works with GPT partition tables.
+
+        To run systemd-repart after the initrd, see
+        `options.systemd.repart.enable`.
       '';
     };
 
-    partitions = lib.mkOption {
-      type = with lib.types; attrsOf (attrsOf (oneOf [ str int bool ]));
-      default = { };
-      example = {
-        "10-root" = {
-          Type = "root";
-        };
-        "20-home" = {
-          Type = "home";
-          SizeMinBytes = "512M";
-          SizeMaxBytes = "2G";
+    systemd.repart = {
+      enable = lib.mkEnableOption (lib.mdDoc "systemd-repart") // {
+        description = lib.mdDoc ''
+          Grow and add partitions to a partition table.
+          systemd-repart only works with GPT partition tables.
+
+          To run systemd-repart while in the initrd, see
+          `options.boot.initrd.systemd.repart.enable`.
+        '';
+      };
+
+      partitions = lib.mkOption {
+        type = with lib.types; attrsOf (attrsOf (oneOf [ str int bool ]));
+        default = { };
+        example = {
+          "10-root" = {
+            Type = "root";
+          };
+          "20-home" = {
+            Type = "home";
+            SizeMinBytes = "512M";
+            SizeMaxBytes = "2G";
+          };
         };
+        description = lib.mdDoc ''
+          Specify partitions as a set of the names of the definition files as the
+          key and the partition configuration as its value. The partition
+          configuration can use all upstream options. See <link
+          xlink:href="https://www.freedesktop.org/software/systemd/man/repart.d.html"/>
+          for all available options.
+        '';
       };
-      description = lib.mdDoc ''
-        Specify partitions as a set of the names of the definition files as the
-        key and the partition configuration as its value. The partition
-        configuration can use all upstream options. See <link
-        xlink:href="https://www.freedesktop.org/software/systemd/man/repart.d.html"/>
-        for all available options.
-      '';
     };
   };
 
-  config = lib.mkIf cfg.enable {
-    # Link the definitions into /etc so that they are included in the
-    # /nix/store of the sysroot. This also allows the user to run the
-    # systemd-repart binary after activation manually while automatically
-    # picking up the definition files.
+  config = lib.mkIf (cfg.enable || initrdCfg.enable) {
+    # Always link the definitions into /etc so that they are also included in
+    # the /nix/store of the sysroot during early userspace (i.e. while in the
+    # initrd).
     environment.etc."repart.d".source = definitionsDirectory;
 
-    boot.initrd.systemd = {
+    boot.initrd.systemd = lib.mkIf initrdCfg.enable {
       additionalUpstreamUnits = [
         "systemd-repart.service"
       ];
@@ -73,7 +88,7 @@ in
 
       # Override defaults in upstream unit.
       services.systemd-repart = {
-        # Unset the coniditions as they cannot be met before activation because
+        # Unset the conditions as they cannot be met before activation because
         # the definition files are not stored in the expected locations.
         unitConfig.ConditionDirectoryNotEmpty = [
           " " # required to unset the previous value.
@@ -97,5 +112,12 @@ in
         after = [ "sysroot.mount" ];
       };
     };
+
+    systemd = lib.mkIf cfg.enable {
+      additionalUpstreamSystemUnits = [
+        "systemd-repart.service"
+      ];
+    };
   };
+
 }
diff --git a/nixos/modules/tasks/filesystems.nix b/nixos/modules/tasks/filesystems.nix
index 7f2c8a41b20a1..822f1593474eb 100644
--- a/nixos/modules/tasks/filesystems.nix
+++ b/nixos/modules/tasks/filesystems.nix
@@ -140,7 +140,10 @@ let
         else if config.fsType == "reiserfs" then "-q"
         else null;
     in {
-      options = mkIf config.autoResize [ "x-nixos.autoresize" ];
+      options = mkMerge [
+        (mkIf config.autoResize [ "x-nixos.autoresize" ])
+        (mkIf (utils.fsNeededForBoot config) [ "x-initrd.mount" ])
+      ];
       formatOptions = mkIf (defaultFormatOptions != null) (mkDefault defaultFormatOptions);
     };
 
@@ -155,27 +158,54 @@ let
 
   makeFstabEntries =
     let
-      fsToSkipCheck = [ "none" "bindfs" "btrfs" "zfs" "tmpfs" "nfs" "nfs4" "vboxsf" "glusterfs" "apfs" "9p" "cifs" "prl_fs" "vmhgfs" ];
+      fsToSkipCheck = [
+        "none"
+        "auto"
+        "overlay"
+        "iso9660"
+        "bindfs"
+        "udf"
+        "btrfs"
+        "zfs"
+        "tmpfs"
+        "bcachefs"
+        "nfs"
+        "nfs4"
+        "nilfs2"
+        "vboxsf"
+        "squashfs"
+        "glusterfs"
+        "apfs"
+        "9p"
+        "cifs"
+        "prl_fs"
+        "vmhgfs"
+      ] ++ lib.optionals (!config.boot.initrd.checkJournalingFS) [
+        "ext3"
+        "ext4"
+        "reiserfs"
+        "xfs"
+        "jfs"
+        "f2fs"
+      ];
       isBindMount = fs: builtins.elem "bind" fs.options;
       skipCheck = fs: fs.noCheck || fs.device == "none" || builtins.elem fs.fsType fsToSkipCheck || isBindMount fs;
       # https://wiki.archlinux.org/index.php/fstab#Filepath_spaces
       escape = string: builtins.replaceStrings [ " " "\t" ] [ "\\040" "\\011" ] string;
-    in fstabFileSystems: { rootPrefix ? "", excludeChecks ? false, extraOpts ? (fs: []) }: concatMapStrings (fs:
+    in fstabFileSystems: { rootPrefix ? "", extraOpts ? (fs: []) }: concatMapStrings (fs:
       (optionalString (isBindMount fs) (escape rootPrefix))
       + (if fs.device != null then escape fs.device
          else if fs.label != null then "/dev/disk/by-label/${escape fs.label}"
          else throw "No device specified for mount point ‘${fs.mountPoint}’.")
-      + " " + escape (rootPrefix + fs.mountPoint)
+      + " " + escape fs.mountPoint
       + " " + fs.fsType
       + " " + escape (builtins.concatStringsSep "," (fs.options ++ (extraOpts fs)))
-      + " " + (optionalString (!excludeChecks)
-        ("0 " + (if skipCheck fs then "0" else if fs.mountPoint == "/" then "1" else "2")))
+      + " 0 " + (if skipCheck fs then "0" else if fs.mountPoint == "/" then "1" else "2")
       + "\n"
     ) fstabFileSystems;
 
     initrdFstab = pkgs.writeText "initrd-fstab" (makeFstabEntries (filter utils.fsNeededForBoot fileSystems) {
       rootPrefix = "/sysroot";
-      excludeChecks = true;
       extraOpts = fs:
         (optional fs.autoResize "x-systemd.growfs")
         ++ (optional fs.autoFormat "x-systemd.makefs");
@@ -328,7 +358,9 @@ in
         )}
       '';
 
-    boot.initrd.systemd.contents."/etc/fstab".source = initrdFstab;
+    boot.initrd.systemd.storePaths = [initrdFstab];
+    boot.initrd.systemd.managerEnvironment.SYSTEMD_SYSROOT_FSTAB = initrdFstab;
+    boot.initrd.systemd.services.initrd-parse-etc.environment.SYSTEMD_SYSROOT_FSTAB = initrdFstab;
 
     # Provide a target that pulls in all filesystems.
     systemd.targets.fs =
diff --git a/nixos/modules/tasks/filesystems/vfat.nix b/nixos/modules/tasks/filesystems/vfat.nix
index 5baab1c802cf9..5421b617b43b9 100644
--- a/nixos/modules/tasks/filesystems/vfat.nix
+++ b/nixos/modules/tasks/filesystems/vfat.nix
@@ -11,7 +11,7 @@ in
 {
   config = mkIf (any (fs: fs == "vfat") config.boot.supportedFilesystems) {
 
-    system.fsPackages = [ pkgs.dosfstools ];
+    system.fsPackages = [ pkgs.dosfstools pkgs.mtools ];
 
     boot.initrd.kernelModules = mkIf inInitrd [ "vfat" "nls_cp437" "nls_iso8859-1" ];
 
diff --git a/nixos/modules/virtualisation/docker.nix b/nixos/modules/virtualisation/docker.nix
index d9bd10ba1fc8f..046b8e2f79010 100644
--- a/nixos/modules/virtualisation/docker.nix
+++ b/nixos/modules/virtualisation/docker.nix
@@ -100,7 +100,7 @@ in
 
     logDriver =
       mkOption {
-        type = types.enum ["none" "json-file" "syslog" "journald" "gelf" "fluentd" "awslogs" "splunk" "etwlogs" "gcplogs"];
+        type = types.enum ["none" "json-file" "syslog" "journald" "gelf" "fluentd" "awslogs" "splunk" "etwlogs" "gcplogs" "local"];
         default = "journald";
         description =
           lib.mdDoc ''
@@ -163,7 +163,7 @@ in
   ###### implementation
 
   config = mkIf cfg.enable (mkMerge [{
-      boot.kernelModules = [ "bridge" "veth" ];
+      boot.kernelModules = [ "bridge" "veth" "br_netfilter" "xt_nat" ];
       boot.kernel.sysctl = {
         "net.ipv4.conf.all.forwarding" = mkOverride 98 true;
         "net.ipv4.conf.default.forwarding" = mkOverride 98 true;
diff --git a/nixos/modules/virtualisation/ec2-metadata-fetcher.sh b/nixos/modules/virtualisation/ec2-metadata-fetcher.sh
index 9e204d45dbd83..716aff7c22fba 100644
--- a/nixos/modules/virtualisation/ec2-metadata-fetcher.sh
+++ b/nixos/modules/virtualisation/ec2-metadata-fetcher.sh
@@ -55,10 +55,9 @@ done
 echo "getting EC2 instance metadata..."
 
 get_imds() {
-  # Intentionally no --fail here, so that we proceed even if e.g. a
-  # 404 was returned (but we still fail if we can't reach the IMDS
-  # server).
-  curl --silent --show-error --header "X-aws-ec2-metadata-token: $IMDS_TOKEN" "$@"
+  # --fail to avoid populating missing files with 404 HTML response body
+  # || true to allow the script to continue even when encountering a 404
+  curl --silent --show-error --fail --header "X-aws-ec2-metadata-token: $IMDS_TOKEN" "$@" || true
 }
 
 get_imds -o "$metaDir/ami-manifest-path" http://169.254.169.254/1.0/meta-data/ami-manifest-path
diff --git a/nixos/modules/virtualisation/multipass.nix b/nixos/modules/virtualisation/multipass.nix
index d066932b61208..6ef7de4b2bf50 100644
--- a/nixos/modules/virtualisation/multipass.nix
+++ b/nixos/modules/virtualisation/multipass.nix
@@ -44,7 +44,7 @@ in
 
       serviceConfig = {
         ExecStart = "${cfg.package}/bin/multipassd --logger platform --verbosity ${cfg.logLevel}";
-        SyslogIdentifer = "multipassd";
+        SyslogIdentifier = "multipassd";
         Restart = "on-failure";
         TimeoutStopSec = 300;
         Type = "simple";
diff --git a/nixos/modules/virtualisation/nixos-containers.nix b/nixos/modules/virtualisation/nixos-containers.nix
index e1e640c447425..669981da59740 100644
--- a/nixos/modules/virtualisation/nixos-containers.nix
+++ b/nixos/modules/virtualisation/nixos-containers.nix
@@ -514,6 +514,7 @@ in
                       };
                     in [ extraConfig ] ++ (map (x: x.value) defs);
                   prefix = [ "containers" name ];
+                  inherit (config) specialArgs;
                 }).config;
               };
             };
@@ -555,6 +556,16 @@ in
               '';
             };
 
+            specialArgs = mkOption {
+              type = types.attrsOf types.unspecified;
+              default = {};
+              description = lib.mdDoc ''
+                A set of special arguments to be passed to NixOS modules.
+                This will be merged into the `specialArgs` used to evaluate
+                the NixOS configurations.
+              '';
+            };
+
             ephemeral = mkOption {
               type = types.bool;
               default = false;
diff --git a/nixos/modules/virtualisation/podman/default.nix b/nixos/modules/virtualisation/podman/default.nix
index 6c00fabaa1858..83ddba3ce06ef 100644
--- a/nixos/modules/virtualisation/podman/default.nix
+++ b/nixos/modules/virtualisation/podman/default.nix
@@ -7,6 +7,8 @@ let
 
   podmanPackage = (pkgs.podman.override {
     extraPackages = cfg.extraPackages
+      # setuid shadow
+      ++ [ "/run/wrappers" ]
       ++ lib.optional (builtins.elem "zfs" config.boot.supportedFilesystems) config.boot.zfs.package;
   });
 
@@ -181,10 +183,6 @@ in
 
       systemd.packages = [ cfg.package ];
 
-      systemd.services.podman.serviceConfig = {
-        ExecStart = [ "" "${cfg.package}/bin/podman $LOGGING system service" ];
-      };
-
       systemd.services.podman-prune = {
         description = "Prune podman resources";
 
@@ -205,10 +203,6 @@ in
       systemd.sockets.podman.wantedBy = [ "sockets.target" ];
       systemd.sockets.podman.socketConfig.SocketGroup = "podman";
 
-      systemd.user.services.podman.serviceConfig = {
-        ExecStart = [ "" "${cfg.package}/bin/podman $LOGGING system service" ];
-      };
-
       systemd.user.sockets.podman.wantedBy = [ "sockets.target" ];
 
       systemd.tmpfiles.packages = [
diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix
index 06210529eb8c4..a55a21a46a538 100644
--- a/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixos/modules/virtualisation/qemu-vm.nix
@@ -108,9 +108,9 @@ let
 
       set -e
 
-      NIX_DISK_IMAGE=$(readlink -f "''${NIX_DISK_IMAGE:-${config.virtualisation.diskImage}}")
+      NIX_DISK_IMAGE=$(readlink -f "''${NIX_DISK_IMAGE:-${toString config.virtualisation.diskImage}}") || test -z "$NIX_DISK_IMAGE"
 
-      if ! test -e "$NIX_DISK_IMAGE"; then
+      if test -n "$NIX_DISK_IMAGE" && ! test -e "$NIX_DISK_IMAGE"; then
           ${qemu}/bin/qemu-img create -f qcow2 "$NIX_DISK_IMAGE" \
             ${toString config.virtualisation.diskSize}M
       fi
@@ -152,9 +152,11 @@ let
 
       ${lib.optionalString cfg.useBootLoader
       ''
-        # Create a writable copy/snapshot of the boot disk.
-        # A writable boot disk can be booted from automatically.
-        ${qemu}/bin/qemu-img create -f qcow2 -F qcow2 -b ${bootDisk}/disk.img "$TMPDIR/disk.img"
+        if ${if !cfg.persistBootDevice then "true" else "! test -e $TMPDIR/disk.img"}; then
+          # Create a writable copy/snapshot of the boot disk.
+          # A writable boot disk can be booted from automatically.
+          ${qemu}/bin/qemu-img create -f qcow2 -F qcow2 -b ${bootDisk}/disk.img "$TMPDIR/disk.img"
+        fi
 
         NIX_EFI_VARS=$(readlink -f "''${NIX_EFI_VARS:-${cfg.efiVars}}")
 
@@ -346,7 +348,7 @@ in
 
     virtualisation.diskImage =
       mkOption {
-        type = types.str;
+        type = types.nullOr types.str;
         default = "./${config.system.name}.qcow2";
         defaultText = literalExpression ''"./''${config.system.name}.qcow2"'';
         description =
@@ -354,6 +356,9 @@ in
             Path to the disk image containing the root filesystem.
             The image will be created on startup if it does not
             exist.
+
+            If null, a tmpfs will be used as the root filesystem and
+            the VM's state will not be persistent.
           '';
       };
 
@@ -367,6 +372,17 @@ in
           '';
       };
 
+    virtualisation.persistBootDevice =
+      mkOption {
+        type = types.bool;
+        default = false;
+        description =
+          lib.mdDoc ''
+            If useBootLoader is specified, whether to recreate the boot device
+            on each instantiaton or allow it to persist.
+            '';
+      };
+
     virtualisation.emptyDiskImages =
       mkOption {
         type = types.listOf types.ints.positive;
@@ -850,6 +866,8 @@ in
     # * The disks are attached in `virtualisation.qemu.drives`.
     #   Their order makes them appear as devices `a`, `b`, etc.
     # * `fileSystems."/boot"` is adjusted to be on device `b`.
+    # * The disk.img is recreated each time the VM is booted unless
+    #   virtualisation.persistBootDevice is set.
 
     # If `useBootLoader`, GRUB goes to the second disk, see
     # note [Disk layout with `useBootLoader`].
@@ -892,7 +910,7 @@ in
 
         ${optionalString cfg.writableStore ''
           echo "mounting overlay filesystem on /nix/store..."
-          mkdir -p 0755 $targetRoot/nix/.rw-store/store $targetRoot/nix/.rw-store/work $targetRoot/nix/store
+          mkdir -p -m 0755 $targetRoot/nix/.rw-store/store $targetRoot/nix/.rw-store/work $targetRoot/nix/store
           mount -t overlay overlay $targetRoot/nix/store \
             -o lowerdir=$targetRoot/nix/.ro-store,upperdir=$targetRoot/nix/.rw-store/store,workdir=$targetRoot/nix/.rw-store/work || fail
         ''}
@@ -990,12 +1008,12 @@ in
     ];
 
     virtualisation.qemu.drives = mkMerge [
-      [{
+      (mkIf (cfg.diskImage != null) [{
         name = "root";
         file = ''"$NIX_DISK_IMAGE"'';
         driveExtraOpts.cache = "writeback";
         driveExtraOpts.werror = "report";
-      }]
+      }])
       (mkIf cfg.useNixStoreImage [{
         name = "nix-store";
         file = ''"$TMPDIR"/store.img'';
@@ -1018,20 +1036,21 @@ in
       }) cfg.emptyDiskImages)
     ];
 
+    fileSystems = mkVMOverride cfg.fileSystems;
+
     # Mount the host filesystem via 9P, and bind-mount the Nix store
     # of the host into our own filesystem.  We use mkVMOverride to
     # allow this module to be applied to "normal" NixOS system
     # configuration, where the regular value for the `fileSystems'
     # attribute should be disregarded for the purpose of building a VM
     # test image (since those filesystems don't exist in the VM).
-    fileSystems =
-    let
+    virtualisation.fileSystems = let
       mkSharedDir = tag: share:
         {
           name =
             if tag == "nix-store" && cfg.writableStore
-              then "/nix/.ro-store"
-              else share.target;
+            then "/nix/.ro-store"
+            else share.target;
           value.device = tag;
           value.fsType = "9p";
           value.neededForBoot = true;
@@ -1039,44 +1058,42 @@ in
             [ "trans=virtio" "version=9p2000.L"  "msize=${toString cfg.msize}" ]
             ++ lib.optional (tag == "nix-store") "cache=loose";
         };
-    in
-      mkVMOverride (cfg.fileSystems //
-      optionalAttrs cfg.useDefaultFilesystems {
-        "/".device = cfg.bootDevice;
-        "/".fsType = "ext4";
-        "/".autoFormat = true;
-      } //
-      optionalAttrs config.boot.tmpOnTmpfs {
-        "/tmp" = {
+    in lib.mkMerge [
+      (lib.mapAttrs' mkSharedDir cfg.sharedDirectories)
+      {
+        "/" = lib.mkIf cfg.useDefaultFilesystems (if cfg.diskImage == null then {
+          device = "tmpfs";
+          fsType = "tmpfs";
+        } else {
+          device = cfg.bootDevice;
+          fsType = "ext4";
+          autoFormat = true;
+        });
+        "/tmp" = lib.mkIf config.boot.tmpOnTmpfs {
           device = "tmpfs";
           fsType = "tmpfs";
           neededForBoot = true;
           # Sync with systemd's tmp.mount;
           options = [ "mode=1777" "strictatime" "nosuid" "nodev" "size=${toString config.boot.tmpOnTmpfsSize}" ];
         };
-      } //
-      optionalAttrs cfg.useNixStoreImage {
-        "/nix/${if cfg.writableStore then ".ro-store" else "store"}" = {
+        "/nix/${if cfg.writableStore then ".ro-store" else "store"}" = lib.mkIf cfg.useNixStoreImage {
           device = "${lookupDriveDeviceName "nix-store" cfg.qemu.drives}";
           neededForBoot = true;
           options = [ "ro" ];
         };
-      } //
-      optionalAttrs (cfg.writableStore && cfg.writableStoreUseTmpfs) {
-        "/nix/.rw-store" = {
+        "/nix/.rw-store" = lib.mkIf (cfg.writableStore && cfg.writableStoreUseTmpfs) {
           fsType = "tmpfs";
           options = [ "mode=0755" ];
           neededForBoot = true;
         };
-      } //
-      optionalAttrs cfg.useBootLoader {
         # see note [Disk layout with `useBootLoader`]
-        "/boot" = {
+        "/boot" = lib.mkIf cfg.useBootLoader {
           device = "${lookupDriveDeviceName "boot" cfg.qemu.drives}2"; # 2 for e.g. `vdb2`, as created in `bootDisk`
           fsType = "vfat";
           noCheck = true; # fsck fails on a r/o filesystem
         };
-      } // lib.mapAttrs' mkSharedDir cfg.sharedDirectories);
+      }
+    ];
 
     boot.initrd.systemd = lib.mkIf (config.boot.initrd.systemd.enable && cfg.writableStore) {
       mounts = [{
@@ -1084,18 +1101,20 @@ in
         what = "overlay";
         type = "overlay";
         options = "lowerdir=/sysroot/nix/.ro-store,upperdir=/sysroot/nix/.rw-store/store,workdir=/sysroot/nix/.rw-store/work";
-        wantedBy = ["local-fs.target"];
-        before = ["local-fs.target"];
-        requires = ["sysroot-nix-.ro\\x2dstore.mount" "sysroot-nix-.rw\\x2dstore.mount" "rw-store.service"];
-        after = ["sysroot-nix-.ro\\x2dstore.mount" "sysroot-nix-.rw\\x2dstore.mount" "rw-store.service"];
-        unitConfig.IgnoreOnIsolate = true;
+        wantedBy = ["initrd-fs.target"];
+        before = ["initrd-fs.target"];
+        requires = ["rw-store.service"];
+        after = ["rw-store.service"];
+        unitConfig.RequiresMountsFor = "/sysroot/nix/.ro-store";
       }];
       services.rw-store = {
-        after = ["sysroot-nix-.rw\\x2dstore.mount"];
-        unitConfig.DefaultDependencies = false;
+        unitConfig = {
+          DefaultDependencies = false;
+          RequiresMountsFor = "/sysroot/nix/.rw-store";
+        };
         serviceConfig = {
           Type = "oneshot";
-          ExecStart = "/bin/mkdir -p 0755 /sysroot/nix/.rw-store/store /sysroot/nix/.rw-store/work /sysroot/nix/store";
+          ExecStart = "/bin/mkdir -p -m 0755 /sysroot/nix/.rw-store/store /sysroot/nix/.rw-store/work /sysroot/nix/store";
         };
       };
     };
diff --git a/nixos/modules/virtualisation/virtualbox-image.nix b/nixos/modules/virtualisation/virtualbox-image.nix
index bb42e6de069bd..0da217fd1cb0c 100644
--- a/nixos/modules/virtualisation/virtualbox-image.nix
+++ b/nixos/modules/virtualisation/virtualbox-image.nix
@@ -81,7 +81,7 @@ in {
       extraDisk = mkOption {
         description = lib.mdDoc ''
           Optional extra disk/hdd configuration.
-          The disk will be an 'ext4' partition on a separate VMDK file.
+          The disk will be an 'ext4' partition on a separate file.
         '';
         default = null;
         example = {
@@ -183,8 +183,8 @@ in {
           export HOME=$PWD
           export PATH=${pkgs.virtualbox}/bin:$PATH
 
-          echo "creating VirtualBox pass-through disk wrapper (no copying involved)..."
-          VBoxManage internalcommands createrawvmdk -filename disk.vmdk -rawdisk $diskImage
+          echo "converting image to VirtualBox format..."
+          VBoxManage convertfromraw $diskImage disk.vdi
 
           ${optionalString (cfg.extraDisk != null) ''
             echo "creating extra disk: data-disk.raw"
@@ -196,8 +196,8 @@ in {
               mkpart primary ext4 1MiB -1
             eval $(partx $dataDiskImage -o START,SECTORS --nr 1 --pairs)
             mkfs.ext4 -F -L ${cfg.extraDisk.label} $dataDiskImage -E offset=$(sectorsToBytes $START) $(sectorsToKilobytes $SECTORS)K
-            echo "creating extra disk: data-disk.vmdk"
-            VBoxManage internalcommands createrawvmdk -filename data-disk.vmdk -rawdisk $dataDiskImage
+            echo "creating extra disk: data-disk.vdi"
+            VBoxManage convertfromraw $dataDiskImage data-disk.vdi
           ''}
 
           echo "creating VirtualBox VM..."
@@ -209,10 +209,10 @@ in {
             ${lib.cli.toGNUCommandLineShell { } cfg.params}
           VBoxManage storagectl "$vmName" ${lib.cli.toGNUCommandLineShell { } cfg.storageController}
           VBoxManage storageattach "$vmName" --storagectl ${cfg.storageController.name} --port 0 --device 0 --type hdd \
-            --medium disk.vmdk
+            --medium disk.vdi
           ${optionalString (cfg.extraDisk != null) ''
             VBoxManage storageattach "$vmName" --storagectl ${cfg.storageController.name} --port 1 --device 0 --type hdd \
-            --medium data-disk.vmdk
+            --medium data-disk.vdi
           ''}
 
           echo "exporting VirtualBox VM..."
diff --git a/nixos/release-combined.nix b/nixos/release-combined.nix
index ed698b63ee63d..125086294d410 100644
--- a/nixos/release-combined.nix
+++ b/nixos/release-combined.nix
@@ -100,7 +100,6 @@ in rec {
         (onFullSupported "nixos.tests.login")
         (onFullSupported "nixos.tests.misc")
         (onFullSupported "nixos.tests.mutableUsers")
-        (onFullSupported "nixos.tests.nat.firewall-conntrack")
         (onFullSupported "nixos.tests.nat.firewall")
         (onFullSupported "nixos.tests.nat.standalone")
         (onFullSupported "nixos.tests.networking.scripted.bond")
@@ -131,8 +130,7 @@ in rec {
         (onFullSupported "nixos.tests.networking.networkd.virtual")
         (onFullSupported "nixos.tests.networking.networkd.vlan")
         (onFullSupported "nixos.tests.systemd-networkd-ipv6-prefix-delegation")
-        # fails with kernel >= 5.15 https://github.com/NixOS/nixpkgs/pull/152505#issuecomment-1005049314
-        #(onFullSupported "nixos.tests.nfs3.simple")
+        (onFullSupported "nixos.tests.nfs3.simple")
         (onFullSupported "nixos.tests.nfs4.simple")
         (onSystems ["x86_64-linux"] "nixos.tests.oci-containers.podman")
         (onFullSupported "nixos.tests.openssh")
diff --git a/nixos/release-small.nix b/nixos/release-small.nix
index deb428d1bec05..2b553adf2bd60 100644
--- a/nixos/release-small.nix
+++ b/nixos/release-small.nix
@@ -1,7 +1,11 @@
 # This jobset is used to generate a NixOS channel that contains a
 # small subset of Nixpkgs, mostly useful for servers that need fast
 # security updates.
-
+#
+# Individual jobs can be tested by running:
+#
+#   nix-build nixos/release-small.nix -A <jobname>
+#
 { nixpkgs ? { outPath = (import ../lib).cleanSource ./..; revCount = 56789; shortRev = "gfedcba"; }
 , stableBranch ? false
 , supportedSystems ? [ "aarch64-linux" "x86_64-linux" ] # no i686-linux
@@ -39,8 +43,7 @@ in rec {
         login
         misc
         nat
-        # fails with kernel >= 5.15 https://github.com/NixOS/nixpkgs/pull/152505#issuecomment-1005049314
-        #nfs3
+        nfs3
         openssh
         php
         predictable-interface-names
@@ -119,11 +122,9 @@ in rec {
         "nixos.tests.ipv6"
         "nixos.tests.login"
         "nixos.tests.misc"
-        "nixos.tests.nat.firewall-conntrack"
         "nixos.tests.nat.firewall"
         "nixos.tests.nat.standalone"
-        # fails with kernel >= 5.15 https://github.com/NixOS/nixpkgs/pull/152505#issuecomment-1005049314
-        #"nixos.tests.nfs3.simple"
+        "nixos.tests.nfs3.simple"
         "nixos.tests.openssh"
         "nixos.tests.php.fpm"
         "nixos.tests.php.pcre"
diff --git a/nixos/release.nix b/nixos/release.nix
index 946379bcd6611..78a74af41242f 100644
--- a/nixos/release.nix
+++ b/nixos/release.nix
@@ -144,7 +144,6 @@ in rec {
   manual = manualHTML; # TODO(@oxij): remove eventually
   manualEpub = (buildFromConfig ({ ... }: { }) (config: config.system.build.manual.manualEpub));
   manpages = buildFromConfig ({ ... }: { }) (config: config.system.build.manual.manpages);
-  manualGeneratedSources = buildFromConfig ({ ... }: { }) (config: config.system.build.manual.generatedSources);
   options = (buildFromConfig ({ ... }: { }) (config: config.system.build.manual.optionsJSON)).x86_64-linux;
 
 
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index ced2687ed9a43..2c34a3996d0bc 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -108,6 +108,7 @@ in {
   breitbandmessung = handleTest ./breitbandmessung.nix {};
   brscan5 = handleTest ./brscan5.nix {};
   btrbk = handleTest ./btrbk.nix {};
+  btrbk-doas = handleTest ./btrbk-doas.nix {};
   btrbk-no-timer = handleTest ./btrbk-no-timer.nix {};
   btrbk-section-order = handleTest ./btrbk-section-order.nix {};
   buildbot = handleTest ./buildbot.nix {};
@@ -125,6 +126,7 @@ in {
   ceph-single-node-bluestore = handleTestOn ["x86_64-linux"] ./ceph-single-node-bluestore.nix {};
   certmgr = handleTest ./certmgr.nix {};
   cfssl = handleTestOn ["aarch64-linux" "x86_64-linux"] ./cfssl.nix {};
+  cgit = handleTest ./cgit.nix {};
   charliecloud = handleTest ./charliecloud.nix {};
   chromium = (handleTestOn ["aarch64-linux" "x86_64-linux"] ./chromium.nix {}).stable or {};
   chrony-ptp = handleTestOn ["aarch64-linux" "x86_64-linux"] ./chrony-ptp.nix {};
@@ -137,6 +139,7 @@ in {
   cntr = handleTestOn ["aarch64-linux" "x86_64-linux"] ./cntr.nix {};
   cockpit = handleTest ./cockpit.nix {};
   cockroachdb = handleTestOn ["x86_64-linux"] ./cockroachdb.nix {};
+  coder = handleTest ./coder.nix {};
   collectd = handleTest ./collectd.nix {};
   connman = handleTest ./connman.nix {};
   consul = handleTest ./consul.nix {};
@@ -228,20 +231,24 @@ in {
   fluentd = handleTest ./fluentd.nix {};
   fluidd = handleTest ./fluidd.nix {};
   fontconfig-default-fonts = handleTest ./fontconfig-default-fonts.nix {};
+  forgejo = handleTest ./gitea.nix { giteaPackage = pkgs.forgejo; };
   freenet = handleTest ./freenet.nix {};
   freeswitch = handleTest ./freeswitch.nix {};
   freshrss-sqlite = handleTest ./freshrss-sqlite.nix {};
   freshrss-pgsql = handleTest ./freshrss-pgsql.nix {};
   frr = handleTest ./frr.nix {};
   fsck = handleTest ./fsck.nix {};
+  fsck-systemd-stage-1 = handleTest ./fsck.nix { systemdStage1 = true; };
   ft2-clone = handleTest ./ft2-clone.nix {};
   mimir = handleTest ./mimir.nix {};
   garage = handleTest ./garage {};
+  gemstash = handleTest ./gemstash.nix {};
   gerrit = handleTest ./gerrit.nix {};
   geth = handleTest ./geth.nix {};
   ghostunnel = handleTest ./ghostunnel.nix {};
   gitdaemon = handleTest ./gitdaemon.nix {};
-  gitea = handleTest ./gitea.nix {};
+  gitea = handleTest ./gitea.nix { giteaPackage = pkgs.gitea; };
+  github-runner = handleTest ./github-runner.nix {};
   gitlab = handleTest ./gitlab.nix {};
   gitolite = handleTest ./gitolite.nix {};
   gitolite-fcgiwrap = handleTest ./gitolite-fcgiwrap.nix {};
@@ -306,6 +313,7 @@ in {
   initrd-network-ssh = handleTest ./initrd-network-ssh {};
   initrdNetwork = handleTest ./initrd-network.nix {};
   initrd-secrets = handleTest ./initrd-secrets.nix {};
+  initrd-secrets-changing = handleTest ./initrd-secrets-changing.nix {};
   input-remapper = handleTest ./input-remapper.nix {};
   inspircd = handleTest ./inspircd.nix {};
   installer = handleTest ./installer.nix {};
@@ -338,6 +346,7 @@ in {
   keter = handleTest ./keter.nix {};
   kexec = handleTest ./kexec.nix {};
   keycloak = discoverTests (import ./keycloak.nix);
+  keyd = handleTest ./keyd.nix {};
   keymap = handleTest ./keymap.nix {};
   knot = handleTest ./knot.nix {};
   komga = handleTest ./komga.nix {};
@@ -368,6 +377,7 @@ in {
   login = handleTest ./login.nix {};
   logrotate = handleTest ./logrotate.nix {};
   loki = handleTest ./loki.nix {};
+  luks = handleTest ./luks.nix {};
   lvm2 = handleTest ./lvm2 {};
   lxd = handleTest ./lxd.nix {};
   lxd-nftables = handleTest ./lxd-nftables.nix {};
@@ -431,10 +441,8 @@ in {
   nagios = handleTest ./nagios.nix {};
   nar-serve = handleTest ./nar-serve.nix {};
   nat.firewall = handleTest ./nat.nix { withFirewall = true; };
-  nat.firewall-conntrack = handleTest ./nat.nix { withFirewall = true; withConntrackHelpers = true; };
   nat.standalone = handleTest ./nat.nix { withFirewall = false; };
   nat.nftables.firewall = handleTest ./nat.nix { withFirewall = true; nftables = true; };
-  nat.nftables.firewall-conntrack = handleTest ./nat.nix { withFirewall = true; withConntrackHelpers = true; nftables = true; };
   nat.nftables.standalone = handleTest ./nat.nix { withFirewall = false; nftables = true; };
   nats = handleTest ./nats.nix {};
   navidrome = handleTest ./navidrome.nix {};
@@ -480,6 +488,7 @@ in {
   nomad = handleTest ./nomad.nix {};
   non-default-filesystems = handleTest ./non-default-filesystems.nix {};
   noto-fonts = handleTest ./noto-fonts.nix {};
+  noto-fonts-cjk-qt-default-weight = handleTest ./noto-fonts-cjk-qt-default-weight.nix {};
   novacomd = handleTestOn ["x86_64-linux"] ./novacomd.nix {};
   nscd = handleTest ./nscd.nix {};
   nsd = handleTest ./nsd.nix {};
@@ -490,6 +499,7 @@ in {
   ombi = handleTest ./ombi.nix {};
   openarena = handleTest ./openarena.nix {};
   openldap = handleTest ./openldap.nix {};
+  opensearch = discoverTests (import ./opensearch.nix);
   openresty-lua = handleTest ./openresty-lua.nix {};
   opensmtpd = handleTest ./opensmtpd.nix {};
   opensmtpd-rspamd = handleTest ./opensmtpd-rspamd.nix {};
@@ -520,6 +530,7 @@ in {
   peerflix = handleTest ./peerflix.nix {};
   peering-manager = handleTest ./web-apps/peering-manager.nix {};
   peertube = handleTestOn ["x86_64-linux"] ./web-apps/peertube.nix {};
+  peroxide = handleTest ./peroxide.nix {};
   pgadmin4 = handleTest ./pgadmin4.nix {};
   pgjwt = handleTest ./pgjwt.nix {};
   pgmanage = handleTest ./pgmanage.nix {};
@@ -578,6 +589,7 @@ in {
   radarr = handleTest ./radarr.nix {};
   radicale = handleTest ./radicale.nix {};
   rasdaemon = handleTest ./rasdaemon.nix {};
+  readarr = handleTest ./readarr.nix {};
   redis = handleTest ./redis.nix {};
   redmine = handleTest ./redmine.nix {};
   restartByActivationScript = handleTest ./restart-by-activation-script.nix {};
@@ -601,6 +613,7 @@ in {
   searx = handleTest ./searx.nix {};
   service-runner = handleTest ./service-runner.nix {};
   sfxr-qt = handleTest ./sfxr-qt.nix {};
+  sgtpuzzles = handleTest ./sgtpuzzles.nix {};
   shadow = handleTest ./shadow.nix {};
   shadowsocks = handleTest ./shadowsocks {};
   shattered-pixel-dungeon = handleTest ./shattered-pixel-dungeon.nix {};
@@ -614,7 +627,6 @@ in {
   soapui = handleTest ./soapui.nix {};
   sogo = handleTest ./sogo.nix {};
   solanum = handleTest ./solanum.nix {};
-  solr = handleTest ./solr.nix {};
   sonarr = handleTest ./sonarr.nix {};
   sourcehut = handleTest ./sourcehut.nix {};
   spacecookie = handleTest ./spacecookie.nix {};
@@ -645,6 +657,7 @@ in {
   systemd-confinement = handleTest ./systemd-confinement.nix {};
   systemd-coredump = handleTest ./systemd-coredump.nix {};
   systemd-cryptenroll = handleTest ./systemd-cryptenroll.nix {};
+  systemd-credentials-tpm2 = handleTest ./systemd-credentials-tpm2.nix {};
   systemd-escaping = handleTest ./systemd-escaping.nix {};
   systemd-initrd-btrfs-raid = handleTest ./systemd-initrd-btrfs-raid.nix {};
   systemd-initrd-luks-fido2 = handleTest ./systemd-initrd-luks-fido2.nix {};
@@ -655,6 +668,7 @@ in {
   systemd-initrd-shutdown = handleTest ./systemd-shutdown.nix { systemdStage1 = true; };
   systemd-initrd-simple = handleTest ./systemd-initrd-simple.nix {};
   systemd-initrd-swraid = handleTest ./systemd-initrd-swraid.nix {};
+  systemd-initrd-vconsole = handleTest ./systemd-initrd-vconsole.nix {};
   systemd-journal = handleTest ./systemd-journal.nix {};
   systemd-machinectl = handleTest ./systemd-machinectl.nix {};
   systemd-networkd = handleTest ./systemd-networkd.nix {};
@@ -683,6 +697,7 @@ in {
   terminal-emulators = handleTest ./terminal-emulators.nix {};
   tiddlywiki = handleTest ./tiddlywiki.nix {};
   tigervnc = handleTest ./tigervnc.nix {};
+  timescaledb = handleTest ./timescaledb.nix {};
   timezone = handleTest ./timezone.nix {};
   tinc = handleTest ./tinc {};
   tinydns = handleTest ./tinydns.nix {};
diff --git a/nixos/tests/atuin.nix b/nixos/tests/atuin.nix
index 85213d1e53eac..2bc5494f55565 100644
--- a/nixos/tests/atuin.nix
+++ b/nixos/tests/atuin.nix
@@ -54,7 +54,7 @@ with lib;
     client.execute("echo 'sync_address = \"http://server:${toString testPort}\"' > ~/.config/atuin/config.toml")
 
     # log in to atuin server on client node
-    client.succeed(f"${atuin}/bin/atuin login -u ${testUser} -p ${testPass} -k {key}")
+    client.succeed(f"${atuin}/bin/atuin login -u ${testUser} -p ${testPass} -k \"{key}\"")
 
     # pull records from atuin server
     client.succeed("${atuin}/bin/atuin sync -f")
diff --git a/nixos/tests/btrbk-doas.nix b/nixos/tests/btrbk-doas.nix
new file mode 100644
index 0000000000000..1e3f8d56addb9
--- /dev/null
+++ b/nixos/tests/btrbk-doas.nix
@@ -0,0 +1,114 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+  let
+    privateKey = ''
+      -----BEGIN OPENSSH PRIVATE KEY-----
+      b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+      QyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrwAAAJB+cF5HfnBe
+      RwAAAAtzc2gtZWQyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrw
+      AAAEBN75NsJZSpt63faCuaD75Unko0JjlSDxMhYHAPJk2/xXHxQHThDpD9/AMWNqQer3Tg
+      9gXMb2lTZMn0pelo8xyvAAAADXJzY2h1ZXR6QGt1cnQ=
+      -----END OPENSSH PRIVATE KEY-----
+    '';
+    publicKey = ''
+      ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHHxQHThDpD9/AMWNqQer3Tg9gXMb2lTZMn0pelo8xyv
+    '';
+  in
+  {
+    name = "btrbk-doas";
+    meta = with pkgs.lib; {
+      maintainers = with maintainers; [ symphorien tu-maurice ];
+    };
+
+    nodes = {
+      archive = { ... }: {
+        security.sudo.enable = false;
+        security.doas.enable = true;
+        environment.systemPackages = with pkgs; [ btrfs-progs ];
+        # note: this makes the privateKey world readable.
+        # don't do it with real ssh keys.
+        environment.etc."btrbk_key".text = privateKey;
+        services.btrbk = {
+          extraPackages = [ pkgs.lz4 ];
+          instances = {
+            remote = {
+              onCalendar = "minutely";
+              settings = {
+                ssh_identity = "/etc/btrbk_key";
+                ssh_user = "btrbk";
+                stream_compress = "lz4";
+                volume = {
+                  "ssh://main/mnt" = {
+                    target = "/mnt";
+                    snapshot_dir = "btrbk/remote";
+                    subvolume = "to_backup";
+                  };
+                };
+              };
+            };
+          };
+        };
+      };
+
+      main = { ... }: {
+        security.sudo.enable = false;
+        security.doas.enable = true;
+        environment.systemPackages = with pkgs; [ btrfs-progs ];
+        services.openssh = {
+          enable = true;
+          passwordAuthentication = false;
+          kbdInteractiveAuthentication = false;
+        };
+        services.btrbk = {
+          extraPackages = [ pkgs.lz4 ];
+          sshAccess = [
+            {
+              key = publicKey;
+              roles = [ "source" "send" "info" "delete" ];
+            }
+          ];
+          instances = {
+            local = {
+              onCalendar = "minutely";
+              settings = {
+                volume = {
+                  "/mnt" = {
+                    snapshot_dir = "btrbk/local";
+                    subvolume = "to_backup";
+                  };
+                };
+              };
+            };
+          };
+        };
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      # create btrfs partition at /mnt
+      for machine in (archive, main):
+        machine.succeed("dd if=/dev/zero of=/data_fs bs=120M count=1")
+        machine.succeed("mkfs.btrfs /data_fs")
+        machine.succeed("mkdir /mnt")
+        machine.succeed("mount /data_fs /mnt")
+
+      # what to backup and where
+      main.succeed("btrfs subvolume create /mnt/to_backup")
+      main.succeed("mkdir -p /mnt/btrbk/{local,remote}")
+
+      # check that local snapshots work
+      with subtest("local"):
+          main.succeed("echo foo > /mnt/to_backup/bar")
+          main.wait_until_succeeds("cat /mnt/btrbk/local/*/bar | grep foo")
+          main.succeed("echo bar > /mnt/to_backup/bar")
+          main.succeed("cat /mnt/btrbk/local/*/bar | grep foo")
+
+      # check that btrfs send/receive works and ssh access works
+      with subtest("remote"):
+          archive.wait_until_succeeds("cat /mnt/*/bar | grep bar")
+          main.succeed("echo baz > /mnt/to_backup/bar")
+          archive.succeed("cat /mnt/*/bar | grep bar")
+    '';
+  })
diff --git a/nixos/tests/cgit.nix b/nixos/tests/cgit.nix
new file mode 100644
index 0000000000000..6aed06adefdff
--- /dev/null
+++ b/nixos/tests/cgit.nix
@@ -0,0 +1,73 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+let
+  robotsTxt = pkgs.writeText "cgit-robots.txt" ''
+    User-agent: *
+    Disallow: /
+  '';
+in {
+  name = "cgit";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ schnusch ];
+  };
+
+  nodes = {
+    server = { ... }: {
+      services.cgit."localhost" = {
+        enable = true;
+        package = pkgs.cgit.overrideAttrs ({ postInstall, ... }: {
+          postInstall = ''
+            ${postInstall}
+            cp ${robotsTxt} "$out/cgit/robots.txt"
+          '';
+        });
+        nginx.location = "/(c)git/";
+        repos = {
+          some-repo = {
+            path = "/srv/git/some-repo";
+            desc = "some-repo description";
+          };
+        };
+      };
+
+      environment.systemPackages = [ pkgs.git ];
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    start_all()
+
+    server.wait_for_unit("nginx.service")
+    server.wait_for_unit("network.target")
+    server.wait_for_open_port(80)
+
+    server.succeed("curl -fsS http://localhost/%28c%29git/cgit.css")
+
+    server.succeed("curl -fsS http://localhost/%28c%29git/robots.txt | diff -u - ${robotsTxt}")
+
+    server.succeed(
+        "curl -fsS http://localhost/%28c%29git/ | grep -F 'some-repo description'"
+    )
+
+    server.fail("curl -fsS http://localhost/robots.txt")
+
+    server.succeed("${pkgs.writeShellScript "setup-cgit-test-repo" ''
+      set -e
+      git init --bare -b master /srv/git/some-repo
+      git init -b master reference
+      cd reference
+      git remote add origin /srv/git/some-repo
+      date > date.txt
+      git add date.txt
+      git -c user.name=test -c user.email=test@localhost commit -m 'add date'
+      git push -u origin master
+    ''}")
+
+    server.succeed(
+        "curl -fsS 'http://localhost/%28c%29git/some-repo/plain/date.txt?id=master' | diff -u reference/date.txt -"
+    )
+
+    server.succeed(
+       "git clone http://localhost/%28c%29git/some-repo && diff -u reference/date.txt some-repo/date.txt"
+    )
+  '';
+})
diff --git a/nixos/tests/clickhouse.nix b/nixos/tests/clickhouse.nix
index 043263ec05dd9..77d6a7ab8be42 100644
--- a/nixos/tests/clickhouse.nix
+++ b/nixos/tests/clickhouse.nix
@@ -1,6 +1,6 @@
 import ./make-test-python.nix ({ pkgs, ... }: {
   name = "clickhouse";
-  meta.maintainers = with pkgs.lib.maintainers; [ ma27 ];
+  meta.maintainers = with pkgs.lib.maintainers; [ ];
 
   nodes.machine = {
     services.clickhouse.enable = true;
diff --git a/nixos/tests/coder.nix b/nixos/tests/coder.nix
new file mode 100644
index 0000000000000..12813827284b9
--- /dev/null
+++ b/nixos/tests/coder.nix
@@ -0,0 +1,24 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "coder";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ shyim ghuntley ];
+  };
+
+  nodes.machine =
+    { pkgs, ... }:
+    {
+      services.coder = {
+        enable = true;
+        accessUrl = "http://localhost:3000";
+      };
+    };
+
+  testScript = ''
+    machine.start()
+    machine.wait_for_unit("postgresql.service")
+    machine.wait_for_unit("coder.service")
+    machine.wait_for_open_port(3000)
+
+    machine.succeed("curl --fail http://localhost:3000")
+  '';
+})
diff --git a/nixos/tests/discourse.nix b/nixos/tests/discourse.nix
index 35ca083c6c4e0..c79ba41c2eb9c 100644
--- a/nixos/tests/discourse.nix
+++ b/nixos/tests/discourse.nix
@@ -40,7 +40,7 @@ import ./make-test-python.nix (
 
         networking.extraHosts = ''
           127.0.0.1 ${discourseDomain}
-          ${nodes.client.config.networking.primaryIPAddress} ${clientDomain}
+          ${nodes.client.networking.primaryIPAddress} ${clientDomain}
         '';
 
         services.postfix = {
@@ -90,7 +90,7 @@ import ./make-test-python.nix (
 
         networking.extraHosts = ''
           127.0.0.1 ${clientDomain}
-          ${nodes.discourse.config.networking.primaryIPAddress} ${discourseDomain}
+          ${nodes.discourse.networking.primaryIPAddress} ${discourseDomain}
         '';
 
         services.dovecot2 = {
@@ -178,8 +178,8 @@ import ./make-test-python.nix (
         discourse.wait_until_succeeds("curl -sS -f https://${discourseDomain}")
         discourse.succeed(
             "curl -sS -f https://${discourseDomain}/session/csrf -c cookie -b cookie -H 'Accept: application/json' | jq -r '\"X-CSRF-Token: \" + .csrf' > csrf_token",
-            "curl -sS -f https://${discourseDomain}/session -c cookie -b cookie -H @csrf_token -H 'Accept: application/json' -d 'login=${nodes.discourse.config.services.discourse.admin.username}' -d \"password=${adminPassword}\" | jq -e '.user.username == \"${nodes.discourse.config.services.discourse.admin.username}\"'",
-            "curl -sS -f https://${discourseDomain}/login -v -H 'Accept: application/json' -c cookie -b cookie 2>&1 | grep ${nodes.discourse.config.services.discourse.admin.username}",
+            "curl -sS -f https://${discourseDomain}/session -c cookie -b cookie -H @csrf_token -H 'Accept: application/json' -d 'login=${nodes.discourse.services.discourse.admin.username}' -d \"password=${adminPassword}\" | jq -e '.user.username == \"${nodes.discourse.services.discourse.admin.username}\"'",
+            "curl -sS -f https://${discourseDomain}/login -v -H 'Accept: application/json' -c cookie -b cookie 2>&1 | grep ${nodes.discourse.services.discourse.admin.username}",
         )
 
         client.wait_for_unit("postfix.service")
diff --git a/nixos/tests/docker-tools.nix b/nixos/tests/docker-tools.nix
index 98704ecb2fb65..44b583ebcea55 100644
--- a/nixos/tests/docker-tools.nix
+++ b/nixos/tests/docker-tools.nix
@@ -1,6 +1,52 @@
 # this test creates a simple GNU image with docker tools and sees if it executes
 
-import ./make-test-python.nix ({ pkgs, ... }: {
+import ./make-test-python.nix ({ pkgs, ... }:
+let
+  # nixpkgs#214434: dockerTools.buildImage fails to unpack base images
+  # containing duplicate layers when those duplicate tarballs
+  # appear under the manifest's 'Layers'. Docker can generate images
+  # like this even though dockerTools does not.
+  repeatedLayerTestImage =
+    let
+      # Rootfs diffs for layers 1 and 2 are identical (and empty)
+      layer1 = pkgs.dockerTools.buildImage {  name = "empty";  };
+      layer2 = layer1.overrideAttrs (_: { fromImage = layer1; });
+      repeatedRootfsDiffs = pkgs.runCommandNoCC "image-with-links.tar" {
+        nativeBuildInputs = [pkgs.jq];
+      } ''
+        mkdir contents
+        tar -xf "${layer2}" -C contents
+        cd contents
+        first_rootfs=$(jq -r '.[0].Layers[0]' manifest.json)
+        second_rootfs=$(jq -r '.[0].Layers[1]' manifest.json)
+        target_rootfs=$(sha256sum "$first_rootfs" | cut -d' ' -f 1).tar
+
+        # Replace duplicated rootfs diffs with symlinks to one tarball
+        chmod -R ug+w .
+        mv "$first_rootfs" "$target_rootfs"
+        rm "$second_rootfs"
+        ln -s "../$target_rootfs" "$first_rootfs"
+        ln -s "../$target_rootfs" "$second_rootfs"
+
+        # Update manifest's layers to use the symlinks' target
+        cat manifest.json | \
+        jq ".[0].Layers[0] = \"$target_rootfs\"" |
+        jq ".[0].Layers[1] = \"$target_rootfs\"" > manifest.json.new
+        mv manifest.json.new manifest.json
+
+        tar --sort=name --hard-dereference -cf $out .
+        '';
+    in pkgs.dockerTools.buildImage {
+      fromImage = repeatedRootfsDiffs;
+      name = "repeated-layer-test";
+      tag = "latest";
+      copyToRoot = pkgs.bash;
+      # A runAsRoot script is required to force previous layers to be unpacked
+      runAsRoot = ''
+        echo 'runAsRoot has run.'
+      '';
+    };
+in {
   name = "docker-tools";
   meta = with pkgs.lib.maintainers; {
     maintainers = [ lnl7 roberth ];
@@ -221,6 +267,12 @@ import ./make-test-python.nix ({ pkgs, ... }: {
             "docker run --rm ${examples.layersUnpackOrder.imageName} cat /layer-order"
         )
 
+    with subtest("Ensure repeated base layers handled by buildImage"):
+        docker.succeed(
+            "docker load --input='${repeatedLayerTestImage}'",
+            "docker run --rm ${repeatedLayerTestImage.imageName} /bin/bash -c 'exit 0'"
+        )
+
     with subtest("Ensure environment variables are correctly inherited"):
         docker.succeed(
             "docker load --input='${examples.environmentVariables}'"
diff --git a/nixos/tests/fsck.nix b/nixos/tests/fsck.nix
index 5b8b09f433a22..ccb664be080c6 100644
--- a/nixos/tests/fsck.nix
+++ b/nixos/tests/fsck.nix
@@ -1,3 +1,9 @@
+{ system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../.. { inherit system config; }
+, systemdStage1 ? false
+}:
+
 import ./make-test-python.nix {
   name = "fsck";
 
@@ -11,13 +17,17 @@ import ./make-test-python.nix {
         autoFormat = true;
       };
     };
+
+    boot.initrd.systemd.enable = systemdStage1;
   };
 
   testScript = ''
     machine.wait_for_unit("default.target")
 
     with subtest("root fs is fsckd"):
-        machine.succeed("journalctl -b | grep 'fsck.ext4.*/dev/vda'")
+        machine.succeed("journalctl -b | grep '${if systemdStage1
+          then "fsck.*vda.*clean"
+          else "fsck.ext4.*/dev/vda"}'")
 
     with subtest("mnt fs is fsckd"):
         machine.succeed("journalctl -b | grep 'fsck.*/dev/vdb.*clean'")
diff --git a/nixos/tests/garage/default.nix b/nixos/tests/garage/default.nix
index 4c38ea1bc898e..0a1ccde056b28 100644
--- a/nixos/tests/garage/default.nix
+++ b/nixos/tests/garage/default.nix
@@ -49,5 +49,5 @@ in
   })
   {}
   [
-    "0_8_0"
+    "0_8"
   ]
diff --git a/nixos/tests/gemstash.nix b/nixos/tests/gemstash.nix
new file mode 100644
index 0000000000000..bc152e42e92eb
--- /dev/null
+++ b/nixos/tests/gemstash.nix
@@ -0,0 +1,51 @@
+{ system ? builtins.currentSystem, config ? { }
+, pkgs ? import ../.. { inherit system config; } }:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let common_meta = { maintainers = [ maintainers.viraptor ]; };
+in
+{
+  gemstash_works = makeTest {
+    name = "gemstash-works";
+    meta = common_meta;
+
+    nodes.machine = { config, pkgs, ... }: {
+      services.gemstash = {
+        enable = true;
+      };
+    };
+
+    # gemstash responds to http requests
+    testScript = ''
+      machine.wait_for_unit("gemstash.service")
+      machine.wait_for_file("/var/lib/gemstash")
+      machine.wait_for_open_port(9292)
+      machine.succeed("curl http://localhost:9292")
+    '';
+  };
+
+  gemstash_custom_port = makeTest {
+    name = "gemstash-custom-port";
+    meta = common_meta;
+
+    nodes.machine = { config, pkgs, ... }: {
+      services.gemstash = {
+        enable = true;
+        openFirewall = true;
+        settings = {
+          bind = "tcp://0.0.0.0:12345";
+        };
+      };
+    };
+
+    # gemstash responds to http requests
+    testScript = ''
+      machine.wait_for_unit("gemstash.service")
+      machine.wait_for_file("/var/lib/gemstash")
+      machine.wait_for_open_port(12345)
+      machine.succeed("curl http://localhost:12345")
+    '';
+  };
+}
diff --git a/nixos/tests/gitea.nix b/nixos/tests/gitea.nix
index 68a2566c11911..86d4fce379291 100644
--- a/nixos/tests/gitea.nix
+++ b/nixos/tests/gitea.nix
@@ -1,5 +1,6 @@
 { system ? builtins.currentSystem,
   config ? {},
+  giteaPackage ? pkgs.gitea,
   pkgs ? import ../.. { inherit system config; }
 }:
 
@@ -7,10 +8,25 @@ with import ../lib/testing-python.nix { inherit system pkgs; };
 with pkgs.lib;
 
 let
+  ## gpg --faked-system-time='20230301T010000!' --quick-generate-key snakeoil ed25519 sign
+  signingPrivateKey = ''
+    -----BEGIN PGP PRIVATE KEY BLOCK-----
+
+    lFgEY/6jkBYJKwYBBAHaRw8BAQdADXiZRV8RJUyC9g0LH04wLMaJL9WTc+szbMi7
+    5fw4yP8AAQCl8EwGfzSLm/P6fCBfA3I9znFb3MEHGCCJhJ6VtKYyRw7ktAhzbmFr
+    ZW9pbIiUBBMWCgA8FiEE+wUM6VW/NLtAdSixTWQt6LZ4x50FAmP+o5ACGwMFCQPC
+    ZwAECwkIBwQVCgkIBRYCAwEAAh4FAheAAAoJEE1kLei2eMedFTgBAKQs1oGFZrCI
+    TZP42hmBTKxGAI1wg7VSdDEWTZxut/2JAQDGgo2sa4VHMfj0aqYGxrIwfP2B7JHO
+    GCqGCRf9O/hzBA==
+    =9Uy3
+    -----END PGP PRIVATE KEY BLOCK-----
+  '';
+  signingPrivateKeyId = "4D642DE8B678C79D";
+
   supportedDbTypes = [ "mysql" "postgres" "sqlite3" ];
   makeGiteaTest = type: nameValuePair type (makeTest {
-    name = "gitea-${type}";
-    meta.maintainers = with maintainers; [ aanderse kolaente ma27 ];
+    name = "${giteaPackage.pname}-${type}";
+    meta.maintainers = with maintainers; [ aanderse indeednotjames kolaente ma27 ];
 
     nodes = {
       server = { config, pkgs, ... }: {
@@ -18,9 +34,11 @@ let
         services.gitea = {
           enable = true;
           database = { inherit type; };
+          package = giteaPackage;
           settings.service.DISABLE_REGISTRATION = true;
+          settings."repository.signing".SIGNING_KEY = signingPrivateKeyId;
         };
-        environment.systemPackages = [ pkgs.gitea pkgs.jq ];
+        environment.systemPackages = [ giteaPackage pkgs.gnupg pkgs.jq ];
         services.openssh.enable = true;
       };
       client1 = { config, pkgs, ... }: {
@@ -57,6 +75,13 @@ let
       server.succeed("curl --fail http://localhost:3000/")
 
       server.succeed(
+          "su -l gitea -c 'gpg --homedir /var/lib/gitea/data/home/.gnupg "
+          + "--import ${toString (pkgs.writeText "gitea.key" signingPrivateKey)}'"
+      )
+
+      assert "BEGIN PGP PUBLIC KEY BLOCK" in server.succeed("curl http://localhost:3000/api/v1/signing-key.gpg")
+
+      server.succeed(
           "curl --fail http://localhost:3000/user/sign_up | grep 'Registration is disabled. "
           + "Please contact your site administrator.'"
       )
@@ -68,7 +93,7 @@ let
       api_token = server.succeed(
           "curl --fail -X POST http://test:totallysafe@localhost:3000/api/v1/users/test/tokens "
           + "-H 'Accept: application/json' -H 'Content-Type: application/json' -d "
-          + "'{\"name\":\"token\"}' | jq '.sha1' | xargs echo -n"
+          + "'{\"name\":\"token\",\"scopes\":[\"all\"]}' | jq '.sha1' | xargs echo -n"
       )
 
       server.succeed(
diff --git a/nixos/tests/github-runner.nix b/nixos/tests/github-runner.nix
new file mode 100644
index 0000000000000..033365d6925cf
--- /dev/null
+++ b/nixos/tests/github-runner.nix
@@ -0,0 +1,37 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+{
+  name = "github-runner";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ veehaitch ];
+  };
+  nodes.machine = { pkgs, ... }: {
+    services.github-runners.test = {
+      enable = true;
+      url = "https://github.com/yaxitech";
+      tokenFile = builtins.toFile "github-runner.token" "not-so-secret";
+    };
+
+    systemd.services.dummy-github-com = {
+      wantedBy = [ "multi-user.target" ];
+      before = [ "github-runner-test.service" ];
+      script = "${pkgs.netcat}/bin/nc -Fl 443 | true && touch /tmp/registration-connect";
+    };
+    networking.hosts."127.0.0.1" = [ "api.github.com" ];
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("dummy-github-com")
+
+    try:
+      machine.wait_for_unit("github-runner-test")
+    except Exception:
+      pass
+
+    out = machine.succeed("journalctl -u github-runner-test")
+    assert "Self-hosted runner registration" in out, "did not read runner registration header"
+
+    machine.wait_until_succeeds("test -f /tmp/registration-connect")
+  '';
+})
diff --git a/nixos/tests/gitlab.nix b/nixos/tests/gitlab.nix
index 59dbc99052a60..c2a11bada0a35 100644
--- a/nixos/tests/gitlab.nix
+++ b/nixos/tests/gitlab.nix
@@ -69,6 +69,10 @@ in {
         databasePasswordFile = pkgs.writeText "dbPassword" "xo0daiF4";
         initialRootPasswordFile = pkgs.writeText "rootPassword" initialRootPassword;
         smtp.enable = true;
+        pages = {
+          enable = true;
+          settings.pages-domain = "localhost";
+        };
         extraConfig = {
           incoming_email = {
             enabled = true;
@@ -79,11 +83,6 @@ in {
             host = "localhost";
             port = 143;
           };
-          # https://github.com/NixOS/nixpkgs/issues/132295
-          # pages = {
-          #   enabled = true;
-          #   host = "localhost";
-          # };
         };
         secrets = {
           secretFile = pkgs.writeText "secret" "Aig5zaic";
@@ -171,10 +170,9 @@ in {
       waitForServices = ''
         gitlab.wait_for_unit("gitaly.service")
         gitlab.wait_for_unit("gitlab-workhorse.service")
-        # https://github.com/NixOS/nixpkgs/issues/132295
-        # gitlab.wait_for_unit("gitlab-pages.service")
         gitlab.wait_for_unit("gitlab-mailroom.service")
         gitlab.wait_for_unit("gitlab.service")
+        gitlab.wait_for_unit("gitlab-pages.service")
         gitlab.wait_for_unit("gitlab-sidekiq.service")
         gitlab.wait_for_file("${nodes.gitlab.config.services.gitlab.statePath}/tmp/sockets/gitlab.socket")
         gitlab.wait_until_succeeds("curl -sSf http://gitlab/users/sign_in")
diff --git a/nixos/tests/gollum.nix b/nixos/tests/gollum.nix
index 833db87f2f326..44d373e35262b 100644
--- a/nixos/tests/gollum.nix
+++ b/nixos/tests/gollum.nix
@@ -9,6 +9,6 @@ import ./make-test-python.nix ({ pkgs, ... }: {
 
   testScript = { nodes, ... }: ''
     webserver.wait_for_unit("gollum")
-    webserver.wait_for_open_port(${toString nodes.webserver.config.services.gollum.port})
+    webserver.wait_for_open_port(${toString nodes.webserver.services.gollum.port})
   '';
 })
diff --git a/nixos/tests/google-oslogin/server.py b/nixos/tests/google-oslogin/server.py
index 5ea9bbd2c96ba..622cd86b26195 100755
--- a/nixos/tests/google-oslogin/server.py
+++ b/nixos/tests/google-oslogin/server.py
@@ -103,6 +103,16 @@ class ReqHandler(BaseHTTPRequestHandler):
             self._send_json_ok(gen_mockuser(username=username, uid=uid, gid=uid, home_directory=f"/home/{username}", snakeoil_pubkey=SNAKEOIL_PUBLIC_KEY))
             return
 
+        # we need to provide something at the groups endpoint.
+        # the nss module does segfault if we don't.
+        elif pu.path == "/computeMetadata/v1/oslogin/groups":
+            self._send_json_ok({
+                "posixGroups": [
+                    {"name" : "demo", "gid" : 4294967295}
+                ],
+            })
+            return
+
         # authorize endpoint
         elif pu.path == "/computeMetadata/v1/oslogin/authorize":
             # is user allowed to login?
diff --git a/nixos/tests/hadoop/hbase.nix b/nixos/tests/hadoop/hbase.nix
index d9d2dac0f6585..0416345682a89 100644
--- a/nixos/tests/hadoop/hbase.nix
+++ b/nixos/tests/hadoop/hbase.nix
@@ -53,6 +53,24 @@ with pkgs.lib;
         };
       };
     };
+    thrift = { ... }:{
+      services.hadoop = {
+        inherit coreSite;
+        hbase = {
+          inherit zookeeperQuorum;
+          thrift = defOpts;
+        };
+      };
+    };
+    rest = { ... }:{
+      services.hadoop = {
+        inherit coreSite;
+        hbase = {
+          inherit zookeeperQuorum;
+          rest = defOpts;
+        };
+      };
+    };
   };
 
   testScript = ''
@@ -80,5 +98,12 @@ with pkgs.lib;
     assert "1 active master, 0 backup masters, 1 servers" in master.succeed("echo status | HADOOP_USER_NAME=hbase hbase shell -n")
     regionserver.wait_until_succeeds("echo \"create 't1','f1'\" | HADOOP_USER_NAME=hbase hbase shell -n")
     assert "NAME => 'f1'" in regionserver.succeed("echo \"describe 't1'\" | HADOOP_USER_NAME=hbase hbase shell -n")
+
+    rest.wait_for_open_port(8080)
+    assert "${hbase.version}" in regionserver.succeed("curl http://rest:8080/version/cluster")
+
+    thrift.wait_for_open_port(9090)
   '';
+
+  meta.maintainers = with maintainers; [ illustris ];
 })
diff --git a/nixos/tests/haproxy.nix b/nixos/tests/haproxy.nix
index b6ff4102fe683..555474d7f2999 100644
--- a/nixos/tests/haproxy.nix
+++ b/nixos/tests/haproxy.nix
@@ -2,7 +2,6 @@ import ./make-test-python.nix ({ pkgs, ...}: {
   name = "haproxy";
   nodes = {
     machine = { ... }: {
-      imports = [ ../modules/profiles/minimal.nix ];
       services.haproxy = {
         enable = true;
         config = ''
diff --git a/nixos/tests/home-assistant.nix b/nixos/tests/home-assistant.nix
index 8d58de75eabc3..8585cb3585fef 100644
--- a/nixos/tests/home-assistant.nix
+++ b/nixos/tests/home-assistant.nix
@@ -22,22 +22,23 @@ in {
       enable = true;
       inherit configDir;
 
-      # tests loading components by overriding the package
+      # provide dependencies through package overrides
       package = (pkgs.home-assistant.override {
         extraPackages = ps: with ps; [
           colorama
         ];
-        extraComponents = [ "zha" ];
-      }).overrideAttrs (oldAttrs: {
-        doInstallCheck = false;
+        extraComponents = [
+          # test char-tty device allow propagation into the service
+          "zha"
+         ];
       });
 
-      # tests loading components from the module
+      # provide component dependencies explicitly from the module
       extraComponents = [
-        "wake_on_lan"
+        "mqtt"
       ];
 
-      # test extra package passing from the module
+      # provide package for postgresql support
       extraPackages = python3Packages: with python3Packages; [
         psycopg2
       ];
@@ -111,36 +112,38 @@ in {
   };
 
   testScript = { nodes, ... }: let
-    system = nodes.hass.config.system.build.toplevel;
+    system = nodes.hass.system.build.toplevel;
   in
   ''
-    import re
     import json
 
     start_all()
 
-    # Parse the package path out of the systemd unit, as we cannot
-    # access the final package, that is overridden inside the module,
-    # by any other means.
-    pattern = re.compile(r"path=(?P<path>[\/a-z0-9-.]+)\/bin\/hass")
-    response = hass.execute("systemctl show -p ExecStart home-assistant.service")[1]
-    match = pattern.search(response)
-    assert match
-    package = match.group('path')
 
-
-    def get_journal_cursor(host) -> str:
-        exit, out = host.execute("journalctl -u home-assistant.service -n1 -o json-pretty --output-fields=__CURSOR")
+    def get_journal_cursor() -> str:
+        exit, out = hass.execute("journalctl -u home-assistant.service -n1 -o json-pretty --output-fields=__CURSOR")
         assert exit == 0
         return json.loads(out)["__CURSOR"]
 
 
-    def wait_for_homeassistant(host, cursor):
-        host.wait_until_succeeds(f"journalctl --after-cursor='{cursor}' -u home-assistant.service | grep -q 'Home Assistant initialized in'")
+    def get_journal_since(cursor) -> str:
+        exit, out = hass.execute(f"journalctl --after-cursor='{cursor}' -u home-assistant.service")
+        assert exit == 0
+        return out
+
+
+    def get_unit_property(property) -> str:
+        exit, out = hass.execute(f"systemctl show --property={property} home-assistant.service")
+        assert exit == 0
+        return out
+
+
+    def wait_for_homeassistant(cursor):
+        hass.wait_until_succeeds(f"journalctl --after-cursor='{cursor}' -u home-assistant.service | grep -q 'Home Assistant initialized in'")
 
 
     hass.wait_for_unit("home-assistant.service")
-    cursor = get_journal_cursor(hass)
+    cursor = get_journal_cursor()
 
     with subtest("Check that YAML configuration file is in place"):
         hass.succeed("test -L ${configDir}/configuration.yaml")
@@ -148,19 +151,22 @@ in {
     with subtest("Check the lovelace config is copied because lovelaceConfigWritable = true"):
         hass.succeed("test -f ${configDir}/ui-lovelace.yaml")
 
-    with subtest("Check extraComponents and extraPackages are considered from the package"):
-        hass.succeed(f"grep -q 'colorama' {package}/extra_packages")
-        hass.succeed(f"grep -q 'zha' {package}/extra_components")
-
-    with subtest("Check extraComponents and extraPackages are considered from the module"):
-        hass.succeed(f"grep -q 'psycopg2' {package}/extra_packages")
-        hass.succeed(f"grep -q 'wake_on_lan' {package}/extra_components")
-
     with subtest("Check that Home Assistant's web interface and API can be reached"):
-        wait_for_homeassistant(hass, cursor)
+        wait_for_homeassistant(cursor)
         hass.wait_for_open_port(8123)
         hass.succeed("curl --fail http://localhost:8123/lovelace")
 
+    with subtest("Check that optional dependencies are in the PYTHONPATH"):
+        env = get_unit_property("Environment")
+        python_path = env.split("PYTHONPATH=")[1].split()[0]
+        for package in ["colorama", "paho-mqtt", "psycopg2"]:
+            assert package in python_path, f"{package} not in PYTHONPATH"
+
+    with subtest("Check that declaratively configured components get setup"):
+        journal = get_journal_since(cursor)
+        for domain in ["emulated_hue", "wake_on_lan"]:
+            assert f"Setup of domain {domain} took" in journal, f"{domain} setup missing"
+
     with subtest("Check that capabilities are passed for emulated_hue to bind to port 80"):
         hass.wait_for_open_port(80)
         hass.succeed("curl --fail http://localhost:80/description.xml")
@@ -169,25 +175,28 @@ in {
         hass.succeed("systemctl show -p DeviceAllow home-assistant.service | grep -q char-ttyUSB")
 
     with subtest("Check service reloads when configuration changes"):
-      # store the old pid of the process
-      pid = hass.succeed("systemctl show --property=MainPID home-assistant.service")
-      cursor = get_journal_cursor(hass)
-      hass.succeed("${system}/specialisation/differentName/bin/switch-to-configuration test")
-      new_pid = hass.succeed("systemctl show --property=MainPID home-assistant.service")
-      assert pid == new_pid, "The PID of the process should not change between process reloads"
-      wait_for_homeassistant(hass, cursor)
-
-    with subtest("check service restarts when package changes"):
-      pid = new_pid
-      cursor = get_journal_cursor(hass)
-      hass.succeed("${system}/specialisation/newFeature/bin/switch-to-configuration test")
-      new_pid = hass.succeed("systemctl show --property=MainPID home-assistant.service")
-      assert pid != new_pid, "The PID of the process shoudl change when the HA binary changes"
-      wait_for_homeassistant(hass, cursor)
+        pid = hass.succeed("systemctl show --property=MainPID home-assistant.service")
+        cursor = get_journal_cursor()
+        hass.succeed("${system}/specialisation/differentName/bin/switch-to-configuration test")
+        new_pid = hass.succeed("systemctl show --property=MainPID home-assistant.service")
+        assert pid == new_pid, "The PID of the process should not change between process reloads"
+        wait_for_homeassistant(cursor)
+
+    with subtest("Check service restarts when dependencies change"):
+        pid = new_pid
+        cursor = get_journal_cursor()
+        hass.succeed("${system}/specialisation/newFeature/bin/switch-to-configuration test")
+        new_pid = hass.succeed("systemctl show --property=MainPID home-assistant.service")
+        assert pid != new_pid, "The PID of the process should change when its PYTHONPATH changess"
+        wait_for_homeassistant(cursor)
+
+    with subtest("Check that new components get setup after restart"):
+        journal = get_journal_since(cursor)
+        for domain in ["esphome"]:
+            assert f"Setup of domain {domain} took" in journal, f"{domain} setup missing"
 
     with subtest("Check that no errors were logged"):
-        output_log = hass.succeed("cat ${configDir}/home-assistant.log")
-        assert "ERROR" not in output_log
+        hass.fail("journalctl -u home-assistant -o cat | grep -q ERROR")
 
     with subtest("Check systemd unit hardening"):
         hass.log(hass.succeed("systemctl cat home-assistant.service"))
diff --git a/nixos/tests/hostname.nix b/nixos/tests/hostname.nix
index 1de8f19267af8..6122e2ffeb83a 100644
--- a/nixos/tests/hostname.nix
+++ b/nixos/tests/hostname.nix
@@ -1,6 +1,6 @@
-{ system ? builtins.currentSystem,
-  config ? {},
-  pkgs ? import ../.. { inherit system config; }
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
 }:
 
 with import ../lib/testing-python.nix { inherit system pkgs; };
@@ -14,55 +14,55 @@ let
         let res = builtins.tryEval str;
         in if (res.success && res.value != null) then res.value else "null";
     in
-      makeTest {
-        name = "hostname-${fqdn}";
-        meta = with pkgs.lib.maintainers; {
-          maintainers = [ primeos blitz ];
-        };
+    makeTest {
+      name = "hostname-${fqdn}";
+      meta = with pkgs.lib.maintainers; {
+        maintainers = [ primeos blitz ];
+      };
 
-        nodes.machine = { lib, ... }: {
-          networking.hostName = hostName;
-          networking.domain = domain;
+      nodes.machine = { lib, ... }: {
+        networking.hostName = hostName;
+        networking.domain = domain;
 
-          environment.systemPackages = with pkgs; [
-            inetutils
-          ];
-        };
+        environment.systemPackages = with pkgs; [
+          inetutils
+        ];
+      };
 
-        testScript = { nodes, ... }: ''
-          start_all()
+      testScript = { nodes, ... }: ''
+        start_all()
 
-          machine = ${hostName}
+        machine = ${hostName}
 
-          machine.wait_for_unit("network-online.target")
+        machine.wait_for_unit("network-online.target")
 
-          # Test if NixOS computes the correct FQDN (either a FQDN or an error/null):
-          assert "${getStr nodes.machine.config.networking.fqdn}" == "${getStr fqdnOrNull}"
+        # Test if NixOS computes the correct FQDN (either a FQDN or an error/null):
+        assert "${getStr nodes.machine.networking.fqdn}" == "${getStr fqdnOrNull}"
 
-          # The FQDN, domain name, and hostname detection should work as expected:
-          assert "${fqdn}" == machine.succeed("hostname --fqdn").strip()
-          assert "${optionalString (domain != null) domain}" == machine.succeed("dnsdomainname").strip()
-          assert (
-              "${hostName}"
-              == machine.succeed(
-                  'hostnamectl status | grep "Static hostname" | cut -d: -f2'
-              ).strip()
-          )
+        # The FQDN, domain name, and hostname detection should work as expected:
+        assert "${fqdn}" == machine.succeed("hostname --fqdn").strip()
+        assert "${optionalString (domain != null) domain}" == machine.succeed("dnsdomainname").strip()
+        assert (
+            "${hostName}"
+            == machine.succeed(
+                'hostnamectl status | grep "Static hostname" | cut -d: -f2'
+            ).strip()
+        )
 
-          # 127.0.0.1 and ::1 should resolve back to "localhost":
-          assert (
-              "localhost" == machine.succeed("getent hosts 127.0.0.1 | awk '{print $2}'").strip()
-          )
-          assert "localhost" == machine.succeed("getent hosts ::1 | awk '{print $2}'").strip()
+        # 127.0.0.1 and ::1 should resolve back to "localhost":
+        assert (
+            "localhost" == machine.succeed("getent hosts 127.0.0.1 | awk '{print $2}'").strip()
+        )
+        assert "localhost" == machine.succeed("getent hosts ::1 | awk '{print $2}'").strip()
 
-          # 127.0.0.2 should resolve back to the FQDN and hostname:
-          fqdn_and_host_name = "${optionalString (domain != null) "${hostName}.${domain} "}${hostName}"
-          assert (
-              fqdn_and_host_name
-              == machine.succeed("getent hosts 127.0.0.2 | awk '{print $2,$3}'").strip()
-          )
-        '';
-      };
+        # 127.0.0.2 should resolve back to the FQDN and hostname:
+        fqdn_and_host_name = "${optionalString (domain != null) "${hostName}.${domain} "}${hostName}"
+        assert (
+            fqdn_and_host_name
+            == machine.succeed("getent hosts 127.0.0.2 | awk '{print $2,$3}'").strip()
+        )
+      '';
+    };
 
 in
 {
diff --git a/nixos/tests/initrd-secrets-changing.nix b/nixos/tests/initrd-secrets-changing.nix
new file mode 100644
index 0000000000000..775c69d0142db
--- /dev/null
+++ b/nixos/tests/initrd-secrets-changing.nix
@@ -0,0 +1,58 @@
+{ system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../.. { inherit system config; }
+, lib ? pkgs.lib
+, testing ? import ../lib/testing-python.nix { inherit system pkgs; }
+}:
+
+let
+  secret1InStore = pkgs.writeText "topsecret" "iamasecret1";
+  secret2InStore = pkgs.writeText "topsecret" "iamasecret2";
+in
+
+testing.makeTest {
+  name = "initrd-secrets-changing";
+
+  nodes.machine = { ... }: {
+    virtualisation.useBootLoader = true;
+    virtualisation.persistBootDevice = true;
+
+    boot.loader.grub.device = "/dev/vda";
+
+    boot.initrd.secrets = {
+      "/test" = secret1InStore;
+      "/run/keys/test" = secret1InStore;
+    };
+    boot.initrd.postMountCommands = "cp /test /mnt-root/secret-from-initramfs";
+
+    specialisation.secrets2System.configuration = {
+      boot.initrd.secrets = lib.mkForce {
+        "/test" = secret2InStore;
+        "/run/keys/test" = secret2InStore;
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("multi-user.target")
+    print(machine.succeed("cat /run/keys/test"))
+    machine.succeed(
+        "cmp ${secret1InStore} /secret-from-initramfs",
+        "cmp ${secret1InStore} /run/keys/test",
+    )
+    # Select the second boot entry corresponding to the specialisation secrets2System.
+    machine.succeed("grub-reboot 1")
+    machine.shutdown()
+
+    with subtest("Check that the specialisation's secrets are distinct despite identical kernels"):
+        machine.wait_for_unit("multi-user.target")
+        print(machine.succeed("cat /run/keys/test"))
+        machine.succeed(
+            "cmp ${secret2InStore} /secret-from-initramfs",
+            "cmp ${secret2InStore} /run/keys/test",
+        )
+        machine.shutdown()
+  '';
+}
diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix
index 3adfa979edcc7..d441765fe194d 100644
--- a/nixos/tests/installer.nix
+++ b/nixos/tests/installer.nix
@@ -77,9 +77,9 @@ let
     let iface = if grubVersion == 1 then "ide" else "virtio";
         isEfi = bootLoader == "systemd-boot" || (bootLoader == "grub" && grubUseEfi);
         bios  = if pkgs.stdenv.isAarch64 then "QEMU_EFI.fd" else "OVMF.fd";
-    in if !isEfi && !pkgs.stdenv.hostPlatform.isx86 then
-      throw "Non-EFI boot methods are only supported on i686 / x86_64"
-    else ''
+    in if !isEfi && !pkgs.stdenv.hostPlatform.isx86 then ''
+      machine.succeed("true")
+    '' else ''
       def assemble_qemu_flags():
           flags = "-cpu max"
           ${if (system == "x86_64-linux" || system == "i686-linux")
diff --git a/nixos/tests/isso.nix b/nixos/tests/isso.nix
index 575e1c52eccf6..4ec8b5ec3593d 100644
--- a/nixos/tests/isso.nix
+++ b/nixos/tests/isso.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, ... }: {
   name = "isso";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ asbachb ];
+    maintainers = [ ];
   };
 
   nodes.machine = { config, pkgs, ... }: {
diff --git a/nixos/tests/k3s/default.nix b/nixos/tests/k3s/default.nix
index 07d93c41c7a68..e168f8233c763 100644
--- a/nixos/tests/k3s/default.nix
+++ b/nixos/tests/k3s/default.nix
@@ -1,9 +1,13 @@
 { system ? builtins.currentSystem
 , pkgs ? import ../../.. { inherit system; }
+, lib ? pkgs.lib
 }:
+let
+  allK3s = lib.filterAttrs (n: _: lib.strings.hasPrefix "k3s_" n) pkgs;
+in
 {
   # Run a single node k3s cluster and verify a pod can run
-  single-node = import ./single-node.nix { inherit system pkgs; };
+  single-node = lib.mapAttrs (_: k3s: import ./single-node.nix { inherit system pkgs k3s; }) allK3s;
   # Run a multi-node k3s cluster and verify pod networking works across nodes
-  multi-node = import ./multi-node.nix { inherit system pkgs; };
+  multi-node = lib.mapAttrs (_: k3s: import ./multi-node.nix { inherit system pkgs k3s; }) allK3s;
 }
diff --git a/nixos/tests/k3s/multi-node.nix b/nixos/tests/k3s/multi-node.nix
index 9a6c7fd465739..932b4639b39c8 100644
--- a/nixos/tests/k3s/multi-node.nix
+++ b/nixos/tests/k3s/multi-node.nix
@@ -1,4 +1,4 @@
-import ../make-test-python.nix ({ pkgs, lib, ... }:
+import ../make-test-python.nix ({ pkgs, lib, k3s, ... }:
   let
     imageEnv = pkgs.buildEnv {
       name = "k3s-pause-image-env";
@@ -39,7 +39,7 @@ import ../make-test-python.nix ({ pkgs, lib, ... }:
     tokenFile = pkgs.writeText "token" "p@s$w0rd";
   in
   {
-    name = "k3s-multi-node";
+    name = "${k3s.name}-multi-node";
 
     nodes = {
       server = { pkgs, ... }: {
@@ -52,7 +52,7 @@ import ../make-test-python.nix ({ pkgs, lib, ... }:
           inherit tokenFile;
           enable = true;
           role = "server";
-          package = pkgs.k3s;
+          package = k3s;
           clusterInit = true;
           extraFlags = builtins.toString [
             "--disable" "coredns"
diff --git a/nixos/tests/k3s/single-node.nix b/nixos/tests/k3s/single-node.nix
index a95fa4a031e3f..d61595d889e2a 100644
--- a/nixos/tests/k3s/single-node.nix
+++ b/nixos/tests/k3s/single-node.nix
@@ -1,4 +1,4 @@
-import ../make-test-python.nix ({ pkgs, lib, ... }:
+import ../make-test-python.nix ({ pkgs, lib, k3s, ... }:
   let
     imageEnv = pkgs.buildEnv {
       name = "k3s-pause-image-env";
@@ -24,7 +24,7 @@ import ../make-test-python.nix ({ pkgs, lib, ... }:
     '';
   in
   {
-    name = "k3s";
+    name = "${k3s.name}-single-node";
     meta = with pkgs.lib.maintainers; {
       maintainers = [ euank ];
     };
@@ -38,7 +38,7 @@ import ../make-test-python.nix ({ pkgs, lib, ... }:
 
       services.k3s.enable = true;
       services.k3s.role = "server";
-      services.k3s.package = pkgs.k3s;
+      services.k3s.package = k3s;
       # Slightly reduce resource usage
       services.k3s.extraFlags = builtins.toString [
         "--disable" "coredns"
@@ -77,6 +77,9 @@ import ../make-test-python.nix ({ pkgs, lib, ... }:
       machine.succeed("k3s kubectl wait --for 'condition=Ready' pod/test")
       machine.succeed("k3s kubectl delete -f ${testPodYaml}")
 
+      # regression test for #176445
+      machine.fail("journalctl -o cat -u k3s.service | grep 'ipset utility not found'")
+
       machine.shutdown()
     '';
   })
diff --git a/nixos/tests/kea.nix b/nixos/tests/kea.nix
index b1d5894cc7cd2..b4095893b4825 100644
--- a/nixos/tests/kea.nix
+++ b/nixos/tests/kea.nix
@@ -1,3 +1,10 @@
+# This test verifies DHCPv4 interaction between a client and a router.
+# For successful DHCP allocations a dynamic update request is sent
+# towards a nameserver to allocate a name in the lan.nixos.test zone.
+# We then verify whether client and router can ping each other, and
+# that the nameserver can resolve the clients fqdn to the correct IP
+# address.
+
 import ./make-test-python.nix ({ pkgs, lib, ...}: {
   meta.maintainers = with lib.maintainers; [ hexa ];
 
@@ -8,17 +15,17 @@ import ./make-test-python.nix ({ pkgs, lib, ...}: {
       virtualisation.vlans = [ 1 ];
 
       networking = {
-        useNetworkd = true;
         useDHCP = false;
         firewall.allowedUDPPorts = [ 67 ];
       };
 
       systemd.network = {
+        enable = true;
         networks = {
           "01-eth1" = {
             name = "eth1";
             networkConfig = {
-              Address = "10.0.0.1/30";
+              Address = "10.0.0.1/29";
             };
           };
         };
@@ -45,13 +52,115 @@ import ./make-test-python.nix ({ pkgs, lib, ...}: {
           };
 
           subnet4 = [ {
-            subnet = "10.0.0.0/30";
+            subnet = "10.0.0.0/29";
             pools = [ {
-              pool = "10.0.0.2 - 10.0.0.2";
+              pool = "10.0.0.3 - 10.0.0.3";
             } ];
           } ];
+
+          # Enable communication between dhcp4 and a local dhcp-ddns
+          # instance.
+          # https://kea.readthedocs.io/en/kea-2.2.0/arm/dhcp4-srv.html#ddns-for-dhcpv4
+          dhcp-ddns = {
+            enable-updates = true;
+          };
+
+          ddns-send-updates = true;
+          ddns-qualifying-suffix = "lan.nixos.test.";
         };
       };
+
+      services.kea.dhcp-ddns = {
+        enable = true;
+        settings = {
+          forward-ddns = {
+            # Configure updates of a forward zone named `lan.nixos.test`
+            # hosted at the nameserver at 10.0.0.2
+            # https://kea.readthedocs.io/en/kea-2.2.0/arm/ddns.html#adding-forward-dns-servers
+            ddns-domains = [ {
+              name = "lan.nixos.test.";
+              # Use a TSIG key in production!
+              key-name = "";
+              dns-servers = [ {
+                ip-address = "10.0.0.2";
+                port = 53;
+              } ];
+            } ];
+          };
+        };
+      };
+    };
+
+    nameserver = { config, pkgs, ... }: {
+      virtualisation.vlans = [ 1 ];
+
+      networking = {
+        useDHCP = false;
+        firewall.allowedUDPPorts = [ 53 ];
+      };
+
+      systemd.network = {
+        enable = true;
+        networks = {
+          "01-eth1" = {
+            name = "eth1";
+            networkConfig = {
+              Address = "10.0.0.2/29";
+            };
+          };
+        };
+      };
+
+      services.resolved.enable = false;
+
+      # Set up an authoritative nameserver, serving the `lan.nixos.test`
+      # zone and configure an ACL that allows dynamic updates from
+      # the router's ip address.
+      # This ACL is likely insufficient for production usage. Please
+      # use TSIG keys.
+      services.knot = let
+        zone = pkgs.writeTextDir "lan.nixos.test.zone" ''
+          @ SOA ns.nixos.test nox.nixos.test 0 86400 7200 3600000 172800
+          @ NS nameserver
+          nameserver A 10.0.0.3
+          router A 10.0.0.1
+        '';
+        zonesDir = pkgs.buildEnv {
+          name = "knot-zones";
+          paths = [ zone ];
+        };
+      in {
+        enable = true;
+        extraArgs = [
+          "-v"
+        ];
+        extraConfig = ''
+          server:
+              listen: 0.0.0.0@53
+
+          log:
+            - target: syslog
+              any: debug
+
+          acl:
+            - id: dhcp_ddns
+              address: 10.0.0.1
+              action: update
+
+          template:
+            - id: default
+              storage: ${zonesDir}
+              zonefile-sync: -1
+              zonefile-load: difference-no-serial
+              journal-content: all
+
+          zone:
+            - domain: lan.nixos.test
+              file: lan.nixos.test.zone
+              acl: [dhcp_ddns]
+        '';
+      };
+
     };
 
     client = { config, pkgs, ... }: {
@@ -70,6 +179,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...}: {
     router.wait_for_unit("kea-dhcp4-server.service")
     client.wait_for_unit("systemd-networkd-wait-online.service")
     client.wait_until_succeeds("ping -c 5 10.0.0.1")
-    router.wait_until_succeeds("ping -c 5 10.0.0.2")
+    router.wait_until_succeeds("ping -c 5 10.0.0.3")
+    nameserver.wait_until_succeeds("kdig +short client.lan.nixos.test @10.0.0.2 | grep -q 10.0.0.3")
   '';
 })
diff --git a/nixos/tests/keepassxc.nix b/nixos/tests/keepassxc.nix
index debb469032a62..a4f452412cdf8 100644
--- a/nixos/tests/keepassxc.nix
+++ b/nixos/tests/keepassxc.nix
@@ -4,6 +4,7 @@ import ./make-test-python.nix ({ pkgs, ...} :
   name = "keepassxc";
   meta = with pkgs.lib.maintainers; {
     maintainers = [ turion ];
+    timeout = 1800;
   };
 
   nodes.machine = { ... }:
@@ -55,9 +56,12 @@ import ./make-test-python.nix ({ pkgs, ...} :
         machine.sleep(5)
         # Regression #163482: keepassxc did not crash
         machine.succeed("ps -e | grep keepassxc")
-        machine.wait_for_text("foo.kdbx")
+        machine.wait_for_text("Open database")
         machine.send_key("ret")
-        machine.sleep(1)
+
+        # Wait for the enter password screen to appear.
+        machine.wait_for_text("/home/alice/foo.kdbx")
+
         # Click on "Browse" button to select keyfile
         machine.send_key("tab")
         machine.send_chars("/home/alice/foo.keyfile")
diff --git a/nixos/tests/keyd.nix b/nixos/tests/keyd.nix
new file mode 100644
index 0000000000000..d492cc194895c
--- /dev/null
+++ b/nixos/tests/keyd.nix
@@ -0,0 +1,82 @@
+# The test template is taken from the `./keymap.nix`
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+
+let
+  readyFile = "/tmp/readerReady";
+  resultFile = "/tmp/readerResult";
+
+  testReader = pkgs.writeScript "test-input-reader" ''
+    rm -f ${resultFile} ${resultFile}.tmp
+    logger "testReader: START: Waiting for $1 characters, expecting '$2'."
+    touch ${readyFile}
+    read -r -N $1 chars
+    rm -f ${readyFile}
+    if [ "$chars" == "$2" ]; then
+      logger -s "testReader: PASS: Got '$2' as expected." 2>${resultFile}.tmp
+    else
+      logger -s "testReader: FAIL: Expected '$2' but got '$chars'." 2>${resultFile}.tmp
+    fi
+    # rename after the file is written to prevent a race condition
+    mv  ${resultFile}.tmp ${resultFile}
+  '';
+
+
+  mkKeyboardTest = name: { settings, test }: with pkgs.lib; makeTest {
+    inherit name;
+
+    nodes.machine = {
+      services.keyd = {
+        enable = true;
+        inherit settings;
+      };
+    };
+
+    testScript = ''
+      import shlex
+
+      machine.wait_for_unit("keyd.service")
+
+      def run_test_case(cmd, test_case_name, inputs, expected):
+          with subtest(test_case_name):
+              assert len(inputs) == len(expected)
+              machine.execute("rm -f ${readyFile} ${resultFile}")
+              # set up process that expects all the keys to be entered
+              machine.succeed(
+                  "{} {} {} {} >&2 &".format(
+                      cmd,
+                      "${testReader}",
+                      len(inputs),
+                      shlex.quote("".join(expected)),
+                  )
+              )
+              # wait for reader to be ready
+              machine.wait_for_file("${readyFile}")
+              # send all keys
+              for key in inputs:
+                  machine.send_key(key)
+              # wait for result and check
+              machine.wait_for_file("${resultFile}")
+              machine.succeed("grep -q 'PASS:' ${resultFile}")
+      test = ${builtins.toJSON test}
+      run_test_case("openvt -sw --", "${name}", test["press"], test["expect"])
+    '';
+  };
+
+in
+pkgs.lib.mapAttrs mkKeyboardTest {
+  swap-ab_and_ctrl-as-shift = {
+    test.press = [ "a" "ctrl-b" "c" ];
+    test.expect = [ "b" "A" "c" ];
+
+    settings.main = {
+      "a" = "b";
+      "b" = "a";
+      "control" = "oneshot(shift)";
+    };
+  };
+}
diff --git a/nixos/tests/knot.nix b/nixos/tests/knot.nix
index 203fd03fac26f..2ecbf69194bb7 100644
--- a/nixos/tests/knot.nix
+++ b/nixos/tests/knot.nix
@@ -31,7 +31,7 @@ let
   # DO NOT USE pkgs.writeText IN PRODUCTION. This put secrets in the nix store!
   tsigFile = pkgs.writeText "tsig.conf" ''
     key:
-      - id: slave_key
+      - id: xfr_key
         algorithm: hmac-sha256
         secret: zOYgOgnzx3TGe5J5I/0kxd7gTcxXhLYMEq3Ek3fY37s=
   '';
@@ -43,7 +43,7 @@ in {
 
 
   nodes = {
-    master = { lib, ... }: {
+    primary = { lib, ... }: {
       imports = [ common ];
 
       # trigger sched_setaffinity syscall
@@ -64,22 +64,17 @@ in {
         server:
             listen: 0.0.0.0@53
             listen: ::@53
-
-        acl:
-          - id: slave_acl
-            address: 192.168.0.2
-            key: slave_key
-            action: transfer
+            automatic-acl: true
 
         remote:
-          - id: slave
+          - id: secondary
             address: 192.168.0.2@53
+            key: xfr_key
 
         template:
           - id: default
             storage: ${knotZonesEnv}
-            notify: [slave]
-            acl: [slave_acl]
+            notify: [secondary]
             dnssec-signing: on
             # Input-only zone files
             # https://www.knot-dns.cz/docs/2.8/html/operation.html#example-3
@@ -105,7 +100,7 @@ in {
       '';
     };
 
-    slave = { lib, ... }: {
+    secondary = { lib, ... }: {
       imports = [ common ];
       networking.interfaces.eth1 = {
         ipv4.addresses = lib.mkForce [
@@ -122,21 +117,16 @@ in {
         server:
             listen: 0.0.0.0@53
             listen: ::@53
-
-        acl:
-          - id: notify_from_master
-            address: 192.168.0.1
-            action: notify
+            automatic-acl: true
 
         remote:
-          - id: master
+          - id: primary
             address: 192.168.0.1@53
-            key: slave_key
+            key: xfr_key
 
         template:
           - id: default
-            master: master
-            acl: [notify_from_master]
+            master: primary
             # zonefileless setup
             # https://www.knot-dns.cz/docs/2.8/html/operation.html#example-2
             zonefile-sync: -1
@@ -174,19 +164,19 @@ in {
   };
 
   testScript = { nodes, ... }: let
-    master4 = (lib.head nodes.master.config.networking.interfaces.eth1.ipv4.addresses).address;
-    master6 = (lib.head nodes.master.config.networking.interfaces.eth1.ipv6.addresses).address;
+    primary4 = (lib.head nodes.primary.config.networking.interfaces.eth1.ipv4.addresses).address;
+    primary6 = (lib.head nodes.primary.config.networking.interfaces.eth1.ipv6.addresses).address;
 
-    slave4 = (lib.head nodes.slave.config.networking.interfaces.eth1.ipv4.addresses).address;
-    slave6 = (lib.head nodes.slave.config.networking.interfaces.eth1.ipv6.addresses).address;
+    secondary4 = (lib.head nodes.secondary.config.networking.interfaces.eth1.ipv4.addresses).address;
+    secondary6 = (lib.head nodes.secondary.config.networking.interfaces.eth1.ipv6.addresses).address;
   in ''
     import re
 
     start_all()
 
     client.wait_for_unit("network.target")
-    master.wait_for_unit("knot.service")
-    slave.wait_for_unit("knot.service")
+    primary.wait_for_unit("knot.service")
+    secondary.wait_for_unit("knot.service")
 
 
     def test(host, query_type, query, pattern):
@@ -195,7 +185,7 @@ in {
         assert re.search(pattern, out), f'Did not match "{pattern}"'
 
 
-    for host in ("${master4}", "${master6}", "${slave4}", "${slave6}"):
+    for host in ("${primary4}", "${primary6}", "${secondary4}", "${secondary6}"):
         with subtest(f"Interrogate {host}"):
             test(host, "SOA", "example.com", r"start of authority.*noc\.example\.com\.")
             test(host, "A", "example.com", r"has no [^ ]+ record")
@@ -211,6 +201,6 @@ in {
             test(host, "RRSIG", "www.example.com", r"RR set signature is")
             test(host, "DNSKEY", "example.com", r"DNSSEC key is")
 
-    master.log(master.succeed("systemd-analyze security knot.service | grep -v '✓'"))
+    primary.log(primary.succeed("systemd-analyze security knot.service | grep -v '✓'"))
   '';
 })
diff --git a/nixos/tests/login.nix b/nixos/tests/login.nix
index 2cff38d20059d..67f5764a0a162 100644
--- a/nixos/tests/login.nix
+++ b/nixos/tests/login.nix
@@ -13,6 +13,8 @@ import ./make-test-python.nix ({ pkgs, latestKernel ? false, ... }:
     };
 
   testScript = ''
+      machine.start(allow_reboot = True)
+
       machine.wait_for_unit("multi-user.target")
       machine.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
       machine.screenshot("postboot")
@@ -53,7 +55,14 @@ import ./make-test-python.nix ({ pkgs, latestKernel ? false, ... }:
           machine.screenshot("getty")
 
       with subtest("Check whether ctrl-alt-delete works"):
-          machine.send_key("ctrl-alt-delete")
-          machine.wait_for_shutdown()
+          boot_id1 = machine.succeed("cat /proc/sys/kernel/random/boot_id").strip()
+          assert boot_id1 != ""
+
+          machine.reboot()
+
+          boot_id2 = machine.succeed("cat /proc/sys/kernel/random/boot_id").strip()
+          assert boot_id2 != ""
+
+          assert boot_id1 != boot_id2
   '';
 })
diff --git a/nixos/tests/luks.nix b/nixos/tests/luks.nix
new file mode 100644
index 0000000000000..82f5095cb2602
--- /dev/null
+++ b/nixos/tests/luks.nix
@@ -0,0 +1,69 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "luks";
+
+  nodes.machine = { pkgs, ... }: {
+    # Use systemd-boot
+    virtualisation = {
+      emptyDiskImages = [ 512 512 ];
+      useBootLoader = true;
+      useEFIBoot = true;
+    };
+    boot.loader.systemd-boot.enable = true;
+
+    boot.kernelParams = lib.mkOverride 5 [ "console=tty1" ];
+
+    environment.systemPackages = with pkgs; [ cryptsetup ];
+
+    specialisation = rec {
+      boot-luks.configuration = {
+        boot.initrd.luks.devices = lib.mkVMOverride {
+          # We have two disks and only type one password - key reuse is in place
+          cryptroot.device = "/dev/vdc";
+          cryptroot2.device = "/dev/vdd";
+        };
+        virtualisation.bootDevice = "/dev/mapper/cryptroot";
+      };
+      boot-luks-custom-keymap.configuration = lib.mkMerge [
+        boot-luks.configuration
+        {
+          console.keyMap = "neo";
+        }
+      ];
+    };
+  };
+
+  enableOCR = true;
+
+  testScript = ''
+    # Create encrypted volume
+    machine.wait_for_unit("multi-user.target")
+    machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdc -")
+    machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdd -")
+
+    # Boot from the encrypted disk
+    machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks.conf")
+    machine.succeed("sync")
+    machine.crash()
+
+    # Boot and decrypt the disk
+    machine.start()
+    machine.wait_for_text("Passphrase for")
+    machine.send_chars("supersecret\n")
+    machine.wait_for_unit("multi-user.target")
+
+    assert "/dev/mapper/cryptroot on / type ext4" in machine.succeed("mount")
+
+    # Boot from the encrypted disk with custom keymap
+    machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks-custom-keymap.conf")
+    machine.succeed("sync")
+    machine.crash()
+
+    # Boot and decrypt the disk
+    machine.start()
+    machine.wait_for_text("Passphrase for")
+    machine.send_chars("havfkhfrkfl\n")
+    machine.wait_for_unit("multi-user.target")
+
+    assert "/dev/mapper/cryptroot on / type ext4" in machine.succeed("mount")
+  '';
+})
diff --git a/nixos/tests/maddy.nix b/nixos/tests/maddy.nix
index b9d0416482da1..800d254f17704 100644
--- a/nixos/tests/maddy.nix
+++ b/nixos/tests/maddy.nix
@@ -9,6 +9,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         hostname = "server";
         primaryDomain = "server";
         openFirewall = true;
+        ensureAccounts = [ "postmaster@server" ];
       };
     };
 
@@ -50,7 +51,6 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     server.wait_for_open_port(587)
 
     server.succeed("maddyctl creds create --password test postmaster@server")
-    server.succeed("maddyctl imap-acct create postmaster@server")
 
     client.succeed("send-testmail")
     client.succeed("test-imap")
diff --git a/nixos/tests/matrix/mjolnir.nix b/nixos/tests/matrix/mjolnir.nix
index b1ac55d951cef..c88113cb260b7 100644
--- a/nixos/tests/matrix/mjolnir.nix
+++ b/nixos/tests/matrix/mjolnir.nix
@@ -107,7 +107,10 @@ import ../make-test-python.nix (
       client = { pkgs, ... }: {
         environment.systemPackages = [
           (pkgs.writers.writePython3Bin "create_management_room_and_invite_mjolnir"
-            { libraries = [ pkgs.python3Packages.matrix-nio ]; } ''
+            { libraries = with pkgs.python3Packages; [
+                matrix-nio
+              ] ++ matrix-nio.optional-dependencies.e2e;
+            } ''
             import asyncio
 
             from nio import (
diff --git a/nixos/tests/minio.nix b/nixos/tests/minio.nix
index ad51f738d4901..ece4864f771c0 100644
--- a/nixos/tests/minio.nix
+++ b/nixos/tests/minio.nix
@@ -1,5 +1,5 @@
-import ./make-test-python.nix ({ pkgs, ...} :
-let
+import ./make-test-python.nix ({ pkgs, ... }:
+  let
     accessKey = "BKIKJAA5BMMU2RHO6IBB";
     secretKey = "V7f1CwQqAcwo80UEIJEjc5gVQUSSx5ohQ9GSrr12";
     minioPythonScript = pkgs.writeScript "minio-test.py" ''
@@ -18,41 +18,55 @@ let
       sio.seek(0)
       minioClient.put_object('test-bucket', 'test.txt', sio, sio_len, content_type='text/plain')
     '';
-in {
-  name = "minio";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ bachp ];
-  };
+    rootCredentialsFile = "/etc/nixos/minio-root-credentials";
+    credsPartial = pkgs.writeText "minio-credentials-partial" ''
+      MINIO_ROOT_USER=${accessKey}
+    '';
+    credsFull = pkgs.writeText "minio-credentials-full" ''
+      MINIO_ROOT_USER=${accessKey}
+      MINIO_ROOT_PASSWORD=${secretKey}
+    '';
+  in
+  {
+    name = "minio";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ bachp ];
+    };
 
-  nodes = {
-    machine = { pkgs, ... }: {
-      services.minio = {
-        enable = true;
-        rootCredentialsFile = pkgs.writeText "minio-credentials" ''
-          MINIO_ROOT_USER=${accessKey}
-          MINIO_ROOT_PASSWORD=${secretKey}
-        '';
-      };
-      environment.systemPackages = [ pkgs.minio-client ];
+    nodes = {
+      machine = { pkgs, ... }: {
+        services.minio = {
+          enable = true;
+          inherit rootCredentialsFile;
+        };
+        environment.systemPackages = [ pkgs.minio-client ];
 
-      # Minio requires at least 1GiB of free disk space to run.
-      virtualisation.diskSize = 4 * 1024;
+        # Minio requires at least 1GiB of free disk space to run.
+        virtualisation.diskSize = 4 * 1024;
+      };
     };
-  };
 
-  testScript = ''
-    start_all()
-    machine.wait_for_unit("minio.service")
-    machine.wait_for_open_port(9000)
+    testScript = ''
+      import time
+
+      start_all()
+      # simulate manually editing root credentials file
+      machine.wait_for_unit("multi-user.target")
+      machine.copy_from_host("${credsPartial}", "${rootCredentialsFile}")
+      time.sleep(3)
+      machine.copy_from_host("${credsFull}", "${rootCredentialsFile}")
 
-    # Create a test bucket on the server
-    machine.succeed(
-        "mc config host add minio http://localhost:9000 ${accessKey} ${secretKey} --api s3v4"
-    )
-    machine.succeed("mc mb minio/test-bucket")
-    machine.succeed("${minioPythonScript}")
-    assert "test-bucket" in machine.succeed("mc ls minio")
-    assert "Test from Python" in machine.succeed("mc cat minio/test-bucket/test.txt")
-    machine.shutdown()
-  '';
-})
+      machine.wait_for_unit("minio.service")
+      machine.wait_for_open_port(9000)
+
+      # Create a test bucket on the server
+      machine.succeed(
+          "mc config host add minio http://localhost:9000 ${accessKey} ${secretKey} --api s3v4"
+      )
+      machine.succeed("mc mb minio/test-bucket")
+      machine.succeed("${minioPythonScript}")
+      assert "test-bucket" in machine.succeed("mc ls minio")
+      assert "Test from Python" in machine.succeed("mc cat minio/test-bucket/test.txt")
+      machine.shutdown()
+    '';
+  })
diff --git a/nixos/tests/miriway.nix b/nixos/tests/miriway.nix
index c4c50646f0153..d0d9f16d40f95 100644
--- a/nixos/tests/miriway.nix
+++ b/nixos/tests/miriway.nix
@@ -3,7 +3,10 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
 
   meta = {
     maintainers = with lib.maintainers; [ OPNA2608 ];
-    # FIXME On ARM Miriway inside the VM doesn't receive keyboard inputs, why?
+    # Natively running Mir has problems with capturing the first registered libinput device.
+    # In our VM  runners on ARM and on some hardware configs (my RPi4, distro-independent), this misses the keyboard.
+    # It can be worked around by dis- and reconnecting the affected hardware, but we can't do this in these tests.
+    # https://github.com/MirServer/mir/issues/2837
     broken = pkgs.stdenv.hostPlatform.isAarch;
   };
 
@@ -30,6 +33,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
       enable = true;
       config = ''
         add-wayland-extensions=all
+        enable-x11=
 
         ctrl-alt=t:foot --maximized
         ctrl-alt=a:env WINIT_UNIX_BACKEND=x11 WAYLAND_DISPLAY=invalid alacritty --option window.startup_mode=maximized
diff --git a/nixos/tests/nat.nix b/nixos/tests/nat.nix
index 912a04deae8b3..0b617cea7774c 100644
--- a/nixos/tests/nat.nix
+++ b/nixos/tests/nat.nix
@@ -3,7 +3,7 @@
 # client on the inside network, a server on the outside network, and a
 # router connected to both that performs Network Address Translation
 # for the client.
-import ./make-test-python.nix ({ pkgs, lib, withFirewall, withConntrackHelpers ? false, nftables ? false, ... }:
+import ./make-test-python.nix ({ pkgs, lib, withFirewall, nftables ? false, ... }:
   let
     unit = if nftables then "nftables" else (if withFirewall then "firewall" else "nat");
 
@@ -16,16 +16,11 @@ import ./make-test-python.nix ({ pkgs, lib, withFirewall, withConntrackHelpers ?
           networking.nat.internalIPs = [ "192.168.1.0/24" ];
           networking.nat.externalInterface = "eth1";
         }
-        (lib.optionalAttrs withConntrackHelpers {
-          networking.firewall.connectionTrackingModules = [ "ftp" ];
-          networking.firewall.autoLoadConntrackHelpers = true;
-        })
       ];
   in
   {
     name = "nat" + (lib.optionalString nftables "Nftables")
-                 + (if withFirewall then "WithFirewall" else "Standalone")
-                 + (lib.optionalString withConntrackHelpers "withConntrackHelpers");
+                 + (if withFirewall then "WithFirewall" else "Standalone");
     meta = with pkgs.lib.maintainers; {
       maintainers = [ eelco rob ];
     };
@@ -39,10 +34,6 @@ import ./make-test-python.nix ({ pkgs, lib, withFirewall, withConntrackHelpers ?
                 (pkgs.lib.head nodes.router.config.networking.interfaces.eth2.ipv4.addresses).address;
               networking.nftables.enable = nftables;
             }
-            (lib.optionalAttrs withConntrackHelpers {
-              networking.firewall.connectionTrackingModules = [ "ftp" ];
-              networking.firewall.autoLoadConntrackHelpers = true;
-            })
           ];
 
         router =
@@ -95,7 +86,7 @@ import ./make-test-python.nix ({ pkgs, lib, withFirewall, withConntrackHelpers ?
         client.succeed("curl -v ftp://server/foo.txt >&2")
 
         # Test whether active FTP works.
-        client.${if withConntrackHelpers then "succeed" else "fail"}("curl -v -P - ftp://server/foo.txt >&2")
+        client.fail("curl -v -P - ftp://server/foo.txt >&2")
 
         # Test ICMP.
         client.succeed("ping -c 1 router >&2")
diff --git a/nixos/tests/netdata.nix b/nixos/tests/netdata.nix
index 0f26630da9d4b..aea67c29d0d4b 100644
--- a/nixos/tests/netdata.nix
+++ b/nixos/tests/netdata.nix
@@ -3,7 +3,7 @@
 import ./make-test-python.nix ({ pkgs, ...} : {
   name = "netdata";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ cransom ];
+    maintainers = [ cransom raitobezarius ];
   };
 
   nodes = {
diff --git a/nixos/tests/nextcloud/default.nix b/nixos/tests/nextcloud/default.nix
index b8d3ba75b51a9..350486e8c733c 100644
--- a/nixos/tests/nextcloud/default.nix
+++ b/nixos/tests/nextcloud/default.nix
@@ -26,4 +26,4 @@ foldl
     };
   })
 { }
-  [ 24 25 ]
+  [ 24 25 26 ]
diff --git a/nixos/tests/nextcloud/openssl-sse.nix b/nixos/tests/nextcloud/openssl-sse.nix
index 7595ee2c67e31..871947e1d2b20 100644
--- a/nixos/tests/nextcloud/openssl-sse.nix
+++ b/nixos/tests/nextcloud/openssl-sse.nix
@@ -55,6 +55,7 @@ in {
     nextcloudwithopenssl1.wait_for_unit("multi-user.target")
     nextcloudwithopenssl1.succeed("nextcloud-occ status")
     nextcloudwithopenssl1.succeed("curl -sSf http://nextcloudwithopenssl1/login")
+    nextcloud_version = ${toString nextcloudVersion}
 
     with subtest("With OpenSSL 1 SSE can be enabled and used"):
         nextcloudwithopenssl1.succeed("nextcloud-occ app:enable encryption")
@@ -71,7 +72,9 @@ in {
         nextcloudwithopenssl1.succeed("nextcloud-occ status")
 
     with subtest("Existing encrypted files cannot be read, but new files can be added"):
-        nextcloudwithopenssl1.fail("${withRcloneEnv3} ${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file >&2")
+        # This will succed starting NC26 because of their custom implementation of openssl_seal
+        read_existing_file_test = nextcloudwithopenssl1.fail if nextcloud_version < 26 else nextcloudwithopenssl1.succeed
+        read_existing_file_test("${withRcloneEnv3} ${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file >&2")
         nextcloudwithopenssl1.succeed("nextcloud-occ encryption:disable")
         nextcloudwithopenssl1.succeed("${copySharedFile3}")
         nextcloudwithopenssl1.succeed("grep bye /var/lib/nextcloud/data/root/files/test-shared-file2")
diff --git a/nixos/tests/nextcloud/with-postgresql-and-redis.nix b/nixos/tests/nextcloud/with-postgresql-and-redis.nix
index 1ef848cfb1217..d28c1bdfd6e1e 100644
--- a/nixos/tests/nextcloud/with-postgresql-and-redis.nix
+++ b/nixos/tests/nextcloud/with-postgresql-and-redis.nix
@@ -13,7 +13,7 @@ in {
     # The only thing the client needs to do is download a file.
     client = { ... }: {};
 
-    nextcloud = { config, pkgs, ... }: {
+    nextcloud = { config, pkgs, lib, ... }: {
       networking.firewall.allowedTCPPorts = [ 80 ];
 
       services.nextcloud = {
@@ -34,6 +34,15 @@ in {
           adminpassFile = toString (pkgs.writeText "admin-pass-file" ''
             ${adminpass}
           '');
+          trustedProxies = [ "::1" ];
+        };
+        notify_push = {
+          enable = true;
+          logLevel = "debug";
+        };
+        extraAppsEnable = true;
+        extraApps = {
+          inherit (pkgs."nextcloud${lib.versions.major config.services.nextcloud.package.version}Packages".apps) notify_push;
         };
       };
 
@@ -94,8 +103,10 @@ in {
         "${withRcloneEnv} ${copySharedFile}"
     )
     client.wait_for_unit("multi-user.target")
+    client.execute("${pkgs.nextcloud-notify_push.passthru.test_client}/bin/test_client http://nextcloud ${adminuser} ${adminpass} >&2 &")
     client.succeed(
         "${withRcloneEnv} ${diffSharedFile}"
     )
+    nextcloud.wait_until_succeeds("journalctl -u nextcloud-notify_push | grep -q \"Sending ping to ${adminuser}\"")
   '';
 })) args
diff --git a/nixos/tests/nixops/default.nix b/nixos/tests/nixops/default.nix
index b77ac24763981..bd00e6143639c 100644
--- a/nixos/tests/nixops/default.nix
+++ b/nixos/tests/nixops/default.nix
@@ -30,12 +30,10 @@ let
         virtualisation.additionalPaths = [
           pkgs.hello
           pkgs.figlet
-
-          # This includes build dependencies all the way down. Not efficient,
-          # but we do need build deps to an *arbitrary* depth, which is hard to
-          # determine.
-          (allDrvOutputs nodes.server.config.system.build.toplevel)
         ];
+
+        # TODO: make this efficient, https://github.com/NixOS/nixpkgs/issues/180529
+        system.includeBuildDependencies = true;
       };
       server = { lib, ... }: {
         imports = [ ./legacy/base-configuration.nix ];
diff --git a/nixos/tests/noto-fonts-cjk-qt-default-weight.nix b/nixos/tests/noto-fonts-cjk-qt-default-weight.nix
new file mode 100644
index 0000000000000..678013cf3ab90
--- /dev/null
+++ b/nixos/tests/noto-fonts-cjk-qt-default-weight.nix
@@ -0,0 +1,30 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "noto-fonts-cjk-qt";
+  meta.maintainers = with lib.maintainers; [ oxalica ];
+
+  nodes.machine = {
+    imports = [ ./common/x11.nix ];
+    fonts = {
+      enableDefaultFonts = false;
+      fonts = [ pkgs.noto-fonts-cjk-sans ];
+    };
+  };
+
+  testScript =
+    let
+      script = pkgs.writers.writePython3 "qt-default-weight" {
+        libraries = [ pkgs.python3Packages.pyqt6 ];
+      } ''
+        from PyQt6.QtWidgets import QApplication
+        from PyQt6.QtGui import QFont, QRawFont
+
+        app = QApplication([])
+        f = QRawFont.fromFont(QFont("Noto Sans CJK SC", 20))
+
+        assert f.styleName() == "Regular", f.styleName()
+      '';
+    in ''
+      machine.wait_for_x()
+      machine.succeed("${script}")
+    '';
+})
diff --git a/nixos/tests/openldap.nix b/nixos/tests/openldap.nix
index 075bb5d1f6405..47d6a91843f11 100644
--- a/nixos/tests/openldap.nix
+++ b/nixos/tests/openldap.nix
@@ -118,7 +118,7 @@ in {
     };
   };
   testScript = { nodes, ... }: let
-    specializations = "${nodes.machine.config.system.build.toplevel}/specialisation";
+    specializations = "${nodes.machine.system.build.toplevel}/specialisation";
     changeRootPw = ''
       dn: olcDatabase={1}mdb,cn=config
       changetype: modify
diff --git a/nixos/tests/opensearch.nix b/nixos/tests/opensearch.nix
new file mode 100644
index 0000000000000..c0caf950cb9c9
--- /dev/null
+++ b/nixos/tests/opensearch.nix
@@ -0,0 +1,52 @@
+let
+  opensearchTest =
+    import ./make-test-python.nix (
+      { pkgs, lib, extraSettings ? {} }: {
+        name = "opensearch";
+        meta.maintainers = with pkgs.lib.maintainers; [ shyim ];
+
+        nodes.machine = lib.mkMerge [
+          {
+            virtualisation.memorySize = 2048;
+            services.opensearch.enable = true;
+          }
+          extraSettings
+        ];
+
+        testScript = ''
+          machine.start()
+          machine.wait_for_unit("opensearch.service")
+          machine.wait_for_open_port(9200)
+
+          machine.succeed(
+              "curl --fail localhost:9200"
+          )
+        '';
+      });
+in
+{
+  opensearch = opensearchTest {};
+  opensearchCustomPathAndUser = opensearchTest {
+    extraSettings = {
+      services.opensearch.dataDir = "/var/opensearch_test";
+      services.opensearch.user = "open_search";
+      services.opensearch.group = "open_search";
+      system.activationScripts.createDirectory = {
+        text = ''
+          mkdir -p "/var/opensearch_test"
+          chown open_search:open_search /var/opensearch_test
+          chmod 0700 /var/opensearch_test
+        '';
+        deps = [ "users" "groups" ];
+      };
+      users = {
+        groups.open_search = {};
+        users.open_search = {
+          description = "OpenSearch daemon user";
+          group = "open_search";
+          isSystemUser = true;
+        };
+      };
+    };
+  };
+}
diff --git a/nixos/tests/pantheon.nix b/nixos/tests/pantheon.nix
index 0773fc0472aa3..0b920c7a6d5f4 100644
--- a/nixos/tests/pantheon.nix
+++ b/nixos/tests/pantheon.nix
@@ -15,6 +15,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...} :
     services.xserver.enable = true;
     services.xserver.desktopManager.pantheon.enable = true;
 
+    environment.systemPackages = [ pkgs.xdotool ];
   };
 
   enableOCR = true;
@@ -29,6 +30,10 @@ import ./make-test-python.nix ({ pkgs, lib, ...} :
         machine.wait_for_text("${user.description}")
         # OCR was struggling with this one.
         # machine.wait_for_text("${bob.description}")
+        # Ensure the password box is focused by clicking it.
+        # Workaround for https://github.com/NixOS/nixpkgs/issues/211366.
+        machine.succeed("XAUTHORITY=/var/lib/lightdm/.Xauthority DISPLAY=:0 xdotool mousemove 512 505 click 1")
+        machine.sleep(2)
         machine.screenshot("elementary_greeter_lightdm")
 
     with subtest("Login with elementary-greeter"):
diff --git a/nixos/tests/pass-secret-service.nix b/nixos/tests/pass-secret-service.nix
index a85a508bfe16b..e0dddf0ad29e2 100644
--- a/nixos/tests/pass-secret-service.nix
+++ b/nixos/tests/pass-secret-service.nix
@@ -1,6 +1,6 @@
 import ./make-test-python.nix ({ pkgs, lib, ... }: {
   name = "pass-secret-service";
-  meta.maintainers = with lib; [ aidalgol ];
+  meta.maintainers = [ lib.maintainers.aidalgol ];
 
   nodes.machine = { nodes, pkgs, ... }:
     {
diff --git a/nixos/tests/peroxide.nix b/nixos/tests/peroxide.nix
new file mode 100644
index 0000000000000..12e196484164b
--- /dev/null
+++ b/nixos/tests/peroxide.nix
@@ -0,0 +1,16 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "peroxide";
+  meta.maintainers = with lib.maintainers; [ aidalgol ];
+
+  nodes.machine =
+    { config, pkgs, ... }: {
+      networking.hostName = "nixos";
+      services.peroxide.enable = true;
+    };
+
+  testScript = ''
+    machine.wait_for_unit("peroxide.service")
+    machine.wait_for_open_port(1143) # IMAP
+    machine.wait_for_open_port(1025) # SMTP
+  '';
+})
diff --git a/nixos/tests/phosh.nix b/nixos/tests/phosh.nix
index 25bf4848542e6..78d6da31beee1 100644
--- a/nixos/tests/phosh.nix
+++ b/nixos/tests/phosh.nix
@@ -3,7 +3,7 @@ import ./make-test-python.nix ({ pkgs, ...}: let
 in {
   name = "phosh";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ zhaofengli ];
+    maintainers = [ tomfitzhenry zhaofengli ];
   };
 
   nodes = {
diff --git a/nixos/tests/pleroma.nix b/nixos/tests/pleroma.nix
index 8998716243a25..4f1aef854146e 100644
--- a/nixos/tests/pleroma.nix
+++ b/nixos/tests/pleroma.nix
@@ -170,8 +170,8 @@ import ./make-test-python.nix ({ pkgs, ... }:
   '';
 
   hosts = nodes: ''
-    ${nodes.pleroma.config.networking.primaryIPAddress} pleroma.nixos.test
-    ${nodes.client.config.networking.primaryIPAddress} client.nixos.test
+    ${nodes.pleroma.networking.primaryIPAddress} pleroma.nixos.test
+    ${nodes.client.networking.primaryIPAddress} client.nixos.test
   '';
   in {
   name = "pleroma";
diff --git a/nixos/tests/podman/default.nix b/nixos/tests/podman/default.nix
index c9b234d2b6092..0e1f420f2a7de 100644
--- a/nixos/tests/podman/default.nix
+++ b/nixos/tests/podman/default.nix
@@ -6,7 +6,14 @@ import ../make-test-python.nix (
     };
 
     nodes = {
-      podman = { pkgs, ... }: {
+      rootful = { pkgs, ... }: {
+        virtualisation.podman.enable = true;
+
+        # hack to ensure that podman built with and without zfs in extraPackages is cached
+        boot.supportedFilesystems = [ "zfs" ];
+        networking.hostId = "00000000";
+      };
+      rootless = { pkgs, ... }: {
         virtualisation.podman.enable = true;
 
         users.users.alice = {
@@ -49,101 +56,109 @@ import ../make-test-python.nix (
           return f"su {user} -l -c {cmd}"
 
 
-      podman.wait_for_unit("sockets.target")
+      rootful.wait_for_unit("sockets.target")
+      rootless.wait_for_unit("sockets.target")
       dns.wait_for_unit("sockets.target")
       docker.wait_for_unit("sockets.target")
       start_all()
 
       with subtest("Run container as root with runc"):
-          podman.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
-          podman.succeed(
+          rootful.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
+          rootful.succeed(
               "podman run --runtime=runc -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
           )
-          podman.succeed("podman ps | grep sleeping")
-          podman.succeed("podman stop sleeping")
-          podman.succeed("podman rm sleeping")
+          rootful.succeed("podman ps | grep sleeping")
+          rootful.succeed("podman stop sleeping")
+          rootful.succeed("podman rm sleeping")
 
       with subtest("Run container as root with crun"):
-          podman.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
-          podman.succeed(
+          rootful.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
+          rootful.succeed(
               "podman run --runtime=crun -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
           )
-          podman.succeed("podman ps | grep sleeping")
-          podman.succeed("podman stop sleeping")
-          podman.succeed("podman rm sleeping")
+          rootful.succeed("podman ps | grep sleeping")
+          rootful.succeed("podman stop sleeping")
+          rootful.succeed("podman rm sleeping")
 
       with subtest("Run container as root with the default backend"):
-          podman.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
-          podman.succeed(
+          rootful.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
+          rootful.succeed(
               "podman run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
           )
-          podman.succeed("podman ps | grep sleeping")
-          podman.succeed("podman stop sleeping")
-          podman.succeed("podman rm sleeping")
+          rootful.succeed("podman ps | grep sleeping")
+          rootful.succeed("podman stop sleeping")
+          rootful.succeed("podman rm sleeping")
 
       # start systemd session for rootless
-      podman.succeed("loginctl enable-linger alice")
-      podman.succeed(su_cmd("whoami"))
-      podman.sleep(1)
+      rootless.succeed("loginctl enable-linger alice")
+      rootless.succeed(su_cmd("whoami"))
+      rootless.sleep(1)
 
       with subtest("Run container rootless with runc"):
-          podman.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg"))
-          podman.succeed(
+          rootless.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg"))
+          rootless.succeed(
               su_cmd(
                   "podman run --runtime=runc -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
               )
           )
-          podman.succeed(su_cmd("podman ps | grep sleeping"))
-          podman.succeed(su_cmd("podman stop sleeping"))
-          podman.succeed(su_cmd("podman rm sleeping"))
+          rootless.succeed(su_cmd("podman ps | grep sleeping"))
+          rootless.succeed(su_cmd("podman stop sleeping"))
+          rootless.succeed(su_cmd("podman rm sleeping"))
 
       with subtest("Run container rootless with crun"):
-          podman.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg"))
-          podman.succeed(
+          rootless.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg"))
+          rootless.succeed(
               su_cmd(
                   "podman run --runtime=crun -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
               )
           )
-          podman.succeed(su_cmd("podman ps | grep sleeping"))
-          podman.succeed(su_cmd("podman stop sleeping"))
-          podman.succeed(su_cmd("podman rm sleeping"))
+          rootless.succeed(su_cmd("podman ps | grep sleeping"))
+          rootless.succeed(su_cmd("podman stop sleeping"))
+          rootless.succeed(su_cmd("podman rm sleeping"))
 
       with subtest("Run container rootless with the default backend"):
-          podman.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg"))
-          podman.succeed(
+          rootless.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg"))
+          rootless.succeed(
               su_cmd(
                   "podman run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
               )
           )
-          podman.succeed(su_cmd("podman ps | grep sleeping"))
-          podman.succeed(su_cmd("podman stop sleeping"))
-          podman.succeed(su_cmd("podman rm sleeping"))
+          rootless.succeed(su_cmd("podman ps | grep sleeping"))
+          rootless.succeed(su_cmd("podman stop sleeping"))
+          rootless.succeed(su_cmd("podman rm sleeping"))
+
+      with subtest("rootlessport"):
+          rootless.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg"))
+          rootless.succeed(
+              su_cmd(
+                  "podman run -d -p 9000:8888 --name=rootlessport -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin -w ${pkgs.writeTextDir "index.html" "<h1>Testing</h1>"} scratchimg ${pkgs.python3}/bin/python -m http.server 8888"
+              )
+          )
+          rootless.succeed(su_cmd("podman ps | grep rootlessport"))
+          rootless.wait_until_succeeds(su_cmd("${pkgs.curl}/bin/curl localhost:9000 | grep Testing"))
+          rootless.succeed(su_cmd("podman stop rootlessport"))
+          rootless.succeed(su_cmd("podman rm rootlessport"))
 
       with subtest("Run container with init"):
-          podman.succeed(
+          rootful.succeed(
               "tar cv -C ${pkgs.pkgsStatic.busybox} . | podman import - busybox"
           )
-          pid = podman.succeed("podman run --rm busybox readlink /proc/self").strip()
+          pid = rootful.succeed("podman run --rm busybox readlink /proc/self").strip()
           assert pid == "1"
-          pid = podman.succeed("podman run --rm --init busybox readlink /proc/self").strip()
+          pid = rootful.succeed("podman run --rm --init busybox readlink /proc/self").strip()
           assert pid == "2"
 
       with subtest("aardvark-dns"):
-        dns.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
-        dns.succeed(
-          "podman run -d --name=webserver -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin -w ${pkgs.writeTextDir "index.html" "<h1>Hi</h1>"} scratchimg ${pkgs.python3}/bin/python -m http.server 8000"
-        )
-        dns.succeed("podman ps | grep webserver")
-        dns.succeed("""
-          for i in `seq 0 120`; do
-            podman run --rm --name=client -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg ${pkgs.curl}/bin/curl http://webserver:8000 >/dev/console \
-              && exit 0
-            sleep 0.5
-          done
-          exit 1
-        """)
-        dns.succeed("podman stop webserver")
-        dns.succeed("podman rm webserver")
+          dns.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
+          dns.succeed(
+              "podman run -d --name=webserver -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin -w ${pkgs.writeTextDir "index.html" "<h1>Testing</h1>"} scratchimg ${pkgs.python3}/bin/python -m http.server 8000"
+          )
+          dns.succeed("podman ps | grep webserver")
+          dns.wait_until_succeeds(
+              "podman run --rm --name=client -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg ${pkgs.curl}/bin/curl http://webserver:8000 | grep Testing"
+          )
+          dns.succeed("podman stop webserver")
+          dns.succeed("podman rm webserver")
 
       with subtest("A podman member can use the docker cli"):
           docker.succeed(su_cmd("docker version"))
diff --git a/nixos/tests/pomerium.nix b/nixos/tests/pomerium.nix
index 7af8283264489..abaf56c518e05 100644
--- a/nixos/tests/pomerium.nix
+++ b/nixos/tests/pomerium.nix
@@ -20,6 +20,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
   }; in {
     pomerium = { pkgs, lib, ... }: {
       imports = [ (base "192.168.1.1") ];
+      environment.systemPackages = with pkgs; [ chromium ];
       services.pomerium = {
         enable = true;
         settings = {
@@ -98,5 +99,11 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
         pomerium.succeed(
             "curl -L --resolve login.required:80:127.0.0.1 http://login.required | grep 'hello I am login page'"
         )
+
+    with subtest("ui"):
+        pomerium.succeed(
+          # check for a string that only appears if the UI is displayed correctly
+            "chromium --no-sandbox --headless --disable-gpu --dump-dom --host-resolver-rules='MAP login.required 127.0.0.1:80' http://login.required/.pomerium | grep 'contact your administrator'"
+        )
   '';
 })
diff --git a/nixos/tests/predictable-interface-names.nix b/nixos/tests/predictable-interface-names.nix
index 08773120bc127..684df9c39246c 100644
--- a/nixos/tests/predictable-interface-names.nix
+++ b/nixos/tests/predictable-interface-names.nix
@@ -13,7 +13,7 @@ in pkgs.lib.listToAttrs (builtins.map ({ predictable, withNetworkd }: {
   name = pkgs.lib.optionalString (!predictable) "un" + "predictable"
        + pkgs.lib.optionalString withNetworkd "Networkd";
   value = makeTest {
-    name = "${if predictable then "" else "un"}predictableInterfaceNames${if withNetworkd then "-with-networkd" else ""}";
+    name = "${pkgs.lib.optionalString (!predictable) "un"}predictableInterfaceNames${pkgs.lib.optionalString withNetworkd "-with-networkd"}";
     meta = {};
 
     nodes.machine = { lib, ... }: {
diff --git a/nixos/tests/quake3.nix b/nixos/tests/quake3.nix
index 82af1af463d03..ef5fcc41476bc 100644
--- a/nixos/tests/quake3.nix
+++ b/nixos/tests/quake3.nix
@@ -1,4 +1,4 @@
-import ./make-test-python.nix ({ pkgs, ...} :
+import ./make-test-python.nix ({ pkgs, lib, ...} :
 
 let
 
@@ -11,9 +11,9 @@ let
     };
 
   # Only allow the demo data to be used (only if it's unfreeRedistributable).
-  unfreePredicate = pkg: with pkgs.lib; let
+  unfreePredicate = pkg: with lib; let
     allowPackageNames = [ "quake3-demodata" "quake3-pointrelease" ];
-    allowLicenses = [ pkgs.lib.licenses.unfreeRedistributable ];
+    allowLicenses = [ lib.licenses.unfreeRedistributable ];
   in elem pkg.pname allowPackageNames &&
      elem (pkg.meta.license or null) allowLicenses;
 
@@ -31,7 +31,7 @@ in
 
 rec {
   name = "quake3";
-  meta = with pkgs.stdenv.lib.maintainers; {
+  meta = with lib.maintainers; {
     maintainers = [ domenkozar eelco ];
   };
 
diff --git a/nixos/tests/readarr.nix b/nixos/tests/readarr.nix
new file mode 100644
index 0000000000000..bb7dd85298486
--- /dev/null
+++ b/nixos/tests/readarr.nix
@@ -0,0 +1,18 @@
+import ./make-test-python.nix ({ lib, ... }:
+
+with lib;
+
+{
+  name = "readarr";
+  meta.maintainers = with maintainers; [ jocelynthode ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    { services.readarr.enable = true; };
+
+  testScript = ''
+    machine.wait_for_unit("readarr.service")
+    machine.wait_for_open_port(8787)
+    machine.succeed("curl --fail http://localhost:8787/")
+  '';
+})
diff --git a/nixos/tests/restic.nix b/nixos/tests/restic.nix
index 42af0783863ea..1071fbada74fe 100644
--- a/nixos/tests/restic.nix
+++ b/nixos/tests/restic.nix
@@ -100,7 +100,7 @@ import ./make-test-python.nix (
           "${pkgs.restic}/bin/restic -r ${remoteRepository} -p ${passwordFile} snapshots",
           '${pkgs.restic}/bin/restic -r ${remoteFromFileRepository} -p ${passwordFile} snapshots"',
           "${pkgs.restic}/bin/restic -r ${rcloneRepository} -p ${passwordFile} snapshots",
-          "grep 'backup .* /opt' /tmp/fake-restic.log",
+          "grep 'backup.* /opt' /tmp/fake-restic.log",
       )
       server.succeed(
           # set up
@@ -129,8 +129,8 @@ import ./make-test-python.nix (
 
           # test that custompackage runs both `restic backup` and `restic check` with reasonable commandlines
           "systemctl start restic-backups-custompackage.service",
-          "grep 'backup .* /opt' /tmp/fake-restic.log",
-          "grep 'check .* --some-check-option' /tmp/fake-restic.log",
+          "grep 'backup.* /opt' /tmp/fake-restic.log",
+          "grep 'check.* --some-check-option' /tmp/fake-restic.log",
 
           # test that we can create four snapshots in remotebackup and rclonebackup
           "timedatectl set-time '2017-12-13 13:45'",
diff --git a/nixos/tests/sgtpuzzles.nix b/nixos/tests/sgtpuzzles.nix
new file mode 100644
index 0000000000000..b8d25d42d312f
--- /dev/null
+++ b/nixos/tests/sgtpuzzles.nix
@@ -0,0 +1,34 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+{
+  name = "sgtpuzzles";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ tomfitzhenry ];
+  };
+
+  nodes.machine = { ... }:
+
+  {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+    environment.systemPackages = with pkgs; [
+      sgtpuzzles
+    ];
+  };
+
+  enableOCR = true;
+
+  testScript = { nodes, ... }:
+  ''
+    start_all()
+    machine.wait_for_x()
+
+    machine.execute("mines >&2 &")
+
+    machine.wait_for_window("Mines")
+    machine.wait_for_text("Marked")
+    machine.screenshot("mines")
+  '';
+})
diff --git a/nixos/tests/shadow.nix b/nixos/tests/shadow.nix
index baa2e5945c05d..c9a04088e8709 100644
--- a/nixos/tests/shadow.nix
+++ b/nixos/tests/shadow.nix
@@ -5,6 +5,7 @@ let
   password4 = "asdf123";
   hashed_bcrypt = "$2b$05$8xIEflrk2RxQtcVXbGIxs.Vl0x7dF1/JSv3cyX6JJt0npzkTCWvxK"; # fnord
   hashed_yeshash = "$y$j9T$d8Z4EAf8P1SvM/aDFbxMS0$VnTXMp/Hnc7QdCBEaLTq5ZFOAFo2/PM0/xEAFuOE88."; # fnord
+  hashed_sha512crypt = "$6$ymzs8WINZ5wGwQcV$VC2S0cQiX8NVukOLymysTPn4v1zJoJp3NGyhnqyv/dAf4NWZsBWYveQcj6gEJr4ZUjRBRjM0Pj1L8TCQ8hUUp0"; # meow
 in import ./make-test-python.nix ({ pkgs, ... }: {
   name = "shadow";
   meta = with pkgs.lib.maintainers; { maintainers = [ nequissimus ]; };
@@ -39,6 +40,12 @@ in import ./make-test-python.nix ({ pkgs, ... }: {
         hashedPassword = hashed_yeshash;
         shell = pkgs.bash;
       };
+      users.leo = {
+        isNormalUser = true;
+        initialHashedPassword = "!";
+        hashedPassword = hashed_sha512crypt; # should take precedence over initialHashedPassword
+        shell = pkgs.bash;
+      };
     };
   };
 
@@ -145,5 +152,21 @@ in import ./make-test-python.nix ({ pkgs, ... }: {
             print(shadow.succeed(f"cat /tmp/{u}"))
             assert u in shadow.succeed(f"cat /tmp/{u}")
             shadow.send_chars("logout\n")
+
+    with subtest("Ensure hashedPassword does not get overridden by initialHashedPassword"):
+        shadow.send_key("alt-f6")
+        shadow.wait_until_succeeds("[ $(fgconsole) = 6 ]")
+        shadow.wait_for_unit("getty@tty6.service")
+        shadow.wait_until_succeeds("pgrep -f 'agetty.*tty6'")
+        shadow.wait_until_tty_matches("6", "login: ")
+        shadow.send_chars("leo\n")
+        shadow.wait_until_tty_matches("6", "login: leo")
+        shadow.wait_until_succeeds("pgrep login")
+        shadow.sleep(2)
+        shadow.send_chars("meow\n")
+        shadow.send_chars("whoami > /tmp/leo\n")
+        shadow.wait_for_file("/tmp/leo")
+        assert "leo" in shadow.succeed("cat /tmp/leo")
+        shadow.send_chars("logout\n")
   '';
 })
diff --git a/nixos/tests/soapui.nix b/nixos/tests/soapui.nix
index e4ce3888fd437..3a2d11a167562 100644
--- a/nixos/tests/soapui.nix
+++ b/nixos/tests/soapui.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, ... }: {
   name = "soapui";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ asbachb ];
+    maintainers = [ ];
   };
 
   nodes.machine = { config, pkgs, ... }: {
diff --git a/nixos/tests/solr.nix b/nixos/tests/solr.nix
deleted file mode 100644
index 33afe9d788f78..0000000000000
--- a/nixos/tests/solr.nix
+++ /dev/null
@@ -1,56 +0,0 @@
-import ./make-test-python.nix ({ pkgs, ... }:
-
-{
-  name = "solr";
-  meta.maintainers = [ pkgs.lib.maintainers.aanderse ];
-
-  nodes.machine =
-    { config, pkgs, ... }:
-    {
-      # Ensure the virtual machine has enough memory for Solr to avoid the following error:
-      #
-      #   OpenJDK 64-Bit Server VM warning:
-      #     INFO: os::commit_memory(0x00000000e8000000, 402653184, 0)
-      #     failed; error='Cannot allocate memory' (errno=12)
-      #
-      #   There is insufficient memory for the Java Runtime Environment to continue.
-      #   Native memory allocation (mmap) failed to map 402653184 bytes for committing reserved memory.
-      virtualisation.memorySize = 2000;
-
-      services.solr.enable = true;
-    };
-
-  testScript = ''
-    start_all()
-
-    machine.wait_for_unit("solr.service")
-    machine.wait_for_open_port(8983)
-    machine.succeed("curl --fail http://localhost:8983/solr/")
-
-    # adapted from pkgs.solr/examples/films/README.txt
-    machine.succeed("sudo -u solr solr create -c films")
-    assert '"status":0' in machine.succeed(
-        """
-      curl http://localhost:8983/solr/films/schema -X POST -H 'Content-type:application/json' --data-binary '{
-        "add-field" : {
-          "name":"name",
-          "type":"text_general",
-          "multiValued":false,
-          "stored":true
-        },
-        "add-field" : {
-          "name":"initial_release_date",
-          "type":"pdate",
-          "stored":true
-        }
-      }'
-    """
-    )
-    machine.succeed(
-        "sudo -u solr post -c films ${pkgs.solr}/example/films/films.json"
-    )
-    assert '"name":"Batman Begins"' in machine.succeed(
-        "curl http://localhost:8983/solr/films/query?q=name:batman"
-    )
-  '';
-})
diff --git a/nixos/tests/sssd.nix b/nixos/tests/sssd.nix
index 25527cb59a59b..c8d356e074ad5 100644
--- a/nixos/tests/sssd.nix
+++ b/nixos/tests/sssd.nix
@@ -13,5 +13,6 @@ import ./make-test-python.nix ({ pkgs, ... }:
       start_all()
       machine.wait_for_unit("multi-user.target")
       machine.wait_for_unit("sssd.service")
+      machine.succeed("sssctl config-check")
     '';
 })
diff --git a/nixos/tests/systemd-credentials-tpm2.nix b/nixos/tests/systemd-credentials-tpm2.nix
new file mode 100644
index 0000000000000..d2dc1fd7b615b
--- /dev/null
+++ b/nixos/tests/systemd-credentials-tpm2.nix
@@ -0,0 +1,124 @@
+import ./make-test-python.nix ({ lib, pkgs, system, ... }:
+
+let
+  tpmSocketPath = "/tmp/swtpm-sock";
+  tpmDeviceModels = {
+    x86_64-linux = "tpm-tis";
+    aarch64-linux = "tpm-tis-device";
+  };
+in
+
+{
+  name = "systemd-credentials-tpm2";
+
+  meta = {
+    maintainers = with pkgs.lib.maintainers; [ tmarkus ];
+  };
+
+  nodes.machine = { pkgs, ... }: {
+    virtualisation = {
+      qemu.options = [
+        "-chardev socket,id=chrtpm,path=${tpmSocketPath}"
+        "-tpmdev emulator,id=tpm_dev_0,chardev=chrtpm"
+        "-device ${tpmDeviceModels.${system}},tpmdev=tpm_dev_0"
+      ];
+    };
+
+    boot.initrd.availableKernelModules = [ "tpm_tis" ];
+
+    environment.systemPackages = with pkgs; [ diffutils ];
+  };
+
+  testScript = ''
+    import subprocess
+    from tempfile import TemporaryDirectory
+
+    # From systemd-initrd-luks-tpm2.nix
+    class Tpm:
+        def __init__(self):
+            self.state_dir = TemporaryDirectory()
+            self.start()
+
+        def start(self):
+            self.proc = subprocess.Popen(["${pkgs.swtpm}/bin/swtpm",
+                "socket",
+                "--tpmstate", f"dir={self.state_dir.name}",
+                "--ctrl", "type=unixio,path=${tpmSocketPath}",
+                "--tpm2",
+                ])
+
+            # Check whether starting swtpm failed
+            try:
+                exit_code = self.proc.wait(timeout=0.2)
+                if exit_code is not None and exit_code != 0:
+                    raise Exception("failed to start swtpm")
+            except subprocess.TimeoutExpired:
+                pass
+
+        """Check whether the swtpm process exited due to an error"""
+        def check(self):
+            exit_code = self.proc.poll()
+            if exit_code is not None and exit_code != 0:
+                raise Exception("swtpm process died")
+
+    CRED_NAME = "testkey"
+    CRED_RAW_FILE = f"/root/{CRED_NAME}"
+    CRED_FILE = f"/root/{CRED_NAME}.cred"
+
+    def systemd_run(machine, cmd):
+        machine.log(f"Executing command (via systemd-run): \"{cmd}\"")
+
+        (status, out) = machine.execute( " ".join([
+            "systemd-run",
+            "--service-type=exec",
+            "--quiet",
+            "--wait",
+            "-E PATH=\"$PATH\"",
+            "-p StandardOutput=journal",
+            "-p StandardError=journal",
+            f"-p LoadCredentialEncrypted={CRED_NAME}:{CRED_FILE}",
+            f"$SHELL -c '{cmd}'"
+            ]) )
+
+        if status != 0:
+            raise Exception(f"systemd_run failed (status {status})")
+
+        machine.log("systemd-run finished successfully")
+
+    tpm = Tpm()
+
+    @polling_condition
+    def swtpm_running():
+        tpm.check()
+
+    machine.wait_for_unit("multi-user.target")
+
+    with subtest("Check whether TPM device exists"):
+        machine.succeed("test -e /dev/tpm0")
+        machine.succeed("test -e /dev/tpmrm0")
+
+    with subtest("Check whether systemd-creds detects TPM2 correctly"):
+        cmd = "systemd-creds has-tpm2"
+        machine.log(f"Running \"{cmd}\"")
+        (status, _) = machine.execute(cmd)
+
+        # Check exit code equals 0 or 1 (1 means firmware support is missing, which is OK here)
+        if status != 0 and status != 1:
+            raise Exception("systemd-creds failed to detect TPM2")
+
+    with subtest("Encrypt credential using systemd-creds"):
+        machine.succeed(f"dd if=/dev/urandom of={CRED_RAW_FILE} bs=1k count=16")
+        machine.succeed(f"systemd-creds --with-key=host+tpm2 encrypt --name=testkey {CRED_RAW_FILE} {CRED_FILE}")
+
+    with subtest("Write provided credential and check for equality"):
+        CRED_OUT_FILE = f"/root/{CRED_NAME}.out"
+        systemd_run(machine, f"systemd-creds cat testkey > {CRED_OUT_FILE}")
+        machine.succeed(f"cmp --silent -- {CRED_RAW_FILE} {CRED_OUT_FILE}")
+
+    with subtest("Check whether systemd service can see credential in systemd-creds list"):
+        systemd_run(machine, f"systemd-creds list | grep {CRED_NAME}")
+
+    with subtest("Check whether systemd service can access credential in $CREDENTIALS_DIRECTORY"):
+        systemd_run(machine, f"cmp --silent -- $CREDENTIALS_DIRECTORY/{CRED_NAME} {CRED_RAW_FILE}")
+  '';
+})
diff --git a/nixos/tests/systemd-cryptenroll.nix b/nixos/tests/systemd-cryptenroll.nix
index 9ee2d280fbbea..055ae7d1681f2 100644
--- a/nixos/tests/systemd-cryptenroll.nix
+++ b/nixos/tests/systemd-cryptenroll.nix
@@ -2,7 +2,6 @@ import ./make-test-python.nix ({ pkgs, ... }: {
   name = "systemd-cryptenroll";
   meta = with pkgs.lib.maintainers; {
     maintainers = [ ymatsiuk ];
-    broken = true; # times out after two hours, details -> https://github.com/NixOS/nixpkgs/issues/167994
   };
 
   nodes.machine = { pkgs, lib, ... }: {
diff --git a/nixos/tests/systemd-initrd-simple.nix b/nixos/tests/systemd-initrd-simple.nix
index 5d98114304b75..f7f4863d17e35 100644
--- a/nixos/tests/systemd-initrd-simple.nix
+++ b/nixos/tests/systemd-initrd-simple.nix
@@ -6,9 +6,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
       enable = true;
       emergencyAccess = true;
     };
-    fileSystems = lib.mkVMOverride {
-      "/".autoResize = true;
-    };
+    virtualisation.fileSystems."/".autoResize = true;
   };
 
   testScript = ''
diff --git a/nixos/tests/systemd-initrd-vconsole.nix b/nixos/tests/systemd-initrd-vconsole.nix
new file mode 100644
index 0000000000000..b74df410c4224
--- /dev/null
+++ b/nixos/tests/systemd-initrd-vconsole.nix
@@ -0,0 +1,33 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "systemd-initrd-vconsole";
+
+  nodes.machine = { pkgs, ... }: {
+    boot.kernelParams = [ "rd.systemd.unit=rescue.target" ];
+
+    boot.initrd.systemd = {
+      enable = true;
+      emergencyAccess = true;
+    };
+
+    console = {
+      earlySetup = true;
+      keyMap = "colemak";
+    };
+  };
+
+  testScript = ''
+    # Boot into rescue shell in initrd
+    machine.start()
+    machine.wait_for_console_text("Press Enter for maintenance")
+    machine.send_console("\n")
+    machine.wait_for_console_text("Logging in with home")
+
+    # Check keymap
+    machine.send_console("(printf '%s to receive text: \\n' Ready && read text && echo \"$text\") </dev/tty1\n")
+    machine.wait_for_console_text("Ready to receive text:")
+    for key in "asdfjkl;\n":
+      machine.send_key(key)
+    machine.wait_for_console_text("arstneio")
+    machine.send_console("systemctl poweroff\n")
+  '';
+})
diff --git a/nixos/tests/systemd-repart.nix b/nixos/tests/systemd-repart.nix
index 92cc1fb04edc0..36de5d988fdb1 100644
--- a/nixos/tests/systemd-repart.nix
+++ b/nixos/tests/systemd-repart.nix
@@ -52,9 +52,6 @@ let
       };
     };
 
-    boot.initrd.systemd.enable = true;
-    boot.initrd.systemd.repart.enable = true;
-
     # systemd-repart operates on disks with a partition table. The qemu module,
     # however, creates separate filesystem images without a partition table, so
     # we have to create a disk image manually.
@@ -88,7 +85,10 @@ in
     nodes.machine = { config, pkgs, ... }: {
       imports = [ common ];
 
-      boot.initrd.systemd.repart.partitions = {
+      boot.initrd.systemd.enable = true;
+
+      boot.initrd.systemd.repart.enable = true;
+      systemd.repart.partitions = {
         "10-root" = {
           Type = "linux-generic";
         };
@@ -105,4 +105,30 @@ in
       assert "Growing existing partition 1." in systemd_repart_logs
     '';
   };
+
+  after-initrd = makeTest {
+    name = "systemd-repart-after-initrd";
+    meta.maintainers = with maintainers; [ nikstur ];
+
+    nodes.machine = { config, pkgs, ... }: {
+      imports = [ common ];
+
+      systemd.repart.enable = true;
+      systemd.repart.partitions = {
+        "10-root" = {
+          Type = "linux-generic";
+        };
+      };
+    };
+
+    testScript = { nodes, ... }: ''
+      ${useDiskImage nodes.machine}
+
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      systemd_repart_logs = machine.succeed("journalctl --unit systemd-repart.service")
+      assert "Growing existing partition 1." in systemd_repart_logs
+    '';
+  };
 }
diff --git a/nixos/tests/systemd-shutdown.nix b/nixos/tests/systemd-shutdown.nix
index 688cd6dd2c175..dad8167f198fc 100644
--- a/nixos/tests/systemd-shutdown.nix
+++ b/nixos/tests/systemd-shutdown.nix
@@ -11,6 +11,7 @@ in {
     systemd.shutdownRamfs.contents."/etc/systemd/system-shutdown/shutdown-message".source = pkgs.writeShellScript "shutdown-message" ''
       echo "${msg}"
     '';
+    boot.initrd.systemd.enable = systemdStage1;
   };
 
   testScript = ''
diff --git a/nixos/tests/teleport.nix b/nixos/tests/teleport.nix
index 34bf1bc0c70d0..cdf762b128448 100644
--- a/nixos/tests/teleport.nix
+++ b/nixos/tests/teleport.nix
@@ -1,18 +1,28 @@
 { system ? builtins.currentSystem
 , config ? { }
 , pkgs ? import ../.. { inherit system config; }
+, lib ? pkgs.lib
 }:
 
 with import ../lib/testing-python.nix { inherit system pkgs; };
 
 let
-  minimal = { config, ... }: {
-    services.teleport.enable = true;
+  packages = with pkgs; {
+    "default" = teleport;
+    "11" = teleport_11;
   };
 
-  client = { config, ... }: {
+  minimal = package: {
     services.teleport = {
       enable = true;
+      inherit package;
+    };
+  };
+
+  client = package: {
+    services.teleport = {
+      enable = true;
+      inherit package;
       settings = {
         teleport = {
           nodename = "client";
@@ -37,9 +47,10 @@ let
     }];
   };
 
-  server = { config, ... }: {
+  server = package: {
     services.teleport = {
       enable = true;
+      inherit package;
       settings = {
         teleport = {
           nodename = "server";
@@ -64,36 +75,41 @@ let
     };
   };
 in
-{
-  minimal = makeTest {
-    # minimal setup should always work
-    name = "teleport-minimal-setup";
-    meta.maintainers = with pkgs.lib.maintainers; [ ymatsiuk ];
-    nodes = { inherit minimal; };
+lib.concatMapAttrs
+  (name: package: {
+    "minimal_${name}" = makeTest {
+      # minimal setup should always work
+      name = "teleport-minimal-setup";
+      meta.maintainers = with pkgs.lib.maintainers; [ justinas ];
+      nodes.minimal = minimal package;
 
-    testScript = ''
-      minimal.wait_for_open_port(3025)
-      minimal.wait_for_open_port(3080)
-      minimal.wait_for_open_port(3022)
-    '';
-  };
+      testScript = ''
+        minimal.wait_for_open_port(3025)
+        minimal.wait_for_open_port(3080)
+        minimal.wait_for_open_port(3022)
+      '';
+    };
 
-  basic = makeTest {
-    # basic server and client test
-    name = "teleport-server-client";
-    meta.maintainers = with pkgs.lib.maintainers; [ ymatsiuk ];
-    nodes = { inherit server client; };
+    "basic_${name}" = makeTest {
+      # basic server and client test
+      name = "teleport-server-client";
+      meta.maintainers = with pkgs.lib.maintainers; [ justinas ];
+      nodes = {
+        server = server package;
+        client = client package;
+      };
 
-    testScript = ''
-      with subtest("teleport ready"):
-          server.wait_for_open_port(3025)
-          client.wait_for_open_port(3022)
+      testScript = ''
+        with subtest("teleport ready"):
+            server.wait_for_open_port(3025)
+            client.wait_for_open_port(3022)
 
-      with subtest("check applied configuration"):
-          server.wait_until_succeeds("tctl get nodes --format=json | ${pkgs.jq}/bin/jq -e '.[] | select(.spec.hostname==\"client\") | .metadata.labels.role==\"client\"'")
-          server.wait_for_open_port(3000)
-          client.succeed("journalctl -u teleport.service --grep='DEBU'")
-          server.succeed("journalctl -u teleport.service --grep='Starting teleport in insecure mode.'")
-    '';
-  };
-}
+        with subtest("check applied configuration"):
+            server.wait_until_succeeds("tctl get nodes --format=json | ${pkgs.jq}/bin/jq -e '.[] | select(.spec.hostname==\"client\") | .metadata.labels.role==\"client\"'")
+            server.wait_for_open_port(3000)
+            client.succeed("journalctl -u teleport.service --grep='DEBU'")
+            server.succeed("journalctl -u teleport.service --grep='Starting teleport in insecure mode.'")
+      '';
+    };
+  })
+  packages
diff --git a/nixos/tests/timescaledb.nix b/nixos/tests/timescaledb.nix
new file mode 100644
index 0000000000000..00a7f9af09fb8
--- /dev/null
+++ b/nixos/tests/timescaledb.nix
@@ -0,0 +1,93 @@
+# mostly copied from ./postgresql.nix as it seemed unapproriate to
+# test additional extensions for postgresql there.
+
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  postgresql-versions = import ../../pkgs/servers/sql/postgresql pkgs;
+  test-sql = pkgs.writeText "postgresql-test" ''
+    CREATE EXTENSION timescaledb;
+    CREATE EXTENSION timescaledb_toolkit;
+
+    CREATE TABLE sth (
+      time TIMESTAMPTZ NOT NULL,
+      value DOUBLE PRECISION
+    );
+
+    SELECT create_hypertable('sth', 'time');
+
+    INSERT INTO sth (time, value) VALUES
+    ('2003-04-12 04:05:06 America/New_York', 1.0),
+    ('2003-04-12 04:05:07 America/New_York', 2.0),
+    ('2003-04-12 04:05:08 America/New_York', 3.0),
+    ('2003-04-12 04:05:09 America/New_York', 4.0),
+    ('2003-04-12 04:05:10 America/New_York', 5.0)
+    ;
+
+    WITH t AS (
+      SELECT
+        time_bucket('1 day'::interval, time) AS dt,
+        stats_agg(value) AS stats
+      FROM sth
+      GROUP BY time_bucket('1 day'::interval, time)
+    )
+    SELECT
+      average(stats)
+    FROM t;
+  '';
+  make-postgresql-test = postgresql-name: postgresql-package: makeTest {
+    name = postgresql-name;
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ typetetris ];
+    };
+
+    nodes.machine = { ... }:
+      {
+        services.postgresql = {
+          enable = true;
+          package = postgresql-package;
+          extraPlugins = with postgresql-package.pkgs; [
+            timescaledb
+            timescaledb_toolkit
+          ];
+          settings = { shared_preload_libraries = "timescaledb, timescaledb_toolkit"; };
+        };
+      };
+
+    testScript = ''
+      def check_count(statement, lines):
+          return 'test $(sudo -u postgres psql postgres -tAc "{}"|wc -l) -eq {}'.format(
+              statement, lines
+          )
+
+
+      machine.start()
+      machine.wait_for_unit("postgresql")
+
+      with subtest("Postgresql with extensions timescaledb and timescaledb_toolkit is available just after unit start"):
+          machine.succeed(
+              "sudo -u postgres psql -f ${test-sql}"
+          )
+
+      machine.fail(check_count("SELECT * FROM sth;", 3))
+      machine.succeed(check_count("SELECT * FROM sth;", 5))
+      machine.fail(check_count("SELECT * FROM sth;", 4))
+
+      machine.shutdown()
+    '';
+
+  };
+  applicablePostgresqlVersions = filterAttrs (_: value: versionAtLeast value.version "12") postgresql-versions;
+in
+mapAttrs'
+  (name: package: {
+    inherit name;
+    value = make-postgresql-test name package;
+  })
+  applicablePostgresqlVersions
diff --git a/nixos/tests/tuxguitar.nix b/nixos/tests/tuxguitar.nix
index 037f489e54483..00833024bfeac 100644
--- a/nixos/tests/tuxguitar.nix
+++ b/nixos/tests/tuxguitar.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, ... }: {
   name = "tuxguitar";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ asbachb ];
+    maintainers = [ ];
   };
 
   nodes.machine = { config, pkgs, ... }: {
diff --git a/nixos/tests/txredisapi.nix b/nixos/tests/txredisapi.nix
index 7c6b36a5c47d5..47c2ba6d3749a 100644
--- a/nixos/tests/txredisapi.nix
+++ b/nixos/tests/txredisapi.nix
@@ -12,7 +12,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
       {
         services.redis.servers."".enable = true;
 
-        environment.systemPackages = with pkgs; [ (python38.withPackages (ps: [ ps.twisted ps.txredisapi ps.mock ]))];
+        environment.systemPackages = with pkgs; [ (python3.withPackages (ps: [ ps.twisted ps.txredisapi ps.mock ]))];
       };
   };
 
diff --git a/nixos/tests/unifi.nix b/nixos/tests/unifi.nix
index 9dc7e5d04bd5d..d371bafd69652 100644
--- a/nixos/tests/unifi.nix
+++ b/nixos/tests/unifi.nix
@@ -16,6 +16,8 @@ let
     };
 
     nodes.server = {
+      nixpkgs.config = config;
+
       services.unifi = {
         enable = true;
         unifiPackage = unifi;
diff --git a/nixos/tests/wireguard/namespaces.nix b/nixos/tests/wireguard/namespaces.nix
index 1790c45bb1f65..d0eb009e1107d 100644
--- a/nixos/tests/wireguard/namespaces.nix
+++ b/nixos/tests/wireguard/namespaces.nix
@@ -39,6 +39,7 @@ import ../make-test-python.nix ({ pkgs, lib, kernelPackages ? null, ... } : {
         preSetup = ''
           ip netns add ${interfaceNamespace}
         '';
+        mtu = 1280;
         inherit interfaceNamespace;
       };
     };
diff --git a/nixos/tests/wireguard/snakeoil-keys.nix b/nixos/tests/wireguard/snakeoil-keys.nix
index 55ad582d40595..c979f0e0c8a96 100644
--- a/nixos/tests/wireguard/snakeoil-keys.nix
+++ b/nixos/tests/wireguard/snakeoil-keys.nix
@@ -6,6 +6,7 @@
 
   peer1 = {
     privateKey = "uO8JVo/sanx2DOM0L9GUEtzKZ82RGkRnYgpaYc7iXmg=";
-    publicKey = "Ks9yRJIi/0vYgRmn14mIOQRwkcUGBujYINbMpik2SBI=";
+    # readFile'd keys may have trailing newlines, emulate this
+    publicKey = "Ks9yRJIi/0vYgRmn14mIOQRwkcUGBujYINbMpik2SBI=\n";
   };
 }
diff --git a/nixos/tests/xfce.nix b/nixos/tests/xfce.nix
index 31f00f77c40d4..3758ccbccf425 100644
--- a/nixos/tests/xfce.nix
+++ b/nixos/tests/xfce.nix
@@ -26,7 +26,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     };
 
   testScript = { nodes, ... }: let
-    user = nodes.machine.config.users.users.alice;
+    user = nodes.machine.users.users.alice;
   in ''
       machine.wait_for_x()
       machine.wait_for_file("${user.home}/.Xauthority")
diff --git a/nixos/tests/zram-generator.nix b/nixos/tests/zram-generator.nix
index 3407361d2824f..2be7bd2e05b1d 100644
--- a/nixos/tests/zram-generator.nix
+++ b/nixos/tests/zram-generator.nix
@@ -1,18 +1,36 @@
 import ./make-test-python.nix {
   name = "zram-generator";
 
-  nodes.machine = { ... }: {
-    zramSwap = {
-      enable = true;
-      priority = 10;
-      algorithm = "lz4";
-      swapDevices = 2;
-      memoryPercent = 30;
-      memoryMax = 10 * 1024 * 1024;
+  nodes = {
+    single = { ... }: {
+      virtualisation = {
+        emptyDiskImages = [ 512 ];
+      };
+      zramSwap = {
+        enable = true;
+        priority = 10;
+        algorithm = "lz4";
+        swapDevices = 1;
+        memoryPercent = 30;
+        memoryMax = 10 * 1024 * 1024;
+        writebackDevice = "/dev/vdb";
+      };
+    };
+    machine = { ... }: {
+      zramSwap = {
+        enable = true;
+        priority = 10;
+        algorithm = "lz4";
+        swapDevices = 2;
+        memoryPercent = 30;
+        memoryMax = 10 * 1024 * 1024;
+      };
     };
   };
 
   testScript = ''
+    single.wait_for_unit("systemd-zram-setup@zram0.service")
+
     machine.wait_for_unit("systemd-zram-setup@zram0.service")
     machine.wait_for_unit("systemd-zram-setup@zram1.service")
     zram = machine.succeed("zramctl --noheadings --raw")