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/development/settings-options.section.md28
-rw-r--r--nixos/doc/manual/release-notes/rl-2405.section.md61
-rw-r--r--nixos/lib/make-disk-image.nix65
-rw-r--r--nixos/lib/systemd-lib.nix16
-rw-r--r--nixos/lib/systemd-unit-options.nix12
-rw-r--r--nixos/lib/test-driver/pyproject.toml1
-rw-r--r--nixos/lib/test-driver/test_driver/driver.py60
-rw-r--r--nixos/lib/test-driver/test_driver/machine.py95
-rw-r--r--nixos/lib/test-script-prepend.py13
-rw-r--r--nixos/lib/testing/nixos-test-base.nix6
-rw-r--r--nixos/lib/testing/nodes.nix22
-rw-r--r--nixos/lib/testing/pkgs.nix6
-rw-r--r--nixos/lib/testing/run.nix4
-rw-r--r--nixos/modules/config/nix.nix2
-rw-r--r--nixos/modules/config/no-x-libs.nix3
-rw-r--r--nixos/modules/config/users-groups.nix22
-rw-r--r--nixos/modules/config/vte.nix4
-rw-r--r--nixos/modules/config/xdg/portal.nix28
-rw-r--r--nixos/modules/hardware/printers.nix27
-rw-r--r--nixos/modules/hardware/video/switcheroo-control.nix17
-rw-r--r--nixos/modules/i18n/input-method/fcitx5.nix14
-rw-r--r--nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares-plasma6.nix46
-rw-r--r--nixos/modules/installer/cd-dvd/installation-cd-minimal-new-kernel-no-zfs.nix12
-rw-r--r--nixos/modules/installer/netboot/netboot.nix19
-rw-r--r--nixos/modules/installer/sd-card/sd-image-aarch64-new-kernel-no-zfs-installer.nix12
-rw-r--r--nixos/modules/misc/nixpkgs-flake.nix105
-rw-r--r--nixos/modules/misc/nixpkgs.nix6
-rw-r--r--nixos/modules/misc/nixpkgs/test.nix6
-rw-r--r--nixos/modules/module-list.nix13
-rw-r--r--nixos/modules/programs/ccache.nix36
-rw-r--r--nixos/modules/programs/gnupg.nix1
-rw-r--r--nixos/modules/programs/kdeconnect.nix5
-rw-r--r--nixos/modules/programs/quark-goldleaf.nix18
-rw-r--r--nixos/modules/programs/steam.nix18
-rw-r--r--nixos/modules/programs/wayland/sway.nix4
-rw-r--r--nixos/modules/programs/yazi.nix4
-rw-r--r--nixos/modules/security/ca.nix14
-rw-r--r--nixos/modules/security/pam.nix43
-rw-r--r--nixos/modules/services/cluster/kubernetes/default.nix3
-rw-r--r--nixos/modules/services/cluster/kubernetes/kubelet.nix59
-rw-r--r--nixos/modules/services/continuous-integration/hydra/default.nix2
-rw-r--r--nixos/modules/services/databases/mysql.nix15
-rw-r--r--nixos/modules/services/databases/pgbouncer.nix19
-rw-r--r--nixos/modules/services/desktops/pipewire/pipewire.nix100
-rw-r--r--nixos/modules/services/desktops/pipewire/wireplumber.nix129
-rw-r--r--nixos/modules/services/development/lorri.nix3
-rw-r--r--nixos/modules/services/development/nixseparatedebuginfod.nix4
-rw-r--r--nixos/modules/services/display-managers/greetd.nix2
-rw-r--r--nixos/modules/services/games/archisteamfarm.nix2
-rw-r--r--nixos/modules/services/games/armagetronad.nix268
-rw-r--r--nixos/modules/services/games/teeworlds.nix310
-rw-r--r--nixos/modules/services/hardware/asusd.nix10
-rw-r--r--nixos/modules/services/hardware/bolt.nix19
-rw-r--r--nixos/modules/services/hardware/hddfancontrol.nix4
-rw-r--r--nixos/modules/services/hardware/monado.nix102
-rw-r--r--nixos/modules/services/hardware/nvidia-container-toolkit-cdi-generator/cdi-generate.nix39
-rw-r--r--nixos/modules/services/hardware/nvidia-container-toolkit-cdi-generator/default.nix38
-rw-r--r--nixos/modules/services/hardware/pcscd.nix9
-rw-r--r--nixos/modules/services/hardware/thinkfan.nix9
-rw-r--r--nixos/modules/services/home-automation/ebusd.nix2
-rw-r--r--nixos/modules/services/home-automation/matter-server.nix125
-rw-r--r--nixos/modules/services/misc/atuin.nix14
-rw-r--r--nixos/modules/services/misc/docker-registry.nix10
-rw-r--r--nixos/modules/services/misc/ollama.nix37
-rw-r--r--nixos/modules/services/misc/paperless.nix4
-rw-r--r--nixos/modules/services/misc/sourcehut/default.nix22
-rw-r--r--nixos/modules/services/misc/transfer-sh.nix102
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/fastly.nix51
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/nut.nix15
-rw-r--r--nixos/modules/services/monitoring/scrutiny.nix221
-rw-r--r--nixos/modules/services/networking/bee-clef.nix107
-rw-r--r--nixos/modules/services/networking/bee.nix11
-rw-r--r--nixos/modules/services/networking/bird-lg.nix4
-rw-r--r--nixos/modules/services/networking/dhcpcd.nix32
-rw-r--r--nixos/modules/services/networking/go-camo.nix73
-rw-r--r--nixos/modules/services/networking/mosquitto.nix34
-rw-r--r--nixos/modules/services/networking/sabnzbd.nix34
-rw-r--r--nixos/modules/services/networking/searx.nix2
-rw-r--r--nixos/modules/services/networking/tailscale.nix9
-rw-r--r--nixos/modules/services/networking/unbound.nix25
-rw-r--r--nixos/modules/services/security/kanidm.nix32
-rw-r--r--nixos/modules/services/system/automatic-timezoned.nix2
-rw-r--r--nixos/modules/services/torrent/transmission.nix2
-rw-r--r--nixos/modules/services/web-apps/mastodon.nix33
-rw-r--r--nixos/modules/services/web-apps/mealie.nix79
-rw-r--r--nixos/modules/services/web-apps/nextcloud.nix2
-rw-r--r--nixos/modules/services/web-apps/photoprism.nix1
-rw-r--r--nixos/modules/services/web-apps/vikunja.nix47
-rw-r--r--nixos/modules/services/web-servers/zope2.nix262
-rw-r--r--nixos/modules/services/x11/desktop-managers/budgie.nix10
-rw-r--r--nixos/modules/services/x11/desktop-managers/deepin.nix9
-rw-r--r--nixos/modules/services/x11/desktop-managers/default.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/plasma5.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/plasma6.nix291
-rw-r--r--nixos/modules/services/x11/display-managers/sddm.nix26
-rw-r--r--nixos/modules/system/boot/kernel.nix7
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py80
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix138
-rw-r--r--nixos/modules/system/boot/networkd.nix4
-rw-r--r--nixos/modules/system/boot/plymouth.nix4
-rw-r--r--nixos/modules/system/boot/stage-1.nix8
-rw-r--r--nixos/modules/system/boot/systemd.nix2
-rw-r--r--nixos/modules/system/boot/systemd/coredump.nix2
-rw-r--r--nixos/modules/system/boot/systemd/initrd.nix2
-rw-r--r--nixos/modules/system/boot/systemd/repart.nix16
-rw-r--r--nixos/modules/system/boot/uki.nix17
-rw-r--r--nixos/modules/system/etc/build-composefs-dump.py3
-rw-r--r--nixos/modules/system/etc/etc.nix4
-rw-r--r--nixos/modules/tasks/filesystems.nix23
-rw-r--r--nixos/modules/tasks/filesystems/apfs.nix4
-rw-r--r--nixos/modules/tasks/filesystems/bcachefs.nix4
-rw-r--r--nixos/modules/tasks/filesystems/btrfs.nix4
-rw-r--r--nixos/modules/tasks/filesystems/cifs.nix4
-rw-r--r--nixos/modules/tasks/filesystems/ecryptfs.nix2
-rw-r--r--nixos/modules/tasks/filesystems/erofs.nix4
-rw-r--r--nixos/modules/tasks/filesystems/exfat.nix2
-rw-r--r--nixos/modules/tasks/filesystems/ext.nix6
-rw-r--r--nixos/modules/tasks/filesystems/f2fs.nix5
-rw-r--r--nixos/modules/tasks/filesystems/glusterfs.nix2
-rw-r--r--nixos/modules/tasks/filesystems/jfs.nix4
-rw-r--r--nixos/modules/tasks/filesystems/nfs.nix4
-rw-r--r--nixos/modules/tasks/filesystems/ntfs.nix2
-rw-r--r--nixos/modules/tasks/filesystems/reiserfs.nix4
-rw-r--r--nixos/modules/tasks/filesystems/squashfs.nix2
-rw-r--r--nixos/modules/tasks/filesystems/sshfs.nix10
-rw-r--r--nixos/modules/tasks/filesystems/unionfs-fuse.nix4
-rw-r--r--nixos/modules/tasks/filesystems/vboxsf.nix4
-rw-r--r--nixos/modules/tasks/filesystems/vfat.nix4
-rw-r--r--nixos/modules/tasks/filesystems/xfs.nix4
-rw-r--r--nixos/modules/tasks/filesystems/zfs.nix63
-rw-r--r--nixos/modules/virtualisation/containers.nix70
-rw-r--r--nixos/modules/virtualisation/cri-o.nix2
-rw-r--r--nixos/modules/virtualisation/docker.nix12
-rw-r--r--nixos/modules/virtualisation/hyperv-image.nix1
-rw-r--r--nixos/modules/virtualisation/incus.nix19
-rw-r--r--nixos/modules/virtualisation/linode-config.nix1
-rw-r--r--nixos/modules/virtualisation/lxc-container.nix12
-rw-r--r--nixos/modules/virtualisation/oci-containers.nix5
-rw-r--r--nixos/modules/virtualisation/podman/default.nix12
-rw-r--r--nixos/modules/virtualisation/qemu-vm.nix4
-rw-r--r--nixos/modules/virtualisation/vmware-image.nix1
-rw-r--r--nixos/release-combined.nix1
-rw-r--r--nixos/release-small.nix4
-rw-r--r--nixos/release.nix6
-rw-r--r--nixos/tests/acme.nix5
-rw-r--r--nixos/tests/activation/etc-overlay-immutable.nix14
-rw-r--r--nixos/tests/activation/etc-overlay-mutable.nix18
-rw-r--r--nixos/tests/all-tests.nix14
-rw-r--r--nixos/tests/armagetronad.nix272
-rw-r--r--nixos/tests/boot.nix82
-rw-r--r--nixos/tests/ccache.nix24
-rw-r--r--nixos/tests/common/ec2.nix2
-rw-r--r--nixos/tests/consul.nix4
-rw-r--r--nixos/tests/docker-registry.nix2
-rw-r--r--nixos/tests/docker-tools.nix61
-rw-r--r--nixos/tests/geoserver.nix65
-rw-r--r--nixos/tests/go-camo.nix30
-rw-r--r--nixos/tests/incus/container.nix17
-rw-r--r--nixos/tests/incus/default.nix7
-rw-r--r--nixos/tests/incus/ui.nix63
-rw-r--r--nixos/tests/installer.nix49
-rw-r--r--nixos/tests/k3s/default.nix5
-rw-r--r--nixos/tests/k3s/etcd.nix100
-rw-r--r--nixos/tests/keepalived.nix2
-rw-r--r--nixos/tests/kernel-generic.nix1
-rw-r--r--nixos/tests/lomiri-system-settings.nix99
-rw-r--r--nixos/tests/matomo.nix4
-rw-r--r--nixos/tests/matter-server.nix45
-rw-r--r--nixos/tests/mealie.nix24
-rw-r--r--nixos/tests/minio.nix6
-rw-r--r--nixos/tests/monado.nix39
-rw-r--r--nixos/tests/morph-browser.nix53
-rw-r--r--nixos/tests/nextcloud/with-postgresql-and-redis.nix6
-rw-r--r--nixos/tests/plasma6.nix64
-rw-r--r--nixos/tests/power-profiles-daemon.nix21
-rw-r--r--nixos/tests/prometheus-exporters.nix3
-rw-r--r--nixos/tests/qemu-vm-external-disk-image.nix3
-rw-r--r--nixos/tests/scrutiny.nix70
-rw-r--r--nixos/tests/searx.nix6
-rw-r--r--nixos/tests/sourcehut.nix252
-rw-r--r--nixos/tests/sourcehut/builds.nix54
-rw-r--r--nixos/tests/sourcehut/default.nix6
-rw-r--r--nixos/tests/sourcehut/git.nix96
-rw-r--r--nixos/tests/sourcehut/nodes/common.nix107
-rw-r--r--nixos/tests/systemd-boot.nix165
-rw-r--r--nixos/tests/transfer-sh.nix20
-rw-r--r--nixos/tests/vikunja.nix26
-rw-r--r--nixos/tests/web-apps/mastodon/default.nix2
-rw-r--r--nixos/tests/web-apps/mastodon/remote-databases.nix (renamed from nixos/tests/web-apps/mastodon/remote-postgresql.nix)38
-rw-r--r--nixos/tests/web-apps/mastodon/script.nix1
-rw-r--r--nixos/tests/web-apps/mastodon/standard.nix1
-rw-r--r--nixos/tests/zfs.nix18
192 files changed, 4930 insertions, 1486 deletions
diff --git a/nixos/doc/manual/development/settings-options.section.md b/nixos/doc/manual/development/settings-options.section.md
index 3a4800742b04..71ec9bbc8892 100644
--- a/nixos/doc/manual/development/settings-options.section.md
+++ b/nixos/doc/manual/development/settings-options.section.md
@@ -73,6 +73,34 @@ have a predefined type and string generator already declared under
 
     It returns a set with INI-specific attributes `type` and `generate`
     as specified [below](#pkgs-formats-result).
+    The type of the input is an *attrset* of sections; key-value pairs where
+    the key is the section name and the value is the corresponding content
+    which is also an *attrset* of key-value pairs for the actual key-value
+    mappings of the INI format.
+    The values of the INI atoms are subject to the above parameters (e.g. lists
+    may be transformed into multiple key-value pairs depending on
+    `listToValue`).
+
+`pkgs.formats.iniWithGlobalSection` { *`listsAsDuplicateKeys`* ? false, *`listToValue`* ? null, \.\.\. }
+
+:   A function taking an attribute set with values
+
+    `listsAsDuplicateKeys`
+
+    :   A boolean for controlling whether list values can be used to
+        represent duplicate INI keys
+
+    `listToValue`
+
+    :   A function for turning a list of values into a single value.
+
+    It returns a set with INI-specific attributes `type` and `generate`
+    as specified [below](#pkgs-formats-result).
+    The type of the input is an *attrset* of the structure
+    `{ sections = {}; globalSection = {}; }` where *sections* are several
+    sections as with *pkgs.formats.ini* and *globalSection* being just a single
+    attrset of key-value pairs for a single section, the global section which
+    preceedes the section definitions.
 
 `pkgs.formats.toml` { }
 
diff --git a/nixos/doc/manual/release-notes/rl-2405.section.md b/nixos/doc/manual/release-notes/rl-2405.section.md
index 70ee02183f4f..a8c735aefecc 100644
--- a/nixos/doc/manual/release-notes/rl-2405.section.md
+++ b/nixos/doc/manual/release-notes/rl-2405.section.md
@@ -16,15 +16,29 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - `linuxPackages_testing_bcachefs` is now fully deprecated by `linuxPackages_latest`, and is therefore no longer available.
 
+- The default kernel package has been updated from 6.1 to 6.6. All supported kernels remain available.
+
 - NixOS now installs a stub ELF loader that prints an informative error message when users attempt to run binaries not made for NixOS.
    - This can be disabled through the `environment.stub-ld.enable` option.
    - If you use `programs.nix-ld.enable`, no changes are needed. The stub will be disabled automatically.
 
+- On flake-based NixOS configurations using `nixpkgs.lib.nixosSystem`, NixOS will automatically set `NIX_PATH` and the system-wide flake registry (`/etc/nix/registry.json`) to point `<nixpkgs>` and the unqualified flake path `nixpkgs` to the version of nixpkgs used to build the system.
+
+  This makes `nix run nixpkgs#hello` and `nix-build '<nixpkgs>' -A hello` work out of the box with no added configuration, reusing dependencies already on the system.
+
+  This may be undesirable if nix commands are not going to be run on the built system since it adds nixpkgs to the system closure. For such closure-size-constrained non-interactive systems, this setting should be disabled.
+
+  To disable this, set [nixpkgs.flake.setNixPath](#opt-nixpkgs.flake.setNixPath) and [nixpkgs.flake.setFlakeRegistry](#opt-nixpkgs.flake.setFlakeRegistry) to false.
+
 - Julia environments can now be built with arbitrary packages from the ecosystem using the `.withPackages` function. For example: `julia.withPackages ["Plots"]`.
 
 - A new option `systemd.sysusers.enable` was added. If enabled, users and
   groups are created with systemd-sysusers instead of with a custom perl script.
 
+- A new option `virtualisation.containers.cdi` was added. It contains `static` and `dynamic` attributes (corresponding to `/etc/cdi` and `/run/cdi` respectively) to configure the Container Device Interface (CDI).
+
+- `virtualisation.docker.enableNvidia` and `virtualisation.podman.enableNvidia` options are deprecated. `virtualisation.containers.cdi.dynamic.nvidia.enable` should be used instead. This option will expose GPUs on containers with the `--device` CLI option. This is supported by Docker 25, Podman 3.2.0 and Singularity 4. Any container runtime that supports the CDI specification will take advantage of this feature.
+
 - A new option `system.etc.overlay.enable` was added. If enabled, `/etc` is
   mounted via an overlayfs instead of being created by a custom perl script.
 
@@ -38,6 +52,8 @@ In addition to numerous new and upgraded packages, this release has the followin
   }
   ```
 
+- Plasma 6 is now available and can be installed with `services.xserver.desktopManager.plasma6.enable = true;`. Plasma 5 will likely be deprecated in the next release (24.11). Note that Plasma 6 runs as Wayland by default, and the X11 session needs to be explicitly selected if necessary.
+
 ## New Services {#sec-release-24.05-new-services}
 
 <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
@@ -62,30 +78,46 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - [hebbot](https://github.com/haecker-felix/hebbot), a Matrix bot to generate "This Week in X" like blog posts. Available as [services.hebbot](#opt-services.hebbot.enable).
 
+- [Python Matter Server](https://github.com/home-assistant-libs/python-matter-server), a
+  Matter Controller Server exposing websocket connections for use with other services, notably Home Assistant.
+  Available as [services.matter-server](#opt-services.matter-server.enable)
+
 - [Anki Sync Server](https://docs.ankiweb.net/sync-server.html), the official sync server built into recent versions of Anki. Available as [services.anki-sync-server](#opt-services.anki-sync-server.enable).
 The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been marked deprecated and will be dropped after 24.05 due to lack of maintenance of the anki-sync-server softwares.
 
+- [transfer-sh](https://github.com/dutchcoders/transfer.sh), a tool that supports easy and fast file sharing from the command-line. Available as [services.transfer-sh](#opt-services.transfer-sh.enable).
+
 - [Suwayomi Server](https://github.com/Suwayomi/Suwayomi-Server), a free and open source manga reader server that runs extensions built for [Tachiyomi](https://tachiyomi.org). Available as [services.suwayomi-server](#opt-services.suwayomi-server.enable).
 
 - [ping_exporter](https://github.com/czerwonk/ping_exporter), a Prometheus exporter for ICMP echo requests. Available as [services.prometheus.exporters.ping](#opt-services.prometheus.exporters.ping.enable).
 
 - [TigerBeetle](https://tigerbeetle.com/), a distributed financial accounting database designed for mission critical safety and performance. Available as [services.tigerbeetle](#opt-services.tigerbeetle.enable).
 
+- [go-camo](https://github.com/cactus/go-camo), a secure image proxy server. Available as [services.go-camo](#opt-services.go-camo.enable).
+
+- [Monado](https://monado.freedesktop.org/), an open source XR runtime. Available as [services.monado](#opt-services.monado.enable).
+
 - [Clevis](https://github.com/latchset/clevis), a pluggable framework for automated decryption, used to unlock encrypted devices in initrd. Available as [boot.initrd.clevis.enable](#opt-boot.initrd.clevis.enable).
 
+- [armagetronad](https://wiki.armagetronad.org), a mid-2000s 3D lightcycle game widely played at iD Tech Camps. You can define multiple servers using `services.armagetronad.<server>.enable`.
+
 - [TuxClocker](https://github.com/Lurkki14/tuxclocker), a hardware control and monitoring program. Available as [programs.tuxclocker](#opt-programs.tuxclocker.enable).
 
 - [ALVR](https://github.com/alvr-org/alvr), a VR desktop streamer. Available as [programs.alvr](#opt-programs.alvr.enable)
 
 - [RustDesk](https://rustdesk.com), a full-featured open source remote control alternative for self-hosting and security with minimal configuration. Alternative to TeamViewer.
 
+- [Scrutiny](https://github.com/AnalogJ/scrutiny), a S.M.A.R.T monitoring tool for hard disks with a web frontend.
+
 - [systemd-lock-handler](https://git.sr.ht/~whynothugo/systemd-lock-handler/), a bridge between logind D-Bus events and systemd targets. Available as [services.systemd-lock-handler.enable](#opt-services.systemd-lock-handler.enable).
 
+- [Mealie](https://nightly.mealie.io/), a self-hosted recipe manager and meal planner with a RestAPI backend and a reactive frontend application built in NuxtJS for a pleasant user experience for the whole family. Available as [services.mealie](#opt-services.mealie.enable)
+
 ## Backward Incompatibilities {#sec-release-24.05-incompatibilities}
 
 <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
 
-- `himalaya` was updated to v1.0.0-beta, which introduces breaking changes. Check out the [release note](https://github.com/soywod/himalaya/releases/tag/v1.0.0-beta) for details.
+- `himalaya` was updated to `v1.0.0-beta.3`, which introduces breaking changes. Check out the [release note](https://github.com/soywod/himalaya/releases/tag/v1.0.0-beta.3) for details.
 
 - The `power.ups` module now generates `upsd.conf`, `upsd.users` and `upsmon.conf` automatically from a set of new configuration options. This breaks compatibility with existing `power.ups` setups where these files were created manually. Back up these files before upgrading NixOS.
 
@@ -103,6 +135,8 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
 
 - `nitter` requires a `guest_accounts.jsonl` to be provided as a path or loaded into the default location at `/var/lib/nitter/guest_accounts.jsonl`. See [Guest Account Branch Deployment](https://github.com/zedeus/nitter/wiki/Guest-Account-Branch-Deployment) for details.
 
+- `boot.supportedFilesystems` and `boot.initrd.supportedFilesystems` are now attribute sets instead of lists. Assignment from lists as done previously is still supported, but checking whether a filesystem is enabled must now by done using `supportedFilesystems.fs or false` instead of using `lib.elem "fs" supportedFilesystems` as was done previously.
+
 - `services.aria2.rpcSecret` has been replaced with `services.aria2.rpcSecretFile`.
   This was done so that secrets aren't stored in the world-readable nix store.
   To migrate, you will have create a file with the same exact string, and change
@@ -128,6 +162,10 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
   release notes of [v19](https://github.com/systemd/mkosi/releases/tag/v19) and
   [v20](https://github.com/systemd/mkosi/releases/tag/v20) for a list of changes.
 
+- The `services.vikunja` systemd service now uses `vikunja` as dynamic user instead of `vikunja-api`. Database users might need to be changed.
+
+- The `services.vikunja.setupNginx` setting has been removed. Users now need to setup the webserver configuration on their own with a proxy pass to the vikunja service.
+
 - The `woodpecker-*` packages have been updated to v2 which includes [breaking changes](https://woodpecker-ci.org/docs/next/migrations#200).
 
 - `services.nginx` will no longer advertise HTTP/3 availability automatically. This must now be manually added, preferably to each location block.
@@ -153,6 +191,8 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
   - The bundled Lua was updated to Lua v5.2, which includes breaking changes. See the [Lua manual](https://www.lua.org/manual/5.2/manual.html#8) for more information.
   - The WebSocket API [was rewritten](https://github.com/MCJack123/craftos2/issues/337), which introduced breaking changes.
 
+- The `gtest` package has been updated past v1.13.0, which requires C++14 or higher.
+
 - The latest available version of Nextcloud is v28 (available as `pkgs.nextcloud28`). The installation logic is as follows:
   - If [`services.nextcloud.package`](#opt-services.nextcloud.package) is specified explicitly, this package will be installed (**recommended**)
   - If [`system.stateVersion`](#opt-system.stateVersion) is >=24.05, `pkgs.nextcloud28` will be installed by default.
@@ -163,12 +203,18 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
 
 - The `cudaPackages` package scope has been updated to `cudaPackages_12`.
 
+- Ada packages (libraries and tools) have been moved into the `gnatPackages` scope. `gnatPackages` uses the default GNAT compiler, `gnat12Packages` and `gnat13Packages` use the respective matching compiler version.
+
+- `spark2014` has been renamed to `gnatprove`. A version of `gnatprove` matching different GNAT versions is available from the different `gnatPackages` sets.
+
 - `services.resolved.fallbackDns` can now be used to disable the upstream fallback servers entirely by setting it to an empty list. To get the previous behaviour of the upstream defaults set it to null, the new default, instead.
 
 - `xxd` has been moved from `vim` default output to its own output to reduce closure size. The canonical way to reference it across all platforms is `unixtools.xxd`.
 
 - The `stalwart-mail` package has been updated to v0.5.3, which includes [breaking changes](https://github.com/stalwartlabs/mail-server/blob/v0.5.3/UPGRADING.md).
 
+- `services.zope2` has been removed as `zope2` is unmaintained and was relying on Python2.
+
 - `services.avahi.nssmdns` got split into `services.avahi.nssmdns4` and `services.avahi.nssmdns6` which enable the mDNS NSS switch for IPv4 and IPv6 respectively.
   Since most mDNS responders only register IPv4 addresses, most users want to keep the IPv6 support disabled to avoid long timeouts.
 
@@ -177,6 +223,8 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
   `wants`), because the dependency that `multi-user.target` has on
   `network-online.target` is planned for removal.
 
+- `services.pgbouncer` now has systemd support enabled and will log to journald. The default setting for `services.pgbouncer.logFile` is now `null` to disable logging to a separate log file.
+
 - `services.archisteamfarm` no longer uses the abbreviation `asf` for its state directory (`/var/lib/asf`), user and group (both `asf`). Instead the long name `archisteamfarm` is used.
   Configurations with `system.stateVersion` 23.11 or earlier, default to the old stateDirectory until the 24.11 release and must either set the option explicitly or move the data to the new directory.
 
@@ -249,6 +297,10 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
 
 - Cinnamon has been updated to 6.0. Please beware that the [Wayland session](https://blog.linuxmint.com/?p=4591) is still experimental in this release.
 
+- New `boot.loader.systemd-boot.xbootldrMountPoint` allows setting up a separate [XBOOTLDR partition](https://uapi-group.org/specifications/specs/boot_loader_specification/) to store boot files. Useful on systems with a small EFI System partition that cannot be easily repartitioned.
+
+- `boot.loader.systemd-boot` will now verify that `efiSysMountPoint` (and `xbootldrMountPoint` if configured) are mounted partitions.
+
 - `services.postgresql.extraPlugins` changed its type from just a list of packages to also a function that returns such a list.
   For example a config line like ``services.postgresql.extraPlugins = with pkgs.postgresql_11.pkgs; [ postgis ];`` is recommended to be changed to ``services.postgresql.extraPlugins = ps: with ps; [ postgis ];``;
 
@@ -337,6 +389,8 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
 
 - The `services.paperless` module no longer uses the previously downloaded NLTK data stored in `/var/cache/paperless/nltk`. This directory can be removed.
 
+- The `services.teeworlds` module now has a wealth of configuration options, including a new `package` option.
+
 - The `hardware.pulseaudio` module now sets permission of pulse user home directory to 755 when running in "systemWide" mode. It fixes [issue 114399](https://github.com/NixOS/nixpkgs/issues/114399).
 
 - The module `services.github-runner` has been removed. To configure a single GitHub Actions Runner refer to `services.github-runners.*`. Note that this will trigger a new runner registration.
@@ -348,6 +402,11 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
 
 - The `mpich` package expression now requires `withPm` to be a list, e.g. `"hydra:gforker"` becomes `[ "hydra" "gforker" ]`.
 
+- When merging systemd unit options (of type `unitOption`),
+  if at least one definition is a list, all those which aren't are now lifted into a list,
+  making it possible to accumulate definitions without resorting to `mkForce`,
+  hence to retain the definitions not anticipating that need.
+
 - YouTrack is bumped to 2023.3. The update is not performed automatically, it requires manual interaction. See the YouTrack section in the manual for details.
 
 - QtMultimedia has changed its default backend to `QT_MEDIA_BACKEND=ffmpeg` (previously `gstreamer` on Linux or `darwin` on MacOS).
diff --git a/nixos/lib/make-disk-image.nix b/nixos/lib/make-disk-image.nix
index 1a33abd01ea1..9bdbf4e0713d 100644
--- a/nixos/lib/make-disk-image.nix
+++ b/nixos/lib/make-disk-image.nix
@@ -56,6 +56,14 @@ This partition table type uses GPT and:
 - creates an FAT32 ESP partition from 8MiB to specified `bootSize` parameter (256MiB by default), set it bootable ;
 - creates an primary ext4 partition starting after the boot partition and extending to the full disk image
 
+#### `efixbootldr`
+
+This partition table type uses GPT and:
+
+- creates an FAT32 ESP partition from 8MiB to 100MiB, set it bootable ;
+- creates an FAT32 BOOT partition from 100MiB to specified `bootSize` parameter (256MiB by default), set `bls_boot` flag ;
+- creates an primary ext4 partition starting after the boot partition and extending to the full disk image
+
 #### `hybrid`
 
 This partition table type uses GPT and:
@@ -111,19 +119,7 @@ To solve this, you can run `fdisk -l $image` and generate `dd if=$image of=$imag
   # When setting one of `user' or `group', the other needs to be set too.
   contents ? []
 
-, # Type of partition table to use; either "legacy", "efi", or "none".
-  # For "efi" images, the GPT partition table is used and a mandatory ESP
-  #   partition of reasonable size is created in addition to the root partition.
-  # For "legacy", the msdos partition table is used and a single large root
-  #   partition is created.
-  # For "legacy+gpt", the GPT partition table is used, a 1MiB no-fs partition for
-  #   use by the bootloader is created, and a single large root partition is
-  #   created.
-  # For "hybrid", the GPT partition table is used and a mandatory ESP
-  #   partition of reasonable size is created in addition to the root partition.
-  #   Also a legacy MBR will be present.
-  # For "none", no partition table is created. Enabling `installBootLoader`
-  #   most likely fails as GRUB will probably refuse to install.
+, # Type of partition table to use; described in the `Image Partitioning` section above.
   partitionTableType ? "legacy"
 
 , # Whether to invoke `switch-to-configuration boot` during image creation
@@ -193,11 +189,11 @@ To solve this, you can run `fdisk -l $image` and generate `dd if=$image of=$imag
   additionalPaths ? []
 }:
 
-assert (lib.assertOneOf "partitionTableType" partitionTableType [ "legacy" "legacy+gpt" "efi" "hybrid" "none" ]);
+assert (lib.assertOneOf "partitionTableType" partitionTableType [ "legacy" "legacy+gpt" "efi" "efixbootldr" "hybrid" "none" ]);
 assert (lib.assertMsg (fsType == "ext4" && deterministic -> rootFSUID != null) "In deterministic mode with a ext4 partition, rootFSUID must be non-null, by default, it is equal to rootGPUID.");
   # We use -E offset=X below, which is only supported by e2fsprogs
 assert (lib.assertMsg (partitionTableType != "none" -> fsType == "ext4") "to produce a partition table, we need to use -E offset flag which is support only for fsType = ext4");
-assert (lib.assertMsg (touchEFIVars -> partitionTableType == "hybrid" || partitionTableType == "efi" || partitionTableType == "legacy+gpt") "EFI variables can be used only with a partition table of type: hybrid, efi or legacy+gpt.");
+assert (lib.assertMsg (touchEFIVars -> partitionTableType == "hybrid" || partitionTableType == "efi" || partitionTableType == "efixbootldr" || partitionTableType == "legacy+gpt") "EFI variables can be used only with a partition table of type: hybrid, efi, efixbootldr, or legacy+gpt.");
   # If only Nix store image, then: contents must be empty, configFile must be unset, and we should no install bootloader.
 assert (lib.assertMsg (onlyNixStore -> contents == [] && configFile == null && !installBootLoader) "In a only Nix store image, the contents must be empty, no configuration must be provided and no bootloader should be installed.");
 # Either both or none of {user,group} need to be set
@@ -225,6 +221,7 @@ let format' = format; in let
     legacy = "1";
     "legacy+gpt" = "2";
     efi = "2";
+    efixbootldr = "3";
     hybrid = "3";
   }.${partitionTableType};
 
@@ -266,6 +263,23 @@ let format' = format; in let
           $diskImage
       ''}
     '';
+    efixbootldr = ''
+      parted --script $diskImage -- \
+        mklabel gpt \
+        mkpart ESP fat32 8MiB 100MiB \
+        set 1 boot on \
+        mkpart BOOT fat32 100MiB ${bootSize} \
+        set 2 bls_boot on \
+        mkpart ROOT ext4 ${bootSize} -1
+      ${optionalString deterministic ''
+          sgdisk \
+          --disk-guid=97FD5997-D90B-4AA3-8D16-C1723AEA73C \
+          --partition-guid=1:1C06F03B-704E-4657-B9CD-681A087A2FDC  \
+          --partition-guid=2:970C694F-AFD0-4B99-B750-CDB7A329AB6F  \
+          --partition-guid=3:${rootGPUID} \
+          $diskImage
+      ''}
+    '';
     hybrid = ''
       parted --script $diskImage -- \
         mklabel gpt \
@@ -436,7 +450,7 @@ let format' = format; in let
     diskImage=nixos.raw
 
     ${if diskSize == "auto" then ''
-      ${if partitionTableType == "efi" || partitionTableType == "hybrid" then ''
+      ${if partitionTableType == "efi" || partitionTableType == "efixbootldr" || partitionTableType == "hybrid" then ''
         # Add the GPT at the end
         gptSpace=$(( 512 * 34 * 1 ))
         # Normally we'd need to account for alignment and things, if bootSize
@@ -536,6 +550,9 @@ let format' = format; in let
         concatStringsSep " " (lib.optional useEFIBoot "-drive if=pflash,format=raw,unit=0,readonly=on,file=${efiFirmware}"
         ++ lib.optionals touchEFIVars [
           "-drive if=pflash,format=raw,unit=1,file=$efiVars"
+        ] ++ lib.optionals (OVMF.systemManagementModeRequired or false) [
+          "-machine" "q35,smm=on"
+          "-global" "driver=cfi.pflash01,property=secure,value=on"
         ]
       );
       inherit memSize;
@@ -567,6 +584,15 @@ let format' = format; in let
 
         ${optionalString touchEFIVars "mount -t efivarfs efivarfs /sys/firmware/efi/efivars"}
       ''}
+      ${optionalString (partitionTableType == "efixbootldr") ''
+        mkdir -p /mnt/{boot,efi}
+        mkfs.vfat -n ESP /dev/vda1
+        mkfs.vfat -n BOOT /dev/vda2
+        mount /dev/vda1 /mnt/efi
+        mount /dev/vda2 /mnt/boot
+
+        ${optionalString touchEFIVars "mount -t efivarfs efivarfs /sys/firmware/efi/efivars"}
+      ''}
 
       # Install a configuration.nix
       mkdir -p /mnt/etc/nixos
@@ -583,6 +609,13 @@ let format' = format; in let
         ''}
 
         # Set up core system link, bootloader (sd-boot, GRUB, uboot, etc.), etc.
+
+        # NOTE: systemd-boot-builder.py calls nix-env --list-generations which
+        # clobbers $HOME/.nix-defexpr/channels/nixos This would cause a  folder
+        # /homeless-shelter to show up in the final image which  in turn breaks
+        # nix builds in the target image if sandboxing is turned off (through
+        # __noChroot for example).
+        export HOME=$TMPDIR
         NIXOS_INSTALL_BOOTLOADER=1 nixos-enter --root $mountPoint -- /nix/var/nix/profiles/system/bin/switch-to-configuration boot
 
         # The above scripts will generate a random machine-id and we don't want to bake a single ID into all our images
diff --git a/nixos/lib/systemd-lib.nix b/nixos/lib/systemd-lib.nix
index c9cca619ed70..ef218e674ebf 100644
--- a/nixos/lib/systemd-lib.nix
+++ b/nixos/lib/systemd-lib.nix
@@ -378,7 +378,7 @@ in rec {
     '';
 
   targetToUnit = name: def:
-    { inherit (def) aliases wantedBy requiredBy enable overrideStrategy;
+    { inherit (def) aliases wantedBy requiredBy upheldBy enable overrideStrategy;
       text =
         ''
           [Unit]
@@ -387,7 +387,7 @@ in rec {
     };
 
   serviceToUnit = name: def:
-    { inherit (def) aliases wantedBy requiredBy enable overrideStrategy;
+    { inherit (def) aliases wantedBy requiredBy upheldBy enable overrideStrategy;
       text = commonUnitText def (''
         [Service]
       '' + (let env = cfg.globalEnvironment // def.environment;
@@ -408,7 +408,7 @@ in rec {
     };
 
   socketToUnit = name: def:
-    { inherit (def) aliases wantedBy requiredBy enable overrideStrategy;
+    { inherit (def) aliases wantedBy requiredBy upheldBy enable overrideStrategy;
       text = commonUnitText def ''
         [Socket]
         ${attrsToSection def.socketConfig}
@@ -418,7 +418,7 @@ in rec {
     };
 
   timerToUnit = name: def:
-    { inherit (def) aliases wantedBy requiredBy enable overrideStrategy;
+    { inherit (def) aliases wantedBy requiredBy upheldBy enable overrideStrategy;
       text = commonUnitText def ''
         [Timer]
         ${attrsToSection def.timerConfig}
@@ -426,7 +426,7 @@ in rec {
     };
 
   pathToUnit = name: def:
-    { inherit (def) aliases wantedBy requiredBy enable overrideStrategy;
+    { inherit (def) aliases wantedBy requiredBy upheldBy enable overrideStrategy;
       text = commonUnitText def ''
         [Path]
         ${attrsToSection def.pathConfig}
@@ -434,7 +434,7 @@ in rec {
     };
 
   mountToUnit = name: def:
-    { inherit (def) aliases wantedBy requiredBy enable overrideStrategy;
+    { inherit (def) aliases wantedBy requiredBy upheldBy enable overrideStrategy;
       text = commonUnitText def ''
         [Mount]
         ${attrsToSection def.mountConfig}
@@ -442,7 +442,7 @@ in rec {
     };
 
   automountToUnit = name: def:
-    { inherit (def) aliases wantedBy requiredBy enable overrideStrategy;
+    { inherit (def) aliases wantedBy requiredBy upheldBy enable overrideStrategy;
       text = commonUnitText def ''
         [Automount]
         ${attrsToSection def.automountConfig}
@@ -450,7 +450,7 @@ in rec {
     };
 
   sliceToUnit = name: def:
-    { inherit (def) aliases wantedBy requiredBy enable overrideStrategy;
+    { inherit (def) aliases wantedBy requiredBy upheldBy enable overrideStrategy;
       text = commonUnitText def ''
         [Slice]
         ${attrsToSection def.sliceConfig}
diff --git a/nixos/lib/systemd-unit-options.nix b/nixos/lib/systemd-unit-options.nix
index df05d165d9e8..e4953ba72dd9 100644
--- a/nixos/lib/systemd-unit-options.nix
+++ b/nixos/lib/systemd-unit-options.nix
@@ -6,7 +6,7 @@ with lib;
 let
   checkService = checkUnitConfig "Service" [
     (assertValueOneOf "Type" [
-      "exec" "simple" "forking" "oneshot" "dbus" "notify" "idle"
+      "exec" "simple" "forking" "oneshot" "dbus" "notify" "notify-reload" "idle"
     ])
     (assertValueOneOf "Restart" [
       "no" "on-success" "on-failure" "on-abnormal" "on-abort" "always"
@@ -21,14 +21,8 @@ in rec {
       let
         defs' = filterOverrides defs;
       in
-        if isList (head defs').value
-        then concatMap (def:
-          if builtins.typeOf def.value == "list"
-          then def.value
-          else
-            throw "The definitions for systemd unit options should be either all lists, representing repeatable options, or all non-lists, but for the option ${showOption loc}, the definitions are a mix of list and non-list ${lib.options.showDefs defs'}"
-        ) defs'
-
+        if any (def: isList def.value) defs'
+        then concatMap (def: toList def.value) defs'
         else mergeEqualOption loc defs';
   };
 
diff --git a/nixos/lib/test-driver/pyproject.toml b/nixos/lib/test-driver/pyproject.toml
index 8638f14dfdae..17b7130a4bad 100644
--- a/nixos/lib/test-driver/pyproject.toml
+++ b/nixos/lib/test-driver/pyproject.toml
@@ -37,7 +37,6 @@ target-version = ['py39']
 include = '\.pyi?$'
 
 [tool.mypy]
-python_version = "3.10"
 warn_redundant_casts = true
 disallow_untyped_calls = true
 disallow_untyped_defs = true
diff --git a/nixos/lib/test-driver/test_driver/driver.py b/nixos/lib/test-driver/test_driver/driver.py
index 786821b0cc0d..f792c0459199 100644
--- a/nixos/lib/test-driver/test_driver/driver.py
+++ b/nixos/lib/test-driver/test_driver/driver.py
@@ -7,11 +7,15 @@ from contextlib import contextmanager
 from pathlib import Path
 from typing import Any, Callable, ContextManager, Dict, Iterator, List, Optional, Union
 
+from colorama import Fore, Style
+
 from test_driver.logger import rootlog
 from test_driver.machine import Machine, NixStartScript, retry
 from test_driver.polling_condition import PollingCondition
 from test_driver.vlan import VLan
 
+SENTINEL = object()
+
 
 def get_tmp_dir() -> Path:
     """Returns a temporary directory that is defined by TMPDIR, TEMP, TMP or CWD
@@ -187,23 +191,61 @@ class Driver:
             # to swallow them and prevent itself from terminating.
             os.kill(os.getpid(), signal.SIGTERM)
 
-    def create_machine(self, args: Dict[str, Any]) -> Machine:
+    def create_machine(
+        self,
+        start_command: str | dict,
+        *,
+        name: Optional[str] = None,
+        keep_vm_state: bool = False,
+    ) -> Machine:
+        # Legacy args handling
+        # FIXME: remove after 24.05
+        if isinstance(start_command, dict):
+            if name is not None or keep_vm_state:
+                raise TypeError(
+                    "Dictionary passed to create_machine must be the only argument"
+                )
+
+            args = start_command
+            start_command = args.pop("startCommand", SENTINEL)
+
+            if start_command is SENTINEL:
+                raise TypeError(
+                    "Dictionary passed to create_machine must contain startCommand"
+                )
+
+            if not isinstance(start_command, str):
+                raise TypeError(
+                    f"startCommand must be a string, got: {repr(start_command)}"
+                )
+
+            name = args.pop("name", None)
+            keep_vm_state = args.pop("keep_vm_state", False)
+
+            if args:
+                raise TypeError(
+                    f"Unsupported arguments passed to create_machine: {args}"
+                )
+
+            rootlog.warning(
+                Fore.YELLOW
+                + Style.BRIGHT
+                + "WARNING: Using create_machine with a single dictionary argument is deprecated and will be removed in NixOS 24.11"
+                + Style.RESET_ALL
+            )
+        # End legacy args handling
+
         tmp_dir = get_tmp_dir()
 
-        if args.get("startCommand"):
-            start_command: str = args.get("startCommand", "")
-            cmd = NixStartScript(start_command)
-            name = args.get("name", cmd.machine_name)
-        else:
-            cmd = Machine.create_startcommand(args)  # type: ignore
-            name = args.get("name", "machine")
+        cmd = NixStartScript(start_command)
+        name = name or cmd.machine_name
 
         return Machine(
             tmp_dir=tmp_dir,
             out_dir=self.out_dir,
             start_command=cmd,
             name=name,
-            keep_vm_state=args.get("keep_vm_state", False),
+            keep_vm_state=keep_vm_state,
         )
 
     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 93411a4a348e..df8628bce956 100644
--- a/nixos/lib/test-driver/test_driver/machine.py
+++ b/nixos/lib/test-driver/test_driver/machine.py
@@ -208,7 +208,6 @@ class StartCommand:
             ),
             stdin=subprocess.PIPE,
             stdout=subprocess.PIPE,
-            stderr=subprocess.STDOUT,
             shell=True,
             cwd=state_dir,
             env=self.build_environment(state_dir, shared_dir),
@@ -235,77 +234,6 @@ class NixStartScript(StartCommand):
         return name
 
 
-class LegacyStartCommand(StartCommand):
-    """Used in some places to create an ad-hoc machine instead of
-    using nix test instrumentation + module system for that purpose.
-    Legacy.
-    """
-
-    def __init__(
-        self,
-        netBackendArgs: Optional[str] = None,  # noqa: N803
-        netFrontendArgs: Optional[str] = None,  # noqa: N803
-        hda: Optional[Tuple[Path, str]] = None,
-        cdrom: Optional[str] = None,
-        usb: Optional[str] = None,
-        bios: Optional[str] = None,
-        qemuBinary: Optional[str] = None,  # noqa: N803
-        qemuFlags: Optional[str] = None,  # noqa: N803
-    ):
-        if qemuBinary is not None:
-            self._cmd = qemuBinary
-        else:
-            self._cmd = "qemu-kvm"
-
-        self._cmd += " -m 384"
-
-        # networking
-        net_backend = "-netdev user,id=net0"
-        net_frontend = "-device virtio-net-pci,netdev=net0"
-        if netBackendArgs is not None:
-            net_backend += "," + netBackendArgs
-        if netFrontendArgs is not None:
-            net_frontend += "," + netFrontendArgs
-        self._cmd += f" {net_backend} {net_frontend}"
-
-        # hda
-        hda_cmd = ""
-        if hda is not None:
-            hda_path = hda[0].resolve()
-            hda_interface = hda[1]
-            if hda_interface == "scsi":
-                hda_cmd += (
-                    f" -drive id=hda,file={hda_path},werror=report,if=none"
-                    " -device scsi-hd,drive=hda"
-                )
-            else:
-                hda_cmd += f" -drive file={hda_path},if={hda_interface},werror=report"
-        self._cmd += hda_cmd
-
-        # cdrom
-        if cdrom is not None:
-            self._cmd += f" -cdrom {cdrom}"
-
-        # usb
-        usb_cmd = ""
-        if usb is not None:
-            # https://github.com/qemu/qemu/blob/master/docs/usb2.txt
-            usb_cmd += (
-                " -device usb-ehci"
-                f" -drive id=usbdisk,file={usb},if=none,readonly"
-                " -device usb-storage,drive=usbdisk "
-            )
-        self._cmd += usb_cmd
-
-        # bios
-        if bios is not None:
-            self._cmd += f" -bios {bios}"
-
-        # qemu flags
-        if qemuFlags is not None:
-            self._cmd += f" {qemuFlags}"
-
-
 class Machine:
     """A handle to the machine with this name, that also knows how to manage
     the machine lifecycle with the help of a start script / command."""
@@ -377,29 +305,6 @@ class Machine:
         self.booted = False
         self.connected = False
 
-    @staticmethod
-    def create_startcommand(args: Dict[str, str]) -> StartCommand:
-        rootlog.warning(
-            "Using legacy create_startcommand(), "
-            "please use proper nix test vm instrumentation, instead "
-            "to generate the appropriate nixos test vm qemu startup script"
-        )
-        hda = None
-        if args.get("hda"):
-            hda_arg: str = args.get("hda", "")
-            hda_arg_path: Path = Path(hda_arg)
-            hda = (hda_arg_path, args.get("hdaInterface", ""))
-        return LegacyStartCommand(
-            netBackendArgs=args.get("netBackendArgs"),
-            netFrontendArgs=args.get("netFrontendArgs"),
-            hda=hda,
-            cdrom=args.get("cdrom"),
-            usb=args.get("usb"),
-            bios=args.get("bios"),
-            qemuBinary=args.get("qemuBinary"),
-            qemuFlags=args.get("qemuFlags"),
-        )
-
     def is_up(self) -> bool:
         return self.booted and self.connected
 
diff --git a/nixos/lib/test-script-prepend.py b/nixos/lib/test-script-prepend.py
index 15e59ce01047..976992ea0015 100644
--- a/nixos/lib/test-script-prepend.py
+++ b/nixos/lib/test-script-prepend.py
@@ -26,6 +26,17 @@ class PollingConditionProtocol(Protocol):
         raise Exception("This is just type information for the Nix test driver")
 
 
+class CreateMachineProtocol(Protocol):
+    def __call__(
+        self,
+        start_command: str | dict,
+        *,
+        name: Optional[str] = None,
+        keep_vm_state: bool = False,
+    ) -> Machine:
+        raise Exception("This is just type information for the Nix test driver")
+
+
 start_all: Callable[[], None]
 subtest: Callable[[str], ContextManager[None]]
 retry: RetryProtocol
@@ -34,7 +45,7 @@ machines: List[Machine]
 vlans: List[VLan]
 driver: Driver
 log: Logger
-create_machine: Callable[[Dict[str, Any]], Machine]
+create_machine: CreateMachineProtocol
 run_tests: Callable[[], None]
 join_all: Callable[[], None]
 serial_stdout_off: Callable[[], None]
diff --git a/nixos/lib/testing/nixos-test-base.nix b/nixos/lib/testing/nixos-test-base.nix
index 59e6e3843367..d76a25361f8c 100644
--- a/nixos/lib/testing/nixos-test-base.nix
+++ b/nixos/lib/testing/nixos-test-base.nix
@@ -16,7 +16,11 @@ in
       # The human version (e.g. 21.05-pre) is left as is, because it is useful
       # for external modules that test with e.g. testers.nixosTest and rely on that
       # version number.
-      config.system.nixos.revision = mkForce "constant-nixos-revision";
+      config.system.nixos = {
+        revision = mkForce "constant-nixos-revision";
+        versionSuffix = mkForce "test";
+        label = mkForce "test";
+      };
     }
 
   ];
diff --git a/nixos/lib/testing/nodes.nix b/nixos/lib/testing/nodes.nix
index 73e6d386fd1d..7941d69e38d2 100644
--- a/nixos/lib/testing/nodes.nix
+++ b/nixos/lib/testing/nodes.nix
@@ -14,6 +14,25 @@ let
     types
     ;
 
+  inherit (hostPkgs) hostPlatform;
+
+  guestSystem =
+    if hostPlatform.isLinux
+    then hostPlatform.system
+    else
+      let
+        hostToGuest = {
+          "x86_64-darwin" = "x86_64-linux";
+          "aarch64-darwin" = "aarch64-linux";
+        };
+
+        supportedHosts = lib.concatStringsSep ", " (lib.attrNames hostToGuest);
+
+        message =
+          "NixOS Test: don't know which VM guest system to pair with VM host system: ${hostPlatform.system}. Perhaps you intended to run the tests on a Linux host, or one of the following systems that may run NixOS tests: ${supportedHosts}";
+      in
+        hostToGuest.${hostPlatform.system} or (throw message);
+
   baseOS =
     import ../eval-config.nix {
       inherit lib;
@@ -27,13 +46,14 @@ let
           ({ config, ... }:
             {
               virtualisation.qemu.package = testModuleArgs.config.qemu.package;
+              virtualisation.host.pkgs = hostPkgs;
             })
           ({ options, ... }: {
             key = "nodes.nix-pkgs";
             config = optionalAttrs (!config.node.pkgsReadOnly) (
               mkIf (!options.nixpkgs.pkgs.isDefined) {
                 # TODO: switch to nixpkgs.hostPlatform and make sure containers-imperative test still evaluates.
-                nixpkgs.system = hostPkgs.stdenv.hostPlatform.system;
+                nixpkgs.system = guestSystem;
               }
             );
           })
diff --git a/nixos/lib/testing/pkgs.nix b/nixos/lib/testing/pkgs.nix
index 22dd586868e3..46d82c65d26a 100644
--- a/nixos/lib/testing/pkgs.nix
+++ b/nixos/lib/testing/pkgs.nix
@@ -2,7 +2,11 @@
 {
   config = {
     # default pkgs for use in VMs
-    _module.args.pkgs = hostPkgs;
+    _module.args.pkgs =
+      # TODO: deprecate it everywhere; not just on darwin. Throw on darwin?
+      lib.warnIf hostPkgs.stdenv.hostPlatform.isDarwin
+        "Do not use the `pkgs` module argument in tests you want to run on darwin. It is ambiguous, and many tests are broken because of it. If you need to use a package on the VM host, use `hostPkgs`. Otherwise, use `config.node.pkgs`, or `config.nodes.<name>.nixpkgs.pkgs`."
+        hostPkgs;
 
     defaults = {
       # TODO: a module to set a shared pkgs, if options.nixpkgs.* is untouched by user (highestPrio) */
diff --git a/nixos/lib/testing/run.nix b/nixos/lib/testing/run.nix
index 9440c1acdfd8..de5a9b97e61d 100644
--- a/nixos/lib/testing/run.nix
+++ b/nixos/lib/testing/run.nix
@@ -41,7 +41,9 @@ in
     rawTestDerivation = hostPkgs.stdenv.mkDerivation {
       name = "vm-test-run-${config.name}";
 
-      requiredSystemFeatures = [ "kvm" "nixos-test" ];
+      requiredSystemFeatures = [ "nixos-test" ]
+        ++ lib.optionals hostPkgs.stdenv.hostPlatform.isLinux [ "kvm" ]
+        ++ lib.optionals hostPkgs.stdenv.hostPlatform.isDarwin [ "apple-virt" ];
 
       buildCommand = ''
         mkdir -p $out
diff --git a/nixos/modules/config/nix.nix b/nixos/modules/config/nix.nix
index 2769d8b25ef6..e6a74bbb73fc 100644
--- a/nixos/modules/config/nix.nix
+++ b/nixos/modules/config/nix.nix
@@ -1,5 +1,5 @@
 /*
-  Manages /etc/nix.conf.
+  Manages /etc/nix/nix.conf.
 
   See also
    - ./nix-channel.nix
diff --git a/nixos/modules/config/no-x-libs.nix b/nixos/modules/config/no-x-libs.nix
index 2f763290e32d..870b3fe77cca 100644
--- a/nixos/modules/config/no-x-libs.nix
+++ b/nixos/modules/config/no-x-libs.nix
@@ -67,7 +67,7 @@ with lib;
       networkmanager-vpnc = super.networkmanager-vpnc.override { withGnome = false; };
       pango = super.pango.override { x11Support = false; };
       pinentry = super.pinentry.override { enabledFlavors = [ "curses" "tty" "emacs" ]; withLibsecret = false; };
-      pipewire = super.pipewire.override { x11Support = false; };
+      pipewire = super.pipewire.override { vulkanSupport = false; x11Support = false; };
       pythonPackagesExtensions = super.pythonPackagesExtensions ++ [
         (python-final: python-prev: {
           # tk feature requires wayland which fails to compile
@@ -83,6 +83,7 @@ with lib;
       # 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; };
+      vte = super.vte.override { gtkVersion = null; };
       zbar = super.zbar.override { enableVideo = false; withXorg = false; };
     }));
   };
diff --git a/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix
index 967ad0846d75..dd34771c0b42 100644
--- a/nixos/modules/config/users-groups.nix
+++ b/nixos/modules/config/users-groups.nix
@@ -649,7 +649,6 @@ in {
         home = "/root";
         shell = mkDefault cfg.defaultUserShell;
         group = "root";
-        initialHashedPassword = mkDefault "!";
       };
       nobody = {
         uid = ids.uids.nobody;
@@ -897,7 +896,26 @@ in {
     ));
 
     warnings =
-      builtins.filter (x: x != null) (
+      flip concatMap (attrValues cfg.users) (user: let
+        unambiguousPasswordConfiguration = 1 >= length (filter (x: x != null) ([
+          user.hashedPassword
+          user.hashedPasswordFile
+          user.password
+        ] ++ optionals cfg.mutableUsers [
+          # For immutable users, initialHashedPassword is set to hashedPassword,
+          # so using these options would always trigger the assertion.
+          user.initialHashedPassword
+          user.initialPassword
+        ]));
+      in optional (!unambiguousPasswordConfiguration) ''
+        The user '${user.name}' has multiple of the options
+        `hashedPassword`, `password`, `hashedPasswordFile`, `initialPassword`
+        & `initialHashedPassword` set to a non-null value.
+        The options silently discard others by the order of precedence
+        given above which can lead to surprising results. To resolve this warning,
+        set at most one of the options above to a non-`null` value.
+      '')
+      ++ builtins.filter (x: x != null) (
         flip mapAttrsToList cfg.users (_: user:
         # This regex matches a subset of the Modular Crypto Format (MCF)[1]
         # informal standard. Since this depends largely on the OS or the
diff --git a/nixos/modules/config/vte.nix b/nixos/modules/config/vte.nix
index a969607f6e0b..48f85246560a 100644
--- a/nixos/modules/config/vte.nix
+++ b/nixos/modules/config/vte.nix
@@ -1,5 +1,3 @@
-# VTE
-
 { config, pkgs, lib, ... }:
 
 with lib;
@@ -9,7 +7,7 @@ let
   vteInitSnippet = ''
     # Show current working directory in VTE terminals window title.
     # Supports both bash and zsh, requires interactive shell.
-    . ${pkgs.vte}/etc/profile.d/vte.sh
+    . ${pkgs.vte.override { gtkVersion = null; }}/etc/profile.d/vte.sh
   '';
 
 in
diff --git a/nixos/modules/config/xdg/portal.nix b/nixos/modules/config/xdg/portal.nix
index 07d4fa76c2e8..5aa23377f9ff 100644
--- a/nixos/modules/config/xdg/portal.nix
+++ b/nixos/modules/config/xdg/portal.nix
@@ -119,19 +119,6 @@ in
     let
       cfg = config.xdg.portal;
       packages = [ pkgs.xdg-desktop-portal ] ++ cfg.extraPortals;
-      configPackages = cfg.configPackages;
-
-      joinedPortals = pkgs.buildEnv {
-        name = "xdg-portals";
-        paths = packages;
-        pathsToLink = [ "/share/xdg-desktop-portal/portals" "/share/applications" ];
-      };
-
-      joinedPortalConfigs = pkgs.buildEnv {
-        name = "xdg-portal-configs";
-        paths = configPackages;
-        pathsToLink = [ "/share/xdg-desktop-portal" ];
-      };
     in
     mkIf cfg.enable {
       warnings = lib.optional (cfg.configPackages == [ ] && cfg.config == { }) ''
@@ -158,17 +145,18 @@ in
       systemd.packages = packages;
 
       environment = {
-        # fixes screen sharing on plasmawayland on non-chromium apps by linking
-        # share/applications/*.desktop files
-        # see https://github.com/NixOS/nixpkgs/issues/145174
-        systemPackages = [ joinedPortals ];
-        pathsToLink = [ "/share/applications" ];
+        systemPackages = packages ++ cfg.configPackages;
+        pathsToLink = [
+          # Portal definitions and upstream desktop environment portal configurations.
+          "/share/xdg-desktop-portal"
+          # .desktop files to register fallback icon and app name.
+          "/share/applications"
+        ];
 
         sessionVariables = {
           GTK_USE_PORTAL = mkIf cfg.gtkUsePortal "1";
           NIXOS_XDG_OPEN_USE_PORTAL = mkIf cfg.xdgOpenUsePortal "1";
-          XDG_DESKTOP_PORTAL_DIR = "${joinedPortals}/share/xdg-desktop-portal/portals";
-          NIXOS_XDG_DESKTOP_PORTAL_CONFIG_DIR = mkIf (cfg.configPackages != [ ]) "${joinedPortalConfigs}/share/xdg-desktop-portal";
+          NIX_XDG_DESKTOP_PORTAL_DIR = "/run/current-system/sw/share/xdg-desktop-portal/portals";
         };
 
         etc = lib.concatMapAttrs
diff --git a/nixos/modules/hardware/printers.nix b/nixos/modules/hardware/printers.nix
index 846ff6f3fb4f..4fb6a192cdd2 100644
--- a/nixos/modules/hardware/printers.nix
+++ b/nixos/modules/hardware/printers.nix
@@ -2,18 +2,23 @@
 with lib;
 let
   cfg = config.hardware.printers;
-  ppdOptionsString = options: optionalString (options != {})
-    (concatStringsSep " "
-      (mapAttrsToList (name: value: "-o '${name}'='${value}'") options)
-    );
-  ensurePrinter = p: ''
-    ${pkgs.cups}/bin/lpadmin -p '${p.name}' -E \
-      ${optionalString (p.location != null) "-L '${p.location}'"} \
-      ${optionalString (p.description != null) "-D '${p.description}'"} \
-      -v '${p.deviceUri}' \
-      -m '${p.model}' \
-      ${ppdOptionsString p.ppdOptions}
+
+  ensurePrinter = p: let
+    args = cli.toGNUCommandLineShell {} ({
+      p = p.name;
+      v = p.deviceUri;
+      m = p.model;
+    } // optionalAttrs (p.location != null) {
+      L = p.location;
+    } // optionalAttrs (p.description != null) {
+      D = p.description;
+    } // optionalAttrs (p.ppdOptions != {}) {
+      o = mapAttrsToList (name: value: "'${name}'='${value}'") p.ppdOptions;
+    });
+  in ''
+    ${pkgs.cups}/bin/lpadmin ${args} -E
   '';
+
   ensureDefaultPrinter = name: ''
     ${pkgs.cups}/bin/lpadmin -d '${name}'
   '';
diff --git a/nixos/modules/hardware/video/switcheroo-control.nix b/nixos/modules/hardware/video/switcheroo-control.nix
index 982388f8e5f4..967120d6744a 100644
--- a/nixos/modules/hardware/video/switcheroo-control.nix
+++ b/nixos/modules/hardware/video/switcheroo-control.nix
@@ -1,18 +1,19 @@
 { config, pkgs, lib, ... }:
 
-with lib;
 let
-  pkg = [ pkgs.switcheroo-control ];
   cfg = config.services.switcherooControl;
 in {
   options.services.switcherooControl = {
-    enable = mkEnableOption (lib.mdDoc "switcheroo-control, a D-Bus service to check the availability of dual-GPU");
+    enable = lib.mkEnableOption "switcheroo-control, a D-Bus service to check the availability of dual-GPU";
+    package = lib.mkPackageOption pkgs "switcheroo-control" { };
   };
 
-  config = mkIf cfg.enable {
-    services.dbus.packages = pkg;
-    environment.systemPackages = pkg;
-    systemd.packages = pkg;
-    systemd.targets.multi-user.wants = [ "switcheroo-control.service" ];
+  config = lib.mkIf cfg.enable {
+    services.dbus.packages = [ cfg.package ];
+    environment.systemPackages = [ cfg.package ];
+    systemd = {
+      packages = [ cfg.package ];
+      targets.multi-user.wants = [ "switcheroo-control.service" ];
+    };
   };
 }
diff --git a/nixos/modules/i18n/input-method/fcitx5.nix b/nixos/modules/i18n/input-method/fcitx5.nix
index 530727f3f292..ee8d2652b1c7 100644
--- a/nixos/modules/i18n/input-method/fcitx5.nix
+++ b/nixos/modules/i18n/input-method/fcitx5.nix
@@ -5,7 +5,10 @@ with lib;
 let
   im = config.i18n.inputMethod;
   cfg = im.fcitx5;
-  fcitx5Package = pkgs.fcitx5-with-addons.override { inherit (cfg) addons; };
+  fcitx5Package =
+    if cfg.plasma6Support
+    then pkgs.qt6Packages.fcitx5-with-addons.override { inherit (cfg) addons; }
+    else pkgs.libsForQt5.fcitx5-with-addons.override { inherit (cfg) addons; };
   settingsFormat = pkgs.formats.ini { };
 in
 {
@@ -27,6 +30,15 @@ in
           See [Using Fcitx 5 on Wayland](https://fcitx-im.org/wiki/Using_Fcitx_5_on_Wayland).
         '';
       };
+      plasma6Support = mkOption {
+        type = types.bool;
+        default = config.services.xserver.desktopManager.plasma6.enable;
+        defaultText = literalExpression "config.services.xserver.desktopManager.plasma6.enable";
+        description = lib.mdDoc ''
+          Use qt6 versions of fcitx5 packages.
+          Required for configuring fcitx5 in KDE System Settings.
+        '';
+      };
       quickPhrase = mkOption {
         type = with types; attrsOf str;
         default = { };
diff --git a/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares-plasma6.nix b/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares-plasma6.nix
new file mode 100644
index 000000000000..11118db3aae2
--- /dev/null
+++ b/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares-plasma6.nix
@@ -0,0 +1,46 @@
+# This module defines a NixOS installation CD that contains Plasma 6.
+
+{ pkgs, ... }:
+
+{
+  imports = [ ./installation-cd-graphical-calamares.nix ];
+
+  isoImage.edition = "plasma6";
+
+  services.xserver = {
+    desktopManager.plasma6.enable = true;
+
+    # Automatically login as nixos.
+    displayManager = {
+      sddm.enable = true;
+      autoLogin = {
+        enable = true;
+        user = "nixos";
+      };
+    };
+  };
+
+  environment.systemPackages = [
+    # FIXME: using Qt5 builds of Maliit as upstream has not ported to Qt6 yet
+    pkgs.maliit-framework
+    pkgs.maliit-keyboard
+  ];
+
+  system.activationScripts.installerDesktop = let
+
+    # Comes from documentation.nix when xserver and nixos.enable are true.
+    manualDesktopFile = "/run/current-system/sw/share/applications/nixos-manual.desktop";
+
+    homeDir = "/home/nixos/";
+    desktopDir = homeDir + "Desktop/";
+
+  in ''
+    mkdir -p ${desktopDir}
+    chown nixos ${homeDir} ${desktopDir}
+
+    ln -sfT ${manualDesktopFile} ${desktopDir + "nixos-manual.desktop"}
+    ln -sfT ${pkgs.gparted}/share/applications/gparted.desktop ${desktopDir + "gparted.desktop"}
+    ln -sfT ${pkgs.calamares-nixos}/share/applications/io.calamares.calamares.desktop ${desktopDir + "io.calamares.calamares.desktop"}
+  '';
+
+}
diff --git a/nixos/modules/installer/cd-dvd/installation-cd-minimal-new-kernel-no-zfs.nix b/nixos/modules/installer/cd-dvd/installation-cd-minimal-new-kernel-no-zfs.nix
index 9d09cdbe0206..fc3cb08bdbbb 100644
--- a/nixos/modules/installer/cd-dvd/installation-cd-minimal-new-kernel-no-zfs.nix
+++ b/nixos/modules/installer/cd-dvd/installation-cd-minimal-new-kernel-no-zfs.nix
@@ -1,15 +1,7 @@
-{ pkgs, ... }:
+{ lib, ... }:
 
 {
   imports = [ ./installation-cd-minimal-new-kernel.nix ];
 
-  # Makes `availableOn` fail for zfs, see <nixos/modules/profiles/base.nix>.
-  # This is a workaround since we cannot remove the `"zfs"` string from `supportedFilesystems`.
-  # The proper fix would be to make `supportedFilesystems` an attrset with true/false which we
-  # could then `lib.mkForce false`
-  nixpkgs.overlays = [(final: super: {
-    zfs = super.zfs.overrideAttrs(_: {
-      meta.platforms = [];
-    });
-  })];
+  boot.supportedFilesystems.zfs = lib.mkForce false;
 }
diff --git a/nixos/modules/installer/netboot/netboot.nix b/nixos/modules/installer/netboot/netboot.nix
index a50f22cbe471..028a2d74041e 100644
--- a/nixos/modules/installer/netboot/netboot.nix
+++ b/nixos/modules/installer/netboot/netboot.nix
@@ -62,19 +62,12 @@ with lib;
       };
 
     fileSystems."/nix/store" = mkImageMediaOverride
-      { fsType = "overlay";
-        device = "overlay";
-        options = [
-          "lowerdir=/nix/.ro-store"
-          "upperdir=/nix/.rw-store/store"
-          "workdir=/nix/.rw-store/work"
-        ];
-
-        depends = [
-          "/nix/.ro-store"
-          "/nix/.rw-store/store"
-          "/nix/.rw-store/work"
-        ];
+      { overlay = {
+          lowerdir = [ "/nix/.ro-store" ];
+          upperdir = "/nix/.rw-store/store";
+          workdir = "/nix/.rw-store/work";
+        };
+        neededForBoot = true;
       };
 
     boot.initrd.availableKernelModules = [ "squashfs" "overlay" ];
diff --git a/nixos/modules/installer/sd-card/sd-image-aarch64-new-kernel-no-zfs-installer.nix b/nixos/modules/installer/sd-card/sd-image-aarch64-new-kernel-no-zfs-installer.nix
index 0e5055960294..da5410057887 100644
--- a/nixos/modules/installer/sd-card/sd-image-aarch64-new-kernel-no-zfs-installer.nix
+++ b/nixos/modules/installer/sd-card/sd-image-aarch64-new-kernel-no-zfs-installer.nix
@@ -1,15 +1,7 @@
-{ pkgs, ... }:
+{ lib, ... }:
 
 {
   imports = [ ./sd-image-aarch64-new-kernel-installer.nix ];
 
-  # Makes `availableOn` fail for zfs, see <nixos/modules/profiles/base.nix>.
-  # This is a workaround since we cannot remove the `"zfs"` string from `supportedFilesystems`.
-  # The proper fix would be to make `supportedFilesystems` an attrset with true/false which we
-  # could then `lib.mkForce false`
-  nixpkgs.overlays = [(final: super: {
-    zfs = super.zfs.overrideAttrs(_: {
-      meta.platforms = [];
-    });
-  })];
+  boot.supportedFilesystems.zfs = lib.mkForce false;
 }
diff --git a/nixos/modules/misc/nixpkgs-flake.nix b/nixos/modules/misc/nixpkgs-flake.nix
new file mode 100644
index 000000000000..8bfe05ca1994
--- /dev/null
+++ b/nixos/modules/misc/nixpkgs-flake.nix
@@ -0,0 +1,105 @@
+{ config, options, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.nixpkgs.flake;
+in
+{
+  options.nixpkgs.flake = {
+    source = mkOption {
+      # In newer Nix versions, particularly with lazy trees, outPath of
+      # flakes becomes a Nix-language path object. We deliberately allow this
+      # to gracefully come through the interface in discussion with @roberth.
+      #
+      # See: https://github.com/NixOS/nixpkgs/pull/278522#discussion_r1460292639
+      type = types.nullOr (types.either types.str types.path);
+
+      default = null;
+      defaultText = "if (using nixpkgsFlake.lib.nixosSystem) then self.outPath else null";
+
+      example = ''builtins.fetchTarball { name = "source"; sha256 = "${lib.fakeHash}"; url = "https://github.com/nixos/nixpkgs/archive/somecommit.tar.gz"; }'';
+
+      description = mdDoc ''
+        The path to the nixpkgs sources used to build the system. This is automatically set up to be
+        the store path of the nixpkgs flake used to build the system if using
+        `nixpkgs.lib.nixosSystem`, and is otherwise null by default.
+
+        This can also be optionally set if the NixOS system is not built with a flake but still uses
+        pinned sources: set this to the store path for the nixpkgs sources used to build the system,
+        as may be obtained by `builtins.fetchTarball`, for example.
+
+        Note: the name of the store path must be "source" due to
+        <https://github.com/NixOS/nix/issues/7075>.
+      '';
+    };
+
+    setNixPath = mkOption {
+      type = types.bool;
+
+      default = cfg.source != null;
+      defaultText = "config.nixpkgs.flake.source != null";
+
+      description = mdDoc ''
+        Whether to set {env}`NIX_PATH` to include `nixpkgs=flake:nixpkgs` such that `<nixpkgs>`
+        lookups receive the version of nixpkgs that the system was built with, in concert with
+        {option}`nixpkgs.flake.setFlakeRegistry`.
+
+        This is on by default for NixOS configurations built with flakes.
+
+        This makes {command}`nix-build '<nixpkgs>' -A hello` work out of the box on flake systems.
+
+        Note that this option makes the NixOS closure depend on the nixpkgs sources, which may add
+        undesired closure size if the system will not have any nix commands run on it.
+      '';
+    };
+
+    setFlakeRegistry = mkOption {
+      type = types.bool;
+
+      default = cfg.source != null;
+      defaultText = "config.nixpkgs.flake.source != null";
+
+      description = mdDoc ''
+        Whether to pin nixpkgs in the system-wide flake registry (`/etc/nix/registry.json`) to the
+        store path of the sources of nixpkgs used to build the NixOS system.
+
+        This is on by default for NixOS configurations built with flakes.
+
+        This option makes {command}`nix run nixpkgs#hello` reuse dependencies from the system, avoid
+        refetching nixpkgs, and have a consistent result every time.
+
+        Note that this option makes the NixOS closure depend on the nixpkgs sources, which may add
+        undesired closure size if the system will not have any nix commands run on it.
+      '';
+    };
+  };
+
+  config = mkIf (cfg.source != null) (mkMerge [
+    {
+      assertions = [
+        {
+          assertion = cfg.setNixPath -> cfg.setFlakeRegistry;
+          message = ''
+            Setting `nixpkgs.flake.setNixPath` requires that `nixpkgs.flake.setFlakeRegistry` also
+            be set, since it is implemented in terms of indirection through the flake registry.
+          '';
+        }
+      ];
+    }
+    (mkIf cfg.setFlakeRegistry {
+      nix.registry.nixpkgs.to = mkDefault {
+        type = "path";
+        path = cfg.source;
+      };
+    })
+    (mkIf cfg.setNixPath {
+      # N.B. This does not include nixos-config in NIX_PATH unlike modules/config/nix-channel.nix
+      # because we would need some kind of evil shim taking the *calling* flake's self path,
+      # perhaps, to ever make that work (in order to know where the Nix expr for the system came
+      # from and how to call it).
+      nix.nixPath = mkDefault ([ "nixpkgs=flake:nixpkgs" ]
+        ++ optional config.nix.channel.enable "/nix/var/nix/profiles/per-user/root/channels");
+    })
+  ]);
+}
diff --git a/nixos/modules/misc/nixpkgs.nix b/nixos/modules/misc/nixpkgs.nix
index da321a923449..10f800cd741a 100644
--- a/nixos/modules/misc/nixpkgs.nix
+++ b/nixos/modules/misc/nixpkgs.nix
@@ -208,7 +208,11 @@ in
       example = { system = "x86_64-linux"; };
       # Make sure that the final value has all fields for sake of other modules
       # referring to this.
-      apply = lib.systems.elaborate;
+      apply = inputBuildPlatform:
+        let elaborated = lib.systems.elaborate inputBuildPlatform;
+        in if lib.systems.equals elaborated cfg.hostPlatform
+          then cfg.hostPlatform  # make identical, so that `==` equality works; see https://github.com/NixOS/nixpkgs/issues/278001
+          else elaborated;
       defaultText = literalExpression
         ''config.nixpkgs.hostPlatform'';
       description = lib.mdDoc ''
diff --git a/nixos/modules/misc/nixpkgs/test.nix b/nixos/modules/misc/nixpkgs/test.nix
index 0536cfc9624a..be9a88a07788 100644
--- a/nixos/modules/misc/nixpkgs/test.nix
+++ b/nixos/modules/misc/nixpkgs/test.nix
@@ -12,6 +12,10 @@ let
     nixpkgs.hostPlatform = "aarch64-linux";
     nixpkgs.buildPlatform = "aarch64-darwin";
   };
+  withSameHostAndBuild = eval {
+    nixpkgs.hostPlatform = "aarch64-linux";
+    nixpkgs.buildPlatform = "aarch64-linux";
+  };
   ambiguous = {
     _file = "ambiguous.nix";
     nixpkgs.hostPlatform = "aarch64-linux";
@@ -81,6 +85,8 @@ lib.recurseIntoAttrs {
     assert withHost._module.args.pkgs.stdenv.buildPlatform.system == "aarch64-linux";
     assert withHostAndBuild._module.args.pkgs.stdenv.hostPlatform.system == "aarch64-linux";
     assert withHostAndBuild._module.args.pkgs.stdenv.buildPlatform.system == "aarch64-darwin";
+    assert withSameHostAndBuild.config.nixpkgs.buildPlatform == withSameHostAndBuild.config.nixpkgs.hostPlatform;
+    assert withSameHostAndBuild._module.args.pkgs.stdenv.buildPlatform == withSameHostAndBuild._module.args.pkgs.stdenv.hostPlatform;
     assert builtins.trace (lib.head (getErrors ambiguous))
       getErrors ambiguous ==
         [''
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 2203e1286fd1..cfe2350d5762 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -133,6 +133,7 @@
   ./misc/meta.nix
   ./misc/nixops-autoluks.nix
   ./misc/nixpkgs.nix
+  ./misc/nixpkgs-flake.nix
   ./misc/passthru.nix
   ./misc/version.nix
   ./misc/wordlist.nix
@@ -242,6 +243,7 @@
   ./programs/proxychains.nix
   ./programs/qdmr.nix
   ./programs/qt5ct.nix
+  ./programs/quark-goldleaf.nix
   ./programs/regreet.nix
   ./programs/rog-control-center.nix
   ./programs/rust-motd.nix
@@ -511,6 +513,7 @@
   ./services/editors/infinoted.nix
   ./services/finance/odoo.nix
   ./services/games/archisteamfarm.nix
+  ./services/games/armagetronad.nix
   ./services/games/crossfire-server.nix
   ./services/games/deliantra-server.nix
   ./services/games/factorio.nix
@@ -546,6 +549,8 @@
   ./services/hardware/kanata.nix
   ./services/hardware/lcd.nix
   ./services/hardware/lirc.nix
+  ./services/hardware/nvidia-container-toolkit-cdi-generator
+  ./services/hardware/monado.nix
   ./services/hardware/nvidia-optimus.nix
   ./services/hardware/openrgb.nix
   ./services/hardware/pcscd.nix
@@ -581,6 +586,7 @@
   ./services/home-automation/govee2mqtt.nix
   ./services/home-automation/home-assistant.nix
   ./services/home-automation/homeassistant-satellite.nix
+  ./services/home-automation/matter-server.nix
   ./services/home-automation/zigbee2mqtt.nix
   ./services/home-automation/zwave-js.nix
   ./services/logging/SystemdJournal2Gelf.nix
@@ -714,6 +720,7 @@
   ./services/misc/libreddit.nix
   ./services/misc/lidarr.nix
   ./services/misc/lifecycled.nix
+  ./services/misc/llama-cpp.nix
   ./services/misc/logkeys.nix
   ./services/misc/mame.nix
   ./services/misc/mbpfan.nix
@@ -782,6 +789,7 @@
   ./services/misc/tiddlywiki.nix
   ./services/misc/tp-auto-kbbl.nix
   ./services/misc/tuxclocker.nix
+  ./services/misc/transfer-sh.nix
   ./services/misc/tzupdate.nix
   ./services/misc/uhub.nix
   ./services/misc/weechat.nix
@@ -840,6 +848,7 @@
   ./services/monitoring/riemann.nix
   ./services/monitoring/rustdesk-server.nix
   ./services/monitoring/scollector.nix
+  ./services/monitoring/scrutiny.nix
   ./services/monitoring/smartd.nix
   ./services/monitoring/snmpd.nix
   ./services/monitoring/statsd.nix
@@ -898,7 +907,6 @@
   ./services/networking/autossh.nix
   ./services/networking/avahi-daemon.nix
   ./services/networking/babeld.nix
-  ./services/networking/bee-clef.nix
   ./services/networking/bee.nix
   ./services/networking/biboumi.nix
   ./services/networking/bind.nix
@@ -963,6 +971,7 @@
   ./services/networking/gns3-server.nix
   ./services/networking/gnunet.nix
   ./services/networking/go-autoconfig.nix
+  ./services/networking/go-camo.nix
   ./services/networking/go-neb.nix
   ./services/networking/go-shadowsocks2.nix
   ./services/networking/gobgpd.nix
@@ -1320,6 +1329,7 @@
   ./services/web-apps/mastodon.nix
   ./services/web-apps/matomo.nix
   ./services/web-apps/mattermost.nix
+  ./services/web-apps/mealie.nix
   ./services/web-apps/mediawiki.nix
   ./services/web-apps/meme-bingo-web.nix
   ./services/web-apps/microbin.nix
@@ -1401,7 +1411,6 @@
   ./services/web-servers/unit/default.nix
   ./services/web-servers/uwsgi.nix
   ./services/web-servers/varnish/default.nix
-  ./services/web-servers/zope2.nix
   ./services/x11/clight.nix
   ./services/x11/colord.nix
   ./services/x11/desktop-managers/default.nix
diff --git a/nixos/modules/programs/ccache.nix b/nixos/modules/programs/ccache.nix
index 567c853e8c7d..7972b2ac4a56 100644
--- a/nixos/modules/programs/ccache.nix
+++ b/nixos/modules/programs/ccache.nix
@@ -1,35 +1,43 @@
 { config, pkgs, lib, ... }:
 
-with lib;
 let
   cfg = config.programs.ccache;
 in {
   options.programs.ccache = {
     # host configuration
-    enable = mkEnableOption (lib.mdDoc "CCache");
-    cacheDir = mkOption {
-      type = types.path;
+    enable = lib.mkEnableOption (lib.mdDoc "CCache");
+    cacheDir = lib.mkOption {
+      type = lib.types.path;
       description = lib.mdDoc "CCache directory";
       default = "/var/cache/ccache";
     };
     # target configuration
-    packageNames = mkOption {
-      type = types.listOf types.str;
+    packageNames = lib.mkOption {
+      type = lib.types.listOf lib.types.str;
       description = lib.mdDoc "Nix top-level packages to be compiled using CCache";
       default = [];
       example = [ "wxGTK32" "ffmpeg" "libav_all" ];
     };
+    owner = lib.mkOption {
+      type = lib.types.str;
+      default = "root";
+      description = lib.mdDoc "Owner of CCache directory";
+    };
+    group = lib.mkOption {
+      type = lib.types.str;
+      default = "nixbld";
+      description = lib.mdDoc "Group owner of CCache directory";
+    };
   };
 
-  config = mkMerge [
+  config = lib.mkMerge [
     # host configuration
-    (mkIf cfg.enable {
-      systemd.tmpfiles.rules = [ "d ${cfg.cacheDir} 0770 root nixbld -" ];
+    (lib.mkIf cfg.enable {
+      systemd.tmpfiles.rules = [ "d ${cfg.cacheDir} 0770 ${cfg.owner} ${cfg.group} -" ];
 
       # "nix-ccache --show-stats" and "nix-ccache --clear"
       security.wrappers.nix-ccache = {
-        owner = "root";
-        group = "nixbld";
+        inherit (cfg) owner group;
         setuid = false;
         setgid = true;
         source = pkgs.writeScript "nix-ccache.pl" ''
@@ -50,9 +58,9 @@ in {
     })
 
     # target configuration
-    (mkIf (cfg.packageNames != []) {
+    (lib.mkIf (cfg.packageNames != []) {
       nixpkgs.overlays = [
-        (self: super: genAttrs cfg.packageNames (pn: super.${pn}.override { stdenv = builtins.trace "with ccache: ${pn}" self.ccacheStdenv; }))
+        (self: super: lib.genAttrs cfg.packageNames (pn: super.${pn}.override { stdenv = builtins.trace "with ccache: ${pn}" self.ccacheStdenv; }))
 
         (self: super: {
           ccacheWrapper = super.ccacheWrapper.override {
@@ -65,7 +73,7 @@ in {
                 echo "Directory '$CCACHE_DIR' does not exist"
                 echo "Please create it with:"
                 echo "  sudo mkdir -m0770 '$CCACHE_DIR'"
-                echo "  sudo chown root:nixbld '$CCACHE_DIR'"
+                echo "  sudo chown ${cfg.owner}:${cfg.group} '$CCACHE_DIR'"
                 echo "====="
                 exit 1
               fi
diff --git a/nixos/modules/programs/gnupg.nix b/nixos/modules/programs/gnupg.nix
index 8f82de033666..179d2de87cc5 100644
--- a/nixos/modules/programs/gnupg.nix
+++ b/nixos/modules/programs/gnupg.nix
@@ -15,6 +15,7 @@ let
   defaultPinentryFlavor =
     if xserverCfg.desktopManager.lxqt.enable
     || xserverCfg.desktopManager.plasma5.enable
+    || xserverCfg.desktopManager.plasma6.enable
     || xserverCfg.desktopManager.deepin.enable then
       "qt"
     else if xserverCfg.desktopManager.xfce.enable then
diff --git a/nixos/modules/programs/kdeconnect.nix b/nixos/modules/programs/kdeconnect.nix
index a16fad03eefe..8cdf1eb4e645 100644
--- a/nixos/modules/programs/kdeconnect.nix
+++ b/nixos/modules/programs/kdeconnect.nix
@@ -20,7 +20,10 @@ with lib;
       cfg = config.programs.kdeconnect;
     in
       mkIf cfg.enable {
-        environment.systemPackages = [ cfg.package ];
+        environment.systemPackages = [
+          cfg.package
+          pkgs.sshfs
+        ];
         networking.firewall = rec {
           allowedTCPPortRanges = [ { from = 1714; to = 1764; } ];
           allowedUDPPortRanges = allowedTCPPortRanges;
diff --git a/nixos/modules/programs/quark-goldleaf.nix b/nixos/modules/programs/quark-goldleaf.nix
new file mode 100644
index 000000000000..71aadc8c594e
--- /dev/null
+++ b/nixos/modules/programs/quark-goldleaf.nix
@@ -0,0 +1,18 @@
+{ config, lib, pkgs, ... }:
+let
+  cfg = config.programs.quark-goldleaf;
+in
+{
+  options = {
+    programs.quark-goldleaf = {
+      enable = lib.mkEnableOption "quark-goldleaf with udev rules applied";
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.quark-goldleaf ];
+    services.udev.packages = [ pkgs.quark-goldleaf ];
+  };
+
+  meta.maintainers = pkgs.quark-goldleaf.meta.maintainers;
+}
diff --git a/nixos/modules/programs/steam.nix b/nixos/modules/programs/steam.nix
index 29c449c16946..c7f1e622f7ba 100644
--- a/nixos/modules/programs/steam.nix
+++ b/nixos/modules/programs/steam.nix
@@ -82,6 +82,14 @@ in {
       '';
     };
 
+    localNetworkGameTransfers.openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Open ports in the firewall for Steam Local Network Game Transfers.
+      '';
+    };
+
     gamescopeSession = mkOption {
       description = mdDoc "Run a GameScope driven Steam session from your display-manager";
       default = {};
@@ -139,15 +147,23 @@ in {
     ] ++ lib.optional cfg.gamescopeSession.enable steam-gamescope;
 
     networking.firewall = lib.mkMerge [
+      (mkIf (cfg.remotePlay.openFirewall || cfg.localNetworkGameTransfers.openFirewall) {
+        allowedUDPPorts = [ 27036 ]; # Peer discovery
+      })
+
       (mkIf cfg.remotePlay.openFirewall {
         allowedTCPPorts = [ 27036 ];
-        allowedUDPPortRanges = [ { from = 27031; to = 27036; } ];
+        allowedUDPPortRanges = [ { from = 27031; to = 27035; } ];
       })
 
       (mkIf cfg.dedicatedServer.openFirewall {
         allowedTCPPorts = [ 27015 ]; # SRCDS Rcon port
         allowedUDPPorts = [ 27015 ]; # Gameplay traffic
       })
+
+      (mkIf cfg.localNetworkGameTransfers.openFirewall {
+        allowedTCPPorts = [ 27040 ]; # Data transfers
+      })
     ];
   };
 
diff --git a/nixos/modules/programs/wayland/sway.nix b/nixos/modules/programs/wayland/sway.nix
index 57ee629b2881..ca2503ae5da7 100644
--- a/nixos/modules/programs/wayland/sway.nix
+++ b/nixos/modules/programs/wayland/sway.nix
@@ -119,10 +119,10 @@ in {
     extraPackages = mkOption {
       type = with types; listOf package;
       default = with pkgs; [
-        swaylock swayidle foot dmenu
+        swaylock swayidle foot dmenu wmenu
       ];
       defaultText = literalExpression ''
-        with pkgs; [ swaylock swayidle foot dmenu ];
+        with pkgs; [ swaylock swayidle foot dmenu wmenu ];
       '';
       example = literalExpression ''
         with pkgs; [
diff --git a/nixos/modules/programs/yazi.nix b/nixos/modules/programs/yazi.nix
index 273a7eeed05f..338eddb60d80 100644
--- a/nixos/modules/programs/yazi.nix
+++ b/nixos/modules/programs/yazi.nix
@@ -22,7 +22,7 @@ in
             description = lib.mdDoc ''
               Configuration included in `${name}.toml`.
 
-              See https://github.com/sxyazi/yazi/blob/v${cfg.package.version}/config/docs/${name}.md for documentation.
+              See https://yazi-rs.github.io/docs/configuration/${name}/ for documentation.
             '';
           }))
           names);
@@ -47,7 +47,5 @@ in
   };
   meta = {
     maintainers = with lib.maintainers; [ linsui ];
-    # The version of the package is used in the doc.
-    buildDocsInSandbox = false;
   };
 }
diff --git a/nixos/modules/security/ca.nix b/nixos/modules/security/ca.nix
index 3cd56bff04d1..ae188ea709dd 100644
--- a/nixos/modules/security/ca.nix
+++ b/nixos/modules/security/ca.nix
@@ -11,7 +11,8 @@ let
     extraCertificateFiles = cfg.certificateFiles;
     extraCertificateStrings = cfg.certificates;
   };
-  caBundle = "${cacertPackage}/etc/ssl/certs/ca-bundle.crt";
+  caBundleName = if cfg.useCompatibleBundle then "ca-no-trust-rules-bundle.crt" else "ca-bundle.crt";
+  caBundle = "${cacertPackage}/etc/ssl/certs/${caBundleName}";
 
 in
 
@@ -23,6 +24,17 @@ in
       internal = true;
     };
 
+    security.pki.useCompatibleBundle = mkEnableOption ''usage of a compatibility bundle.
+
+      Such a bundle consist exclusively of `BEGIN CERTIFICATE` and no `BEGIN TRUSTED CERTIFICATE`,
+      which is a OpenSSL specific PEM format.
+
+      It is known to be incompatible with certain software stacks.
+
+      Nevertheless, enabling this will strip all additional trust rules provided by the
+      certificates themselves, this can have security consequences depending on your usecases.
+    '';
+
     security.pki.certificateFiles = mkOption {
       type = types.listOf types.path;
       default = [];
diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix
index ed03254cb5ee..560e5eff5c39 100644
--- a/nixos/modules/security/pam.nix
+++ b/nixos/modules/security/pam.nix
@@ -96,6 +96,10 @@ let
 
   pamOpts = { config, name, ... }: let cfg = config; in let config = parentConfig; in {
 
+    imports = [
+      (lib.mkRenamedOptionModule [ "enableKwallet" ] [ "kwallet" "enable" ])
+    ];
+
     options = {
 
       name = mkOption {
@@ -462,16 +466,23 @@ let
         '';
       };
 
-      enableKwallet = mkOption {
-        default = false;
-        type = types.bool;
-        description = lib.mdDoc ''
-          If enabled, pam_wallet will attempt to automatically unlock the
-          user's default KDE wallet upon login. If the user has no wallet named
-          "kdewallet", or the login password does not match their wallet
-          password, KDE will prompt separately after login.
-        '';
+      kwallet = {
+        enable = mkOption {
+          default = false;
+          type = types.bool;
+          description = lib.mdDoc ''
+            If enabled, pam_wallet will attempt to automatically unlock the
+            user's default KDE wallet upon login. If the user has no wallet named
+            "kdewallet", or the login password does not match their wallet
+            password, KDE will prompt separately after login.
+          '';
+        };
+
+        package = mkPackageOption pkgs.plasma5Packages "kwallet-pam" {
+          pkgsText = "pkgs.plasma5Packages";
+        };
       };
+
       sssdStrictAccess = mkOption {
         default = false;
         type = types.bool;
@@ -686,7 +697,7 @@ let
             (config.security.pam.enableEcryptfs
               || config.security.pam.enableFscrypt
               || cfg.pamMount
-              || cfg.enableKwallet
+              || cfg.kwallet.enable
               || cfg.enableGnomeKeyring
               || config.services.intune.enable
               || cfg.googleAuthenticator.enable
@@ -711,9 +722,7 @@ let
               { name = "mount"; enable = cfg.pamMount; control = "optional"; modulePath = "${pkgs.pam_mount}/lib/security/pam_mount.so"; settings = {
                 disable_interactive = true;
               }; }
-              { name = "kwallet5"; enable = cfg.enableKwallet; control = "optional"; modulePath = "${pkgs.plasma5Packages.kwallet-pam}/lib/security/pam_kwallet5.so"; settings = {
-                kwalletd = "${pkgs.plasma5Packages.kwallet.bin}/bin/kwalletd5";
-              }; }
+              { name = "kwallet"; enable = cfg.kwallet.enable; control = "optional"; modulePath = "${cfg.kwallet.package}/lib/security/pam_kwallet5.so"; }
               { name = "gnome_keyring"; enable = cfg.enableGnomeKeyring; control = "optional"; modulePath = "${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so"; }
               { name = "intune"; enable = config.services.intune.enable; control = "optional"; modulePath = "${pkgs.intune-portal}/lib/security/pam_intune.so"; }
               { name = "gnupg"; enable = cfg.gnupg.enable; control = "optional"; modulePath = "${pkgs.pam_gnupg}/lib/security/pam_gnupg.so"; settings = {
@@ -848,9 +857,7 @@ let
             order = "user,group,default";
             debug = true;
           }; }
-          { name = "kwallet5"; enable = cfg.enableKwallet; control = "optional"; modulePath = "${pkgs.plasma5Packages.kwallet-pam}/lib/security/pam_kwallet5.so"; settings = {
-            kwalletd = "${pkgs.plasma5Packages.kwallet.bin}/bin/kwalletd5";
-          }; }
+          { name = "kwallet"; enable = cfg.kwallet.enable; control = "optional"; modulePath = "${cfg.kwallet.package}/lib/security/pam_kwallet5.so"; }
           { name = "gnome_keyring"; enable = cfg.enableGnomeKeyring; control = "optional"; modulePath = "${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so"; settings = {
             auto_start = true;
           }; }
@@ -1458,9 +1465,9 @@ in
         '';
       }
       {
-        assertion = config.security.pam.zfs.enable -> (config.boot.zfs.enabled || config.boot.zfs.enableUnstable);
+        assertion = config.security.pam.zfs.enable -> config.boot.zfs.enabled;
         message = ''
-          `security.pam.zfs.enable` requires enabling ZFS (`boot.zfs.enabled` or `boot.zfs.enableUnstable`).
+          `security.pam.zfs.enable` requires enabling ZFS (`boot.zfs.enabled`).
         '';
       }
       {
diff --git a/nixos/modules/services/cluster/kubernetes/default.nix b/nixos/modules/services/cluster/kubernetes/default.nix
index 3fb916c76971..a920b6cb1268 100644
--- a/nixos/modules/services/cluster/kubernetes/default.nix
+++ b/nixos/modules/services/cluster/kubernetes/default.nix
@@ -285,7 +285,7 @@ in {
       systemd.tmpfiles.rules = [
         "d /opt/cni/bin 0755 root root -"
         "d /run/kubernetes 0755 kubernetes kubernetes -"
-        "d /var/lib/kubernetes 0755 kubernetes kubernetes -"
+        "d ${cfg.dataDir} 0755 kubernetes kubernetes -"
       ];
 
       users.users.kubernetes = {
@@ -294,6 +294,7 @@ in {
         group = "kubernetes";
         home = cfg.dataDir;
         createHome = true;
+        homeMode = "755";
       };
       users.groups.kubernetes.gid = config.ids.gids.kubernetes;
 
diff --git a/nixos/modules/services/cluster/kubernetes/kubelet.nix b/nixos/modules/services/cluster/kubernetes/kubelet.nix
index fd2dce7ee6a2..313dbe234018 100644
--- a/nixos/modules/services/cluster/kubernetes/kubelet.nix
+++ b/nixos/modules/services/cluster/kubernetes/kubelet.nix
@@ -33,6 +33,41 @@ let
 
   kubeconfig = top.lib.mkKubeConfig "kubelet" cfg.kubeconfig;
 
+  # Flag based settings are deprecated, use the `--config` flag with a
+  # `KubeletConfiguration` struct.
+  # https://kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file/
+  #
+  # NOTE: registerWithTaints requires a []core/v1.Taint, therefore requires
+  # additional work to be put in config format.
+  #
+  kubeletConfig = pkgs.writeText "kubelet-config" (builtins.toJSON ({
+    apiVersion = "kubelet.config.k8s.io/v1beta1";
+    kind = "KubeletConfiguration";
+    address = cfg.address;
+    port = cfg.port;
+    authentication = {
+      x509 = lib.optionalAttrs (cfg.clientCaFile != null) { clientCAFile = cfg.clientCaFile; };
+      webhook = {
+        enabled = true;
+        cacheTTL = "10s";
+      };
+    };
+    authorization = {
+      mode = "Webhook";
+    };
+    cgroupDriver = "systemd";
+    hairpinMode = "hairpin-veth";
+    registerNode = cfg.registerNode;
+    containerRuntimeEndpoint = cfg.containerRuntimeEndpoint;
+    healthzPort = cfg.healthz.port;
+    healthzBindAddress = cfg.healthz.bind;
+  } // lib.optionalAttrs (cfg.tlsCertFile != null)  { tlsCertFile = cfg.tlsCertFile; }
+    // lib.optionalAttrs (cfg.tlsKeyFile != null)   { tlsPrivateKeyFile = cfg.tlsKeyFile; }
+    // lib.optionalAttrs (cfg.clusterDomain != "")  { clusterDomain = cfg.clusterDomain; }
+    // lib.optionalAttrs (cfg.clusterDns != "")     { clusterDNS = [ cfg.clusterDns ] ; }
+    // lib.optionalAttrs (cfg.featureGates != [])   { featureGates = cfg.featureGates; }
+  ));
+
   manifestPath = "kubernetes/manifests";
 
   taintOptions = with lib.types; { name, ... }: {
@@ -294,21 +329,7 @@ in
           Restart = "on-failure";
           RestartSec = "1000ms";
           ExecStart = ''${top.package}/bin/kubelet \
-            --address=${cfg.address} \
-            --authentication-token-webhook \
-            --authentication-token-webhook-cache-ttl="10s" \
-            --authorization-mode=Webhook \
-            ${optionalString (cfg.clientCaFile != null)
-              "--client-ca-file=${cfg.clientCaFile}"} \
-            ${optionalString (cfg.clusterDns != "")
-              "--cluster-dns=${cfg.clusterDns}"} \
-            ${optionalString (cfg.clusterDomain != "")
-              "--cluster-domain=${cfg.clusterDomain}"} \
-            ${optionalString (cfg.featureGates != [])
-              "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.featureGates}"} \
-            --hairpin-mode=hairpin-veth \
-            --healthz-bind-address=${cfg.healthz.bind} \
-            --healthz-port=${toString cfg.healthz.port} \
+            --config=${kubeletConfig} \
             --hostname-override=${cfg.hostname} \
             --kubeconfig=${kubeconfig} \
             ${optionalString (cfg.nodeIp != null)
@@ -316,18 +337,10 @@ in
             --pod-infra-container-image=pause \
             ${optionalString (cfg.manifests != {})
               "--pod-manifest-path=/etc/${manifestPath}"} \
-            --port=${toString cfg.port} \
-            --register-node=${boolToString cfg.registerNode} \
             ${optionalString (taints != "")
               "--register-with-taints=${taints}"} \
             --root-dir=${top.dataDir} \
-            ${optionalString (cfg.tlsCertFile != null)
-              "--tls-cert-file=${cfg.tlsCertFile}"} \
-            ${optionalString (cfg.tlsKeyFile != null)
-              "--tls-private-key-file=${cfg.tlsKeyFile}"} \
             ${optionalString (cfg.verbosity != null) "--v=${toString cfg.verbosity}"} \
-            --container-runtime-endpoint=${cfg.containerRuntimeEndpoint} \
-            --cgroup-driver=systemd \
             ${cfg.extraOpts}
           '';
           WorkingDirectory = top.dataDir;
diff --git a/nixos/modules/services/continuous-integration/hydra/default.nix b/nixos/modules/services/continuous-integration/hydra/default.nix
index 54bbe69703f9..b1d44e67658b 100644
--- a/nixos/modules/services/continuous-integration/hydra/default.nix
+++ b/nixos/modules/services/continuous-integration/hydra/default.nix
@@ -39,7 +39,7 @@ let
 
   hydra-package =
   let
-    makeWrapperArgs = concatStringsSep " " (mapAttrsToList (key: value: "--set \"${key}\" \"${value}\"") hydraEnv);
+    makeWrapperArgs = concatStringsSep " " (mapAttrsToList (key: value: "--set-default \"${key}\" \"${value}\"") hydraEnv);
   in pkgs.buildEnv rec {
     name = "hydra-env";
     nativeBuildInputs = [ pkgs.makeWrapper ];
diff --git a/nixos/modules/services/databases/mysql.nix b/nixos/modules/services/databases/mysql.nix
index 128bb0862175..a6d71cca88de 100644
--- a/nixos/modules/services/databases/mysql.nix
+++ b/nixos/modules/services/databases/mysql.nix
@@ -7,6 +7,9 @@ let
   cfg = config.services.mysql;
 
   isMariaDB = lib.getName cfg.package == lib.getName pkgs.mariadb;
+  isOracle = lib.getName cfg.package == lib.getName pkgs.mysql80;
+  # Oracle MySQL has supported "notify" service type since 8.0
+  hasNotify = isMariaDB || (isOracle && versionAtLeast cfg.package.version "8.0");
 
   mysqldOptions =
     "--user=${cfg.user} --datadir=${cfg.dataDir} --basedir=${cfg.package}";
@@ -377,19 +380,11 @@ in
         # The super user account to use on *first* run of MySQL server
         superUser = if isMariaDB then cfg.user else "root";
       in ''
-        ${optionalString (!isMariaDB) ''
+        ${optionalString (!hasNotify) ''
           # Wait until the MySQL server is available for use
-          count=0
           while [ ! -e /run/mysqld/mysqld.sock ]
           do
-              if [ $count -eq 30 ]
-              then
-                  echo "Tried 30 times, giving up..."
-                  exit 1
-              fi
-
               echo "MySQL daemon not yet started. Waiting for 1 second..."
-              count=$((count++))
               sleep 1
           done
         ''}
@@ -477,7 +472,7 @@ in
 
       serviceConfig = mkMerge [
         {
-          Type = if isMariaDB then "notify" else "simple";
+          Type = if hasNotify then "notify" else "simple";
           Restart = "on-abort";
           RestartSec = "5s";
 
diff --git a/nixos/modules/services/databases/pgbouncer.nix b/nixos/modules/services/databases/pgbouncer.nix
index 65b287e84442..157d49c13161 100644
--- a/nixos/modules/services/databases/pgbouncer.nix
+++ b/nixos/modules/services/databases/pgbouncer.nix
@@ -66,9 +66,6 @@ let
       ${optionalString (cfg.adminUsers != null) "admin_users = ${cfg.adminUsers}"}
       ${optionalString (cfg.statsUsers != null) "stats_users = ${cfg.statsUsers}"}
 
-      # linux
-      pidfile = /run/pgbouncer/pgbouncer.pid
-
       # extra
       ${cfg.extraConfig}
     '';
@@ -96,10 +93,9 @@ in {
 
     logFile = mkOption {
       type = types.nullOr types.str;
-      default = "pgbouncer.log";
+      default = null;
       description = lib.mdDoc ''
-        Specifies the log file.
-        Either this or syslog has to be specified.
+        Specifies a log file in addition to journald.
       '';
     };
 
@@ -601,22 +597,21 @@ in {
 
     systemd.services.pgbouncer = {
       description = "PgBouncer - PostgreSQL connection pooler";
-      wants    = [ "postgresql.service" ];
-      after    = [ "postgresql.service" ];
+      wants    = [ "network-online.target" ] ++ lib.optional config.services.postgresql.enable "postgresql.service";
+      after    = [ "network-online.target" ] ++ lib.optional config.services.postgresql.enable "postgresql.service";
       wantedBy = [ "multi-user.target" ];
       serviceConfig = {
-        Type = "forking";
+        Type = "notify";
         User = cfg.user;
         Group = cfg.group;
-        ExecStart = "${pkgs.pgbouncer}/bin/pgbouncer -d ${confFile}";
+        ExecStart = "${lib.getExe pkgs.pgbouncer} ${confFile}";
         ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID";
         RuntimeDirectory = "pgbouncer";
-        PIDFile = "/run/pgbouncer/pgbouncer.pid";
         LimitNOFILE = cfg.openFilesLimit;
       };
     };
 
-    networking.firewall.allowedTCPPorts = optional cfg.openFirewall cfg.port;
+    networking.firewall.allowedTCPPorts = optional cfg.openFirewall cfg.listenPort;
 
   };
 
diff --git a/nixos/modules/services/desktops/pipewire/pipewire.nix b/nixos/modules/services/desktops/pipewire/pipewire.nix
index da409030b3a3..09448833620c 100644
--- a/nixos/modules/services/desktops/pipewire/pipewire.nix
+++ b/nixos/modules/services/desktops/pipewire/pipewire.nix
@@ -1,11 +1,15 @@
-# pipewire service.
+# PipeWire service.
 { config, lib, pkgs, ... }:
 
 with lib;
 
 let
   json = pkgs.formats.json {};
-  mapToFiles = location: config: concatMapAttrs (name: value: { "pipewire/${location}.conf.d/${name}.conf".source = json.generate "${name}" value;}) config;
+  mapToFiles = location: config: concatMapAttrs (name: value: { "share/pipewire/${location}.conf.d/${name}.conf" = json.generate "${name}" value; }) config;
+  extraConfigPkgFromFiles = locations: filesSet: pkgs.runCommand "pipewire-extra-config" { } ''
+    mkdir -p ${lib.concatMapStringsSep " " (l: "$out/share/pipewire/${l}.conf.d") locations}
+    ${lib.concatMapStringsSep ";" ({name, value}: "ln -s ${value} $out/${name}") (lib.attrsToList filesSet)}
+  '';
   cfg = config.services.pipewire;
   enable32BitAlsaPlugins = cfg.alsa.support32Bit
                            && pkgs.stdenv.isx86_64
@@ -19,13 +23,48 @@ let
     mkdir -p "$out/lib"
     ln -s "${cfg.package.jack}/lib" "$out/lib/pipewire"
   '';
+
+  configPackages = cfg.configPackages;
+
+  extraConfigPkg = extraConfigPkgFromFiles
+    [ "pipewire" "client" "client-rt" "jack" "pipewire-pulse" ]
+    (
+      mapToFiles "pipewire" cfg.extraConfig.pipewire
+      // mapToFiles "client" cfg.extraConfig.client
+      // mapToFiles "client-rt" cfg.extraConfig.client-rt
+      // mapToFiles "jack" cfg.extraConfig.jack
+      // mapToFiles "pipewire-pulse" cfg.extraConfig.pipewire-pulse
+    );
+
+  configs = pkgs.buildEnv {
+    name = "pipewire-configs";
+    paths = configPackages
+      ++ [ extraConfigPkg ]
+      ++ lib.optionals cfg.wireplumber.enable cfg.wireplumber.configPackages;
+    pathsToLink = [ "/share/pipewire" ];
+  };
+
+  requiredLv2Packages = lib.flatten
+    (
+      lib.concatMap
+      (p:
+        lib.attrByPath ["passthru" "requiredLv2Packages"] [] p
+      )
+      configPackages
+    );
+
+  lv2Plugins = pkgs.buildEnv {
+    name = "pipewire-lv2-plugins";
+    paths = cfg.extraLv2Packages ++ requiredLv2Packages;
+    pathsToLink = [ "/lib/lv2" ];
+  };
 in {
   meta.maintainers = teams.freedesktop.members ++ [ lib.maintainers.k900 ];
 
   ###### interface
   options = {
     services.pipewire = {
-      enable = mkEnableOption (lib.mdDoc "pipewire service");
+      enable = mkEnableOption (lib.mdDoc "PipeWire service");
 
       package = mkPackageOption pkgs "pipewire" { };
 
@@ -33,7 +72,7 @@ in {
         default = true;
         type = types.bool;
         description = lib.mdDoc ''
-          Automatically run pipewire when connections are made to the pipewire socket.
+          Automatically run PipeWire when connections are made to the PipeWire socket.
         '';
       };
 
@@ -200,6 +239,34 @@ in {
           '';
         };
       };
+
+      configPackages = lib.mkOption {
+        type = lib.types.listOf lib.types.package;
+        default = [];
+        description = lib.mdDoc ''
+          List of packages that provide PipeWire configuration, in the form of
+          `share/pipewire/*/*.conf` files.
+
+          LV2 dependencies will be picked up from config packages automatically
+          via `passthru.requiredLv2Packages`.
+        '';
+      };
+
+      extraLv2Packages = lib.mkOption {
+        type = lib.types.listOf lib.types.package;
+        default = [];
+        example = lib.literalExpression "[ pkgs.lsp-plugins ]";
+        description = lib.mdDoc ''
+          List of packages that provide LV2 plugins in `lib/lv2` that should
+          be made available to PipeWire for [filter chains][wiki-filter-chain].
+
+          Config packages have their required LV2 plugins added automatically,
+          so they don't need to be specified here. Config packages need to set
+          `passthru.requiredLv2Packages` for this to work.
+
+          [wiki-filter-chain]: https://docs.pipewire.org/page_module_filter_chain.html
+        '';
+      };
     };
   };
 
@@ -230,6 +297,18 @@ in {
         assertion = (cfg.alsa.enable || cfg.pulse.enable) -> cfg.audio.enable;
         message = "Using PipeWire's ALSA/PulseAudio compatibility layers requires running PipeWire as the sound server. Set `services.pipewire.audio.enable` to true.";
       }
+      {
+        assertion = builtins.length
+          (builtins.attrNames
+            (
+              lib.filterAttrs
+                (name: value:
+                  lib.hasPrefix "pipewire/" name || name == "pipewire"
+                )
+                config.environment.etc
+            )) == 1;
+        message = "Using `environment.etc.\"pipewire<...>\"` directly is no longer supported in 24.05. Use `services.pipewire.extraConfig` or `services.pipewire.configPackages` instead.";
+      }
     ];
 
     environment.systemPackages = [ cfg.package ]
@@ -249,6 +328,9 @@ in {
     systemd.user.sockets.pipewire.enable = !cfg.systemWide;
     systemd.user.services.pipewire.enable = !cfg.systemWide;
 
+    systemd.services.pipewire.environment.LV2_PATH = lib.mkIf cfg.systemWide "${lv2Plugins}/lib/lv2";
+    systemd.user.services.pipewire.environment.LV2_PATH = lib.mkIf (!cfg.systemWide) "${lv2Plugins}/lib/lv2";
+
     # Mask pw-pulse if it's not wanted
     systemd.user.services.pipewire-pulse.enable = cfg.pulse.enable;
     systemd.user.sockets.pipewire-pulse.enable = cfg.pulse.enable;
@@ -283,12 +365,8 @@ in {
       "alsa/conf.d/99-pipewire-default.conf" = mkIf cfg.alsa.enable {
         source = "${cfg.package}/share/alsa/alsa.conf.d/99-pipewire-default.conf";
       };
-    }
-    // mapToFiles "pipewire" cfg.extraConfig.pipewire
-    // mapToFiles "client" cfg.extraConfig.client
-    // mapToFiles "client-rt" cfg.extraConfig.client-rt
-    // mapToFiles "jack" cfg.extraConfig.jack
-    // mapToFiles "pipewire-pulse" cfg.extraConfig.pipewire-pulse;
+      pipewire.source = "${configs}/share/pipewire";
+    };
 
     environment.sessionVariables.LD_LIBRARY_PATH =
       lib.mkIf cfg.jack.enable [ "${cfg.package.jack}/lib" ];
@@ -301,7 +379,7 @@ in {
           "audio"
           "video"
         ] ++ lib.optional config.security.rtkit.enable "rtkit";
-        description = "Pipewire system service user";
+        description = "PipeWire system service user";
         isSystemUser = true;
         home = "/var/lib/pipewire";
         createHome = true;
diff --git a/nixos/modules/services/desktops/pipewire/wireplumber.nix b/nixos/modules/services/desktops/pipewire/wireplumber.nix
index 95a7ece26c5d..009d68bd4f28 100644
--- a/nixos/modules/services/desktops/pipewire/wireplumber.nix
+++ b/nixos/modules/services/desktops/pipewire/wireplumber.nix
@@ -14,60 +14,127 @@ in
         type = lib.types.bool;
         default = config.services.pipewire.enable;
         defaultText = lib.literalExpression "config.services.pipewire.enable";
-        description = lib.mdDoc "Whether to enable Wireplumber, a modular session / policy manager for PipeWire";
+        description = lib.mdDoc "Whether to enable WirePlumber, a modular session / policy manager for PipeWire";
       };
 
       package = lib.mkOption {
         type = lib.types.package;
         default = pkgs.wireplumber;
         defaultText = lib.literalExpression "pkgs.wireplumber";
-        description = lib.mdDoc "The wireplumber derivation to use.";
+        description = lib.mdDoc "The WirePlumber derivation to use.";
       };
-    };
-  };
 
-  config = lib.mkIf cfg.enable {
-    assertions = [
-      {
-        assertion = !config.hardware.bluetooth.hsphfpd.enable;
-        message = "Using Wireplumber conflicts with hsphfpd, as it provides the same functionality. `hardware.bluetooth.hsphfpd.enable` needs be set to false";
-      }
-    ];
+      configPackages = lib.mkOption {
+        type = lib.types.listOf lib.types.package;
+        default = [ ];
+        description = lib.mdDoc ''
+          List of packages that provide WirePlumber configuration, in the form of
+          `share/wireplumber/*/*.lua` files.
+
+          LV2 dependencies will be picked up from config packages automatically
+          via `passthru.requiredLv2Packages`.
+        '';
+      };
+
+      extraLv2Packages = lib.mkOption {
+        type = lib.types.listOf lib.types.package;
+        default = [];
+        example = lib.literalExpression "[ pkgs.lsp-plugins ]";
+        description = lib.mdDoc ''
+          List of packages that provide LV2 plugins in `lib/lv2` that should
+          be made available to WirePlumber for [filter chains][wiki-filter-chain].
+
+          Config packages have their required LV2 plugins added automatically,
+          so they don't need to be specified here. Config packages need to set
+          `passthru.requiredLv2Packages` for this to work.
 
-    environment.systemPackages = [ cfg.package ];
+          [wiki-filter-chain]: https://docs.pipewire.org/page_module_filter_chain.html
+        '';
+      };
+    };
+  };
 
-    environment.etc."wireplumber/main.lua.d/80-nixos.lua" = lib.mkIf (!pwUsedForAudio) {
-      text = ''
-        -- Pipewire is not used for audio, so prevent it from grabbing audio devices
+  config =
+    let
+      pwNotForAudioConfigPkg = pkgs.writeTextDir "share/wireplumber/main.lua.d/80-pw-not-for-audio.lua" ''
+        -- PipeWire is not used for audio, so prevent it from grabbing audio devices
         alsa_monitor.enable = function() end
       '';
-    };
-    environment.etc."wireplumber/main.lua.d/80-systemwide.lua" = lib.mkIf config.services.pipewire.systemWide {
-      text = ''
+      systemwideConfigPkg = pkgs.writeTextDir "share/wireplumber/main.lua.d/80-systemwide.lua" ''
         -- When running system-wide, these settings need to be disabled (they
         -- use functions that aren't available on the system dbus).
         alsa_monitor.properties["alsa.reserve"] = false
         default_access.properties["enable-flatpak-portal"] = false
       '';
-    };
-    environment.etc."wireplumber/bluetooth.lua.d/80-systemwide.lua" = lib.mkIf config.services.pipewire.systemWide {
-      text = ''
+      systemwideBluetoothConfigPkg = pkgs.writeTextDir "share/wireplumber/bluetooth.lua.d/80-systemwide.lua" ''
         -- When running system-wide, logind-integration needs to be disabled.
         bluez_monitor.properties["with-logind"] = false
       '';
-    };
 
-    systemd.packages = [ cfg.package ];
+      configPackages = cfg.configPackages
+          ++ lib.optional (!pwUsedForAudio) pwNotForAudioConfigPkg
+          ++ lib.optionals config.services.pipewire.systemWide [ systemwideConfigPkg systemwideBluetoothConfigPkg ];
 
-    systemd.services.wireplumber.enable = config.services.pipewire.systemWide;
-    systemd.user.services.wireplumber.enable = !config.services.pipewire.systemWide;
+      configs = pkgs.buildEnv {
+        name = "wireplumber-configs";
+        paths = configPackages;
+        pathsToLink = [ "/share/wireplumber" ];
+      };
+
+      requiredLv2Packages = lib.flatten
+        (
+          lib.concatMap
+            (p:
+              lib.attrByPath ["passthru" "requiredLv2Packages"] [] p
+            )
+            configPackages
+        );
+
+      lv2Plugins = pkgs.buildEnv {
+        name = "wireplumber-lv2-plugins";
+        paths = cfg.extraLv2Packages ++ requiredLv2Packages;
+        pathsToLink = [ "/lib/lv2" ];
+      };
+    in
+    lib.mkIf cfg.enable {
+      assertions = [
+        {
+          assertion = !config.hardware.bluetooth.hsphfpd.enable;
+          message = "Using WirePlumber conflicts with hsphfpd, as it provides the same functionality. `hardware.bluetooth.hsphfpd.enable` needs be set to false";
+        }
+        {
+          assertion = builtins.length
+            (builtins.attrNames
+              (
+                lib.filterAttrs
+                  (name: value:
+                    lib.hasPrefix "wireplumber/" name || name == "wireplumber"
+                  )
+                  config.environment.etc
+              )) == 1;
+          message = "Using `environment.etc.\"wireplumber<...>\"` directly is no longer supported in 24.05. Use `services.pipewire.wireplumber.configPackages` instead.";
+        }
+      ];
+
+      environment.systemPackages = [ cfg.package ];
+
+      environment.etc.wireplumber.source = "${configs}/share/wireplumber";
 
-    systemd.services.wireplumber.wantedBy = [ "pipewire.service" ];
-    systemd.user.services.wireplumber.wantedBy = [ "pipewire.service" ];
+      systemd.packages = [ cfg.package ];
 
-    systemd.services.wireplumber.environment = lib.mkIf config.services.pipewire.systemWide {
-      # Force wireplumber to use system dbus.
-      DBUS_SESSION_BUS_ADDRESS = "unix:path=/run/dbus/system_bus_socket";
+      systemd.services.wireplumber.enable = config.services.pipewire.systemWide;
+      systemd.user.services.wireplumber.enable = !config.services.pipewire.systemWide;
+
+      systemd.services.wireplumber.wantedBy = [ "pipewire.service" ];
+      systemd.user.services.wireplumber.wantedBy = [ "pipewire.service" ];
+
+      systemd.services.wireplumber.environment = lib.mkIf config.services.pipewire.systemWide {
+        # Force WirePlumber to use system dbus.
+        DBUS_SESSION_BUS_ADDRESS = "unix:path=/run/dbus/system_bus_socket";
+        LV2_PATH = "${lv2Plugins}/lib/lv2";
+      };
+
+      systemd.user.services.wireplumber.environment.LV2_PATH =
+        lib.mkIf (!config.services.pipewire.systemWide) "${lv2Plugins}/lib/lv2";
     };
-  };
 }
diff --git a/nixos/modules/services/development/lorri.nix b/nixos/modules/services/development/lorri.nix
index 74f56f5890fc..df3d814d7444 100644
--- a/nixos/modules/services/development/lorri.nix
+++ b/nixos/modules/services/development/lorri.nix
@@ -44,8 +44,7 @@ in {
       serviceConfig = {
         ExecStart = "${cfg.package}/bin/lorri daemon";
         PrivateTmp = true;
-        ProtectSystem = "strict";
-        ProtectHome = "read-only";
+        ProtectSystem = "full";
         Restart = "on-failure";
       };
     };
diff --git a/nixos/modules/services/development/nixseparatedebuginfod.nix b/nixos/modules/services/development/nixseparatedebuginfod.nix
index daf85153d339..a2ec0d2c80e1 100644
--- a/nixos/modules/services/development/nixseparatedebuginfod.nix
+++ b/nixos/modules/services/development/nixseparatedebuginfod.nix
@@ -90,7 +90,9 @@ in
 
     users.groups.nixseparatedebuginfod = { };
 
-    nix.settings.extra-allowed-users = [ "nixseparatedebuginfod" ];
+    nix.settings = lib.optionalAttrs (lib.versionAtLeast config.nix.package.version "2.4") {
+      extra-allowed-users = [ "nixseparatedebuginfod" ];
+    };
 
     environment.variables.DEBUGINFOD_URLS = "http://${url}";
 
diff --git a/nixos/modules/services/display-managers/greetd.nix b/nixos/modules/services/display-managers/greetd.nix
index 2212f97a9ffe..c2d345152de9 100644
--- a/nixos/modules/services/display-managers/greetd.nix
+++ b/nixos/modules/services/display-managers/greetd.nix
@@ -78,7 +78,7 @@ in
       serviceConfig = {
         ExecStart = "${pkgs.greetd.greetd}/bin/greetd --config ${settingsFormat.generate "greetd.toml" cfg.settings}";
 
-        Restart = mkIf cfg.restart "always";
+        Restart = mkIf cfg.restart "on-success";
 
         # Defaults from greetd upstream configuration
         IgnoreSIGPIPE = false;
diff --git a/nixos/modules/services/games/archisteamfarm.nix b/nixos/modules/services/games/archisteamfarm.nix
index c00ae8116b39..4bb7234f430f 100644
--- a/nixos/modules/services/games/archisteamfarm.nix
+++ b/nixos/modules/services/games/archisteamfarm.nix
@@ -270,6 +270,6 @@ in
 
   meta = {
     buildDocsInSandbox = false;
-    maintainers = with lib.maintainers; [ lom SuperSandro2000 ];
+    maintainers = with lib.maintainers; [ SuperSandro2000 ];
   };
 }
diff --git a/nixos/modules/services/games/armagetronad.nix b/nixos/modules/services/games/armagetronad.nix
new file mode 100644
index 000000000000..f79818e0e53b
--- /dev/null
+++ b/nixos/modules/services/games/armagetronad.nix
@@ -0,0 +1,268 @@
+{ config, lib, pkgs, ... }:
+let
+  inherit (lib) mkEnableOption mkIf mkOption mkMerge literalExpression;
+  inherit (lib) mapAttrsToList filterAttrs unique recursiveUpdate types;
+
+  mkValueStringArmagetron = with lib; v:
+    if isInt v then toString v
+    else if isFloat v then toString v
+    else if isString v then v
+    else if true == v then "1"
+    else if false == v then "0"
+    else if null == v then ""
+    else throw "unsupported type: ${builtins.typeOf v}: ${(lib.generators.toPretty {} v)}";
+
+  settingsFormat = pkgs.formats.keyValue {
+    mkKeyValue = lib.generators.mkKeyValueDefault
+      {
+        mkValueString = mkValueStringArmagetron;
+      } " ";
+    listsAsDuplicateKeys = true;
+  };
+
+  cfg = config.services.armagetronad;
+  enabledServers = lib.filterAttrs (n: v: v.enable) cfg.servers;
+  nameToId = serverName: "armagetronad-${serverName}";
+  getStateDirectory = serverName: "armagetronad/${serverName}";
+  getServerRoot = serverName: "/var/lib/${getStateDirectory serverName}";
+in
+{
+  options = {
+    services.armagetronad = {
+      servers = mkOption {
+        description = lib.mdDoc "Armagetron server definitions.";
+        default = { };
+        type = types.attrsOf (types.submodule {
+          options = {
+            enable = mkEnableOption (lib.mdDoc "armagetronad");
+
+            package = lib.mkPackageOptionMD pkgs "armagetronad-dedicated" {
+              example = ''
+                pkgs.armagetronad."0.2.9-sty+ct+ap".dedicated
+              '';
+              extraDescription = ''
+                Ensure that you use a derivation which contains the path `bin/armagetronad-dedicated`.
+              '';
+            };
+
+            host = mkOption {
+              type = types.str;
+              default = "0.0.0.0";
+              description = lib.mdDoc "Host to listen on. Used for SERVER_IP.";
+            };
+
+            port = mkOption {
+              type = types.port;
+              default = 4534;
+              description = lib.mdDoc "Port to listen on. Used for SERVER_PORT.";
+            };
+
+            dns = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              description = lib.mdDoc "DNS address to use for this server. Optional.";
+            };
+
+            openFirewall = mkOption {
+              type = types.bool;
+              default = true;
+              description = lib.mdDoc "Set to true to open the configured UDP port for Armagetron Advanced.";
+            };
+
+            name = mkOption {
+              type = types.str;
+              description = "The name of this server.";
+            };
+
+            settings = mkOption {
+              type = settingsFormat.type;
+              default = { };
+              description = lib.mdDoc ''
+                Armagetron Advanced server rules configuration. Refer to:
+                <https://wiki.armagetronad.org/index.php?title=Console_Commands>
+                or `armagetronad-dedicated --doc` for a list.
+
+                This attrset is used to populate `settings_custom.cfg`; see:
+                <https://wiki.armagetronad.org/index.php/Configuration_Files>
+              '';
+              example = literalExpression ''
+                {
+                  CYCLE_RUBBER = 40;
+                }
+              '';
+            };
+
+            roundSettings = mkOption {
+              type = settingsFormat.type;
+              default = { };
+              description = lib.mdDoc ''
+                Armagetron Advanced server per-round configuration. Refer to:
+                <https://wiki.armagetronad.org/index.php?title=Console_Commands>
+                or `armagetronad-dedicated --doc` for a list.
+
+                This attrset is used to populate `everytime.cfg`; see:
+                <https://wiki.armagetronad.org/index.php/Configuration_Files>
+              '';
+              example = literalExpression ''
+                {
+                  SAY = [
+                    "Hosted on NixOS"
+                    "https://nixos.org"
+                    "iD Tech High Rubber rul3z!! Happy New Year 2008!!1"
+                  ];
+                }
+              '';
+            };
+          };
+        });
+      };
+    };
+  };
+
+  config = mkIf (enabledServers != { }) {
+    systemd.tmpfiles.settings = mkMerge (mapAttrsToList
+      (serverName: serverCfg:
+        let
+          serverId = nameToId serverName;
+          serverRoot = getServerRoot serverName;
+          serverInfo = (
+            {
+              SERVER_IP = serverCfg.host;
+              SERVER_PORT = serverCfg.port;
+              SERVER_NAME = serverCfg.name;
+            } // (lib.optionalAttrs (serverCfg.dns != null) { SERVER_DNS = serverCfg.dns; })
+          );
+          customSettings = serverCfg.settings;
+          everytimeSettings = serverCfg.roundSettings;
+
+          serverInfoCfg = settingsFormat.generate "server_info.${serverName}.cfg" serverInfo;
+          customSettingsCfg = settingsFormat.generate "settings_custom.${serverName}.cfg" customSettings;
+          everytimeSettingsCfg = settingsFormat.generate "everytime.${serverName}.cfg" everytimeSettings;
+        in
+        {
+          "10-armagetronad-${serverId}" = {
+            "${serverRoot}/data" = {
+              d = {
+                group = serverId;
+                user = serverId;
+                mode = "0750";
+              };
+            };
+            "${serverRoot}/settings" = {
+              d = {
+                group = serverId;
+                user = serverId;
+                mode = "0750";
+              };
+            };
+            "${serverRoot}/var" = {
+              d = {
+                group = serverId;
+                user = serverId;
+                mode = "0750";
+              };
+            };
+            "${serverRoot}/resource" = {
+              d = {
+                group = serverId;
+                user = serverId;
+                mode = "0750";
+              };
+            };
+            "${serverRoot}/input" = {
+              "f+" = {
+                group = serverId;
+                user = serverId;
+                mode = "0640";
+              };
+            };
+            "${serverRoot}/settings/server_info.cfg" = {
+              "L+" = {
+                argument = "${serverInfoCfg}";
+              };
+            };
+            "${serverRoot}/settings/settings_custom.cfg" = {
+              "L+" = {
+                argument = "${customSettingsCfg}";
+              };
+            };
+            "${serverRoot}/settings/everytime.cfg" = {
+              "L+" = {
+                argument = "${everytimeSettingsCfg}";
+              };
+            };
+          };
+        }
+      )
+      enabledServers
+    );
+
+    systemd.services = mkMerge (mapAttrsToList
+      (serverName: serverCfg:
+        let
+          serverId = nameToId serverName;
+        in
+        {
+          "armagetronad-${serverName}" = {
+            description = "Armagetron Advanced Dedicated Server for ${serverName}";
+            wants = [ "basic.target" ];
+            after = [ "basic.target" "network.target" "multi-user.target" ];
+            wantedBy = [ "multi-user.target" ];
+            serviceConfig =
+              let
+                serverRoot = getServerRoot serverName;
+              in
+              {
+                Type = "simple";
+                StateDirectory = getStateDirectory serverName;
+                ExecStart = "${lib.getExe serverCfg.package} --daemon --input ${serverRoot}/input --userdatadir ${serverRoot}/data --userconfigdir ${serverRoot}/settings --vardir ${serverRoot}/var --autoresourcedir ${serverRoot}/resource";
+                Restart = "on-failure";
+                CapabilityBoundingSet = "";
+                LockPersonality = true;
+                NoNewPrivileges = true;
+                PrivateDevices = true;
+                PrivateTmp = true;
+                PrivateUsers = true;
+                ProtectClock = true;
+                ProtectControlGroups = true;
+                ProtectHome = true;
+                ProtectHostname = true;
+                ProtectKernelLogs = true;
+                ProtectKernelModules = true;
+                ProtectKernelTunables = true;
+                ProtectProc = "invisible";
+                ProtectSystem = "strict";
+                RestrictNamespaces = true;
+                RestrictSUIDSGID = true;
+                User = serverId;
+                Group = serverId;
+              };
+          };
+        })
+      enabledServers
+    );
+
+    networking.firewall.allowedUDPPorts =
+      unique (mapAttrsToList (serverName: serverCfg: serverCfg.port) (filterAttrs (serverName: serverCfg: serverCfg.openFirewall) enabledServers));
+
+    users.users = mkMerge (mapAttrsToList
+      (serverName: serverCfg:
+        {
+          ${nameToId serverName} = {
+            group = nameToId serverName;
+            description = "Armagetron Advanced dedicated user for server ${serverName}";
+            isSystemUser = true;
+          };
+        })
+      enabledServers
+    );
+
+    users.groups = mkMerge (mapAttrsToList
+      (serverName: serverCfg:
+        {
+          ${nameToId serverName} = { };
+        })
+      enabledServers
+    );
+  };
+}
diff --git a/nixos/modules/services/games/teeworlds.nix b/nixos/modules/services/games/teeworlds.nix
index bd0df1ffca57..04b611fb3cb1 100644
--- a/nixos/modules/services/games/teeworlds.nix
+++ b/nixos/modules/services/games/teeworlds.nix
@@ -6,13 +6,86 @@ let
   cfg = config.services.teeworlds;
   register = cfg.register;
 
+  bool = b: if b != null && b then "1" else "0";
+  optionalSetting = s: setting: optionalString (s != null) "${setting} ${s}";
+  lookup = attrs: key: default: if attrs ? key then attrs."${key}" else default;
+
+  inactivePenaltyOptions = {
+    "spectator" = "1";
+    "spectator/kick" = "2";
+    "kick" = "3";
+  };
+  skillLevelOptions = {
+    "casual" = "0";
+    "normal" = "1";
+    "competitive" = "2";
+  };
+  tournamentModeOptions = {
+    "disable" = "0";
+    "enable"  = "1";
+    "restrictSpectators" = "2";
+  };
+
   teeworldsConf = pkgs.writeText "teeworlds.cfg" ''
     sv_port ${toString cfg.port}
-    sv_register ${if cfg.register then "1" else "0"}
-    ${optionalString (cfg.name != null) "sv_name ${cfg.name}"}
-    ${optionalString (cfg.motd != null) "sv_motd ${cfg.motd}"}
-    ${optionalString (cfg.password != null) "password ${cfg.password}"}
-    ${optionalString (cfg.rconPassword != null) "sv_rcon_password ${cfg.rconPassword}"}
+    sv_register ${bool cfg.register}
+    sv_name ${cfg.name}
+    ${optionalSetting cfg.motd "sv_motd"}
+    ${optionalSetting cfg.password "password"}
+    ${optionalSetting cfg.rconPassword "sv_rcon_password"}
+
+    ${optionalSetting cfg.server.bindAddr "bindaddr"}
+    ${optionalSetting cfg.server.hostName "sv_hostname"}
+    sv_high_bandwidth ${bool cfg.server.enableHighBandwidth}
+    sv_inactivekick ${lookup inactivePenaltyOptions cfg.server.inactivePenalty "spectator/kick"}
+    sv_inactivekick_spec ${bool cfg.server.kickInactiveSpectators}
+    sv_inactivekick_time ${toString cfg.server.inactiveTime}
+    sv_max_clients ${toString cfg.server.maxClients}
+    sv_max_clients_per_ip ${toString cfg.server.maxClientsPerIP}
+    sv_skill_level ${lookup skillLevelOptions cfg.server.skillLevel "normal"}
+    sv_spamprotection ${bool cfg.server.enableSpamProtection}
+
+    sv_gametype ${cfg.game.gameType}
+    sv_map ${cfg.game.map}
+    sv_match_swap ${bool cfg.game.swapTeams}
+    sv_player_ready_mode ${bool cfg.game.enableReadyMode}
+    sv_player_slots ${toString cfg.game.playerSlots}
+    sv_powerups ${bool cfg.game.enablePowerups}
+    sv_scorelimit ${toString cfg.game.scoreLimit}
+    sv_strict_spectate_mode ${bool cfg.game.restrictSpectators}
+    sv_teamdamage ${bool cfg.game.enableTeamDamage}
+    sv_timelimit ${toString cfg.game.timeLimit}
+    sv_tournament_mode ${lookup tournamentModeOptions cfg.server.tournamentMode "disable"}
+    sv_vote_kick ${bool cfg.game.enableVoteKick}
+    sv_vote_kick_bantime ${toString cfg.game.voteKickBanTime}
+    sv_vote_kick_min ${toString cfg.game.voteKickMinimumPlayers}
+
+    ${optionalSetting cfg.server.bindAddr "bindaddr"}
+    ${optionalSetting cfg.server.hostName "sv_hostname"}
+    sv_high_bandwidth ${bool cfg.server.enableHighBandwidth}
+    sv_inactivekick ${lookup inactivePenaltyOptions cfg.server.inactivePenalty "spectator/kick"}
+    sv_inactivekick_spec ${bool cfg.server.kickInactiveSpectators}
+    sv_inactivekick_time ${toString cfg.server.inactiveTime}
+    sv_max_clients ${toString cfg.server.maxClients}
+    sv_max_clients_per_ip ${toString cfg.server.maxClientsPerIP}
+    sv_skill_level ${lookup skillLevelOptions cfg.server.skillLevel "normal"}
+    sv_spamprotection ${bool cfg.server.enableSpamProtection}
+
+    sv_gametype ${cfg.game.gameType}
+    sv_map ${cfg.game.map}
+    sv_match_swap ${bool cfg.game.swapTeams}
+    sv_player_ready_mode ${bool cfg.game.enableReadyMode}
+    sv_player_slots ${toString cfg.game.playerSlots}
+    sv_powerups ${bool cfg.game.enablePowerups}
+    sv_scorelimit ${toString cfg.game.scoreLimit}
+    sv_strict_spectate_mode ${bool cfg.game.restrictSpectators}
+    sv_teamdamage ${bool cfg.game.enableTeamDamage}
+    sv_timelimit ${toString cfg.game.timeLimit}
+    sv_tournament_mode ${lookup tournamentModeOptions cfg.server.tournamentMode "disable"}
+    sv_vote_kick ${bool cfg.game.enableVoteKick}
+    sv_vote_kick_bantime ${toString cfg.game.voteKickBanTime}
+    sv_vote_kick_min ${toString cfg.game.voteKickMinimumPlayers}
+
     ${concatStringsSep "\n" cfg.extraOptions}
   '';
 
@@ -22,17 +95,19 @@ in
     services.teeworlds = {
       enable = mkEnableOption (lib.mdDoc "Teeworlds Server");
 
+      package = mkPackageOptionMD pkgs "teeworlds-server" { };
+
       openPorts = mkOption {
         type = types.bool;
         default = false;
-        description = lib.mdDoc "Whether to open firewall ports for Teeworlds";
+        description = lib.mdDoc "Whether to open firewall ports for Teeworlds.";
       };
 
       name = mkOption {
-        type = types.nullOr types.str;
-        default = null;
+        type = types.str;
+        default = "unnamed server";
         description = lib.mdDoc ''
-          Name of the server. Defaults to 'unnamed server'.
+          Name of the server.
         '';
       };
 
@@ -41,7 +116,7 @@ in
         example = true;
         default = false;
         description = lib.mdDoc ''
-          Whether the server registers as public server in the global server list. This is disabled by default because of privacy.
+          Whether the server registers as a public server in the global server list. This is disabled by default for privacy reasons.
         '';
       };
 
@@ -49,7 +124,7 @@ in
         type = types.nullOr types.str;
         default = null;
         description = lib.mdDoc ''
-          Set the server message of the day text.
+          The server's message of the day text.
         '';
       };
 
@@ -85,6 +160,217 @@ in
         '';
         example = [ "sv_map dm1" "sv_gametype dm" ];
       };
+
+      server = {
+        bindAddr = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = lib.mdDoc ''
+            The address the server will bind to.
+          '';
+        };
+
+        enableHighBandwidth = mkOption {
+          type = types.bool;
+          default = false;
+          description = lib.mdDoc ''
+            Whether to enable high bandwidth mode on LAN servers. This will double the amount of bandwidth required for running the server.
+          '';
+        };
+
+        hostName = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = lib.mdDoc ''
+            Hostname for the server.
+          '';
+        };
+
+        inactivePenalty = mkOption {
+          type = types.enum [ "spectator" "spectator/kick" "kick" ];
+          example = "spectator";
+          default = "spectator/kick";
+          description = lib.mdDoc ''
+            Specify what to do when a client goes inactive (see [](#opt-services.teeworlds.server.inactiveTime)).
+
+            - `spectator`: send the client into spectator mode
+
+            - `spectator/kick`: send the client into a free spectator slot, otherwise kick the client
+
+            - `kick`: kick the client
+          '';
+        };
+
+        kickInactiveSpectators = mkOption {
+          type = types.bool;
+          default = false;
+          description = lib.mdDoc ''
+            Whether to kick inactive spectators.
+          '';
+        };
+
+        inactiveTime = mkOption {
+          type = types.ints.unsigned;
+          default = 3;
+          description = lib.mdDoc ''
+            The amount of minutes a client has to idle before it is considered inactive.
+          '';
+        };
+
+        maxClients = mkOption {
+          type = types.ints.unsigned;
+          default = 12;
+          description = lib.mdDoc ''
+            The maximum amount of clients that can be connected to the server at the same time.
+          '';
+        };
+
+        maxClientsPerIP = mkOption {
+          type = types.ints.unsigned;
+          default = 12;
+          description = lib.mdDoc ''
+            The maximum amount of clients with the same IP address that can be connected to the server at the same time.
+          '';
+        };
+
+        skillLevel = mkOption {
+          type = types.enum [ "casual" "normal" "competitive" ];
+          default = "normal";
+          description = lib.mdDoc ''
+            The skill level shown in the server browser.
+          '';
+        };
+
+        enableSpamProtection = mkOption {
+          type = types.bool;
+          default = true;
+          description = lib.mdDoc ''
+            Whether to enable chat spam protection.
+          '';
+        };
+      };
+
+      game = {
+        gameType = mkOption {
+          type = types.str;
+          example = "ctf";
+          default = "dm";
+          description = lib.mdDoc ''
+            The game type to use on the server.
+
+            The default gametypes are `dm`, `tdm`, `ctf`, `lms`, and `lts`.
+          '';
+        };
+
+        map = mkOption {
+          type = types.str;
+          example = "ctf5";
+          default = "dm1";
+          description = lib.mdDoc ''
+            The map to use on the server.
+          '';
+        };
+
+        swapTeams = mkOption {
+          type = types.bool;
+          default = true;
+          description = lib.mdDoc ''
+            Whether to swap teams each round.
+          '';
+        };
+
+        enableReadyMode = mkOption {
+          type = types.bool;
+          default = false;
+          description = lib.mdDoc ''
+            Whether to enable "ready mode"; where players can pause/unpause the game
+            and start the game in warmup, using their ready state.
+          '';
+        };
+
+        playerSlots = mkOption {
+          type = types.ints.unsigned;
+          default = 8;
+          description = lib.mdDoc ''
+            The amount of slots to reserve for players (as opposed to spectators).
+          '';
+        };
+
+        enablePowerups = mkOption {
+          type = types.bool;
+          default = true;
+          description = lib.mdDoc ''
+            Whether to allow powerups such as the ninja.
+          '';
+        };
+
+        scoreLimit = mkOption {
+          type = types.ints.unsigned;
+          example = 400;
+          default = 20;
+          description = lib.mdDoc ''
+            The score limit needed to win a round.
+          '';
+        };
+
+        restrictSpectators = mkOption {
+          type = types.bool;
+          default = false;
+          description = lib.mdDoc ''
+            Whether to restrict access to information such as health, ammo and armour in spectator mode.
+          '';
+        };
+
+        enableTeamDamage = mkOption {
+          type = types.bool;
+          default = false;
+          description = lib.mdDoc ''
+            Whether to enable team damage; whether to allow team mates to inflict damage on one another.
+          '';
+        };
+
+        timeLimit = mkOption {
+          type = types.ints.unsigned;
+          default = 0;
+          description = lib.mdDoc ''
+            Time limit of the game. In cases of equal points, there will be sudden death.
+            Setting this to 0 disables a time limit.
+          '';
+        };
+
+        tournamentMode = mkOption {
+          type = types.enum [ "disable" "enable" "restrictSpectators" ];
+          default = "disable";
+          description = lib.mdDoc ''
+            Whether to enable tournament mode. In tournament mode, players join as spectators.
+            If this is set to `restrictSpectators`, tournament mode is enabled but spectator chat is restricted.
+          '';
+        };
+
+        enableVoteKick = mkOption {
+          type = types.bool;
+          default = true;
+          description = lib.mdDoc ''
+            Whether to enable voting to kick players.
+          '';
+        };
+
+        voteKickBanTime = mkOption {
+          type = types.ints.unsigned;
+          default = 5;
+          description = lib.mdDoc ''
+            The amount of minutes that a player is banned for if they get kicked by a vote.
+          '';
+        };
+
+        voteKickMinimumPlayers = mkOption {
+          type = types.ints.unsigned;
+          default = 5;
+          description = lib.mdDoc ''
+            The minimum amount of players required to start a kick vote.
+          '';
+        };
+      };
     };
   };
 
@@ -100,7 +386,7 @@ in
 
       serviceConfig = {
         DynamicUser = true;
-        ExecStart = "${pkgs.teeworlds-server}/bin/teeworlds_srv -f ${teeworldsConf}";
+        ExecStart = "${cfg.package}/bin/teeworlds_srv -f ${teeworldsConf}";
 
         # Hardening
         CapabilityBoundingSet = false;
diff --git a/nixos/modules/services/hardware/asusd.nix b/nixos/modules/services/hardware/asusd.nix
index ebbdea26c051..ff9a751e5be8 100644
--- a/nixos/modules/services/hardware/asusd.nix
+++ b/nixos/modules/services/hardware/asusd.nix
@@ -8,6 +8,8 @@ in
     services.asusd = {
       enable = lib.mkEnableOption (lib.mdDoc "the asusd service for ASUS ROG laptops");
 
+      package = lib.mkPackageOption pkgs "asusctl" { };
+
       enableUserService = lib.mkOption {
         type = lib.types.bool;
         default = false;
@@ -73,7 +75,7 @@ in
   };
 
   config = lib.mkIf cfg.enable {
-    environment.systemPackages = [ pkgs.asusctl ];
+    environment.systemPackages = [ cfg.package ];
 
     environment.etc =
       let
@@ -92,9 +94,9 @@ in
       };
 
     services.dbus.enable = true;
-    systemd.packages = [ pkgs.asusctl ];
-    services.dbus.packages = [ pkgs.asusctl ];
-    services.udev.packages = [ pkgs.asusctl ];
+    systemd.packages = [ cfg.package ];
+    services.dbus.packages = [ cfg.package ];
+    services.udev.packages = [ cfg.package ];
     services.supergfxd.enable = lib.mkDefault true;
 
     systemd.user.services.asusd-user.enable = cfg.enableUserService;
diff --git a/nixos/modules/services/hardware/bolt.nix b/nixos/modules/services/hardware/bolt.nix
index 6990a9ea63b3..3bdf67cc1758 100644
--- a/nixos/modules/services/hardware/bolt.nix
+++ b/nixos/modules/services/hardware/bolt.nix
@@ -1,14 +1,13 @@
-# Thunderbolt 3 device manager
-
 { config, lib, pkgs, ...}:
 
 with lib;
 
+let
+  cfg = config.services.hardware.bolt;
+in
 {
   options = {
-
     services.hardware.bolt = {
-
       enable = mkOption {
         type = types.bool;
         default = false;
@@ -20,15 +19,13 @@ with lib;
         '';
       };
 
+      package = mkPackageOption pkgs "bolt" { };
     };
-
   };
 
-  config = mkIf config.services.hardware.bolt.enable {
-
-    environment.systemPackages = [ pkgs.bolt ];
-    services.udev.packages = [ pkgs.bolt ];
-    systemd.packages = [ pkgs.bolt ];
-
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+    services.udev.packages = [ cfg.package ];
+    systemd.packages = [ cfg.package ];
   };
 }
diff --git a/nixos/modules/services/hardware/hddfancontrol.nix b/nixos/modules/services/hardware/hddfancontrol.nix
index f472b5774cbf..746154e7aa17 100644
--- a/nixos/modules/services/hardware/hddfancontrol.nix
+++ b/nixos/modules/services/hardware/hddfancontrol.nix
@@ -60,6 +60,10 @@ in
       systemd.services.hddfancontrol = {
         wantedBy = [ "multi-user.target" ];
         environment.HDDFANCONTROL_ARGS = lib.escapeShellArgs args;
+        serviceConfig = {
+          # Hardening
+          PrivateNetwork = true;
+        };
       };
     }
   );
diff --git a/nixos/modules/services/hardware/monado.nix b/nixos/modules/services/hardware/monado.nix
new file mode 100644
index 000000000000..9f9c6c39a0b4
--- /dev/null
+++ b/nixos/modules/services/hardware/monado.nix
@@ -0,0 +1,102 @@
+{ config
+, lib
+, pkgs
+, ...
+}:
+let
+  inherit (lib) mkDefault mkEnableOption mkIf mkOption mkPackageOption types;
+
+  cfg = config.services.monado;
+
+in
+{
+  options.services.monado = {
+    enable = mkEnableOption "Monado user service";
+
+    package = mkPackageOption pkgs "monado" { };
+
+    defaultRuntime = mkOption {
+      type = types.bool;
+      description = ''
+        Whether to enable Monado as the default OpenXR runtime on the system.
+
+        Note that applications can bypass this option by setting an active
+        runtime in a writable XDG_CONFIG_DIRS location like `~/.config`.
+      '';
+      default = false;
+      example = true;
+    };
+
+    highPriority = mkEnableOption "high priority capability for monado-service"
+      // mkOption { default = true; };
+  };
+
+  config = mkIf cfg.enable {
+    security.wrappers."monado-service" = mkIf cfg.highPriority {
+      setuid = false;
+      owner = "root";
+      group = "root";
+      # cap_sys_nice needed for asynchronous reprojection
+      capabilities = "cap_sys_nice+eip";
+      source = lib.getExe' cfg.package "monado-service";
+    };
+
+    services.udev.packages = with pkgs; [ xr-hardware ];
+
+    systemd.user = {
+      services.monado = {
+        description = "Monado XR runtime service module";
+        requires = [ "monado.socket" ];
+        conflicts = [ "monado-dev.service" ];
+
+        unitConfig.ConditionUser = "!root";
+
+        environment = {
+          # Default options
+          # https://gitlab.freedesktop.org/monado/monado/-/blob/4548e1738591d0904f8db4df8ede652ece889a76/src/xrt/targets/service/monado.in.service#L12
+          XRT_COMPOSITOR_LOG = mkDefault "debug";
+          XRT_PRINT_OPTIONS = mkDefault "on";
+          IPC_EXIT_ON_DISCONNECT = mkDefault "off";
+        };
+
+        serviceConfig = {
+          ExecStart =
+            if cfg.highPriority
+            then "${config.security.wrapperDir}/monado-service"
+            else lib.getExe' cfg.package "monado-service";
+          Restart = "no";
+        };
+
+        restartTriggers = [ cfg.package ];
+      };
+
+      sockets.monado = {
+        description = "Monado XR service module connection socket";
+        conflicts = [ "monado-dev.service" ];
+
+        unitConfig.ConditionUser = "!root";
+
+        socketConfig = {
+          ListenStream = "%t/monado_comp_ipc";
+          RemoveOnStop = true;
+
+          # If Monado crashes while starting up, we want to close incoming OpenXR connections
+          FlushPending = true;
+        };
+
+        restartTriggers = [ cfg.package ];
+
+        wantedBy = [ "sockets.target" ];
+      };
+    };
+
+    environment.systemPackages = [ cfg.package ];
+    environment.pathsToLink = [ "/share/openxr" ];
+
+    environment.etc."xdg/openxr/1/active_runtime.json" = mkIf cfg.defaultRuntime {
+      source = "${cfg.package}/share/openxr/1/openxr_monado.json";
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ Scrumplex ];
+}
diff --git a/nixos/modules/services/hardware/nvidia-container-toolkit-cdi-generator/cdi-generate.nix b/nixos/modules/services/hardware/nvidia-container-toolkit-cdi-generator/cdi-generate.nix
new file mode 100644
index 000000000000..a90d234f65c0
--- /dev/null
+++ b/nixos/modules/services/hardware/nvidia-container-toolkit-cdi-generator/cdi-generate.nix
@@ -0,0 +1,39 @@
+{ config, lib, pkgs }: let
+  mountOptions = { options = ["ro" "nosuid" "nodev" "bind"]; };
+  mounts = [
+    { hostPath = "${lib.getBin config.hardware.nvidia.package}/bin/nvidia-cuda-mps-control";
+      containerPath = "/usr/bin/nvidia-cuda-mps-control"; }
+    { hostPath = "${lib.getBin config.hardware.nvidia.package}/bin/nvidia-cuda-mps-server";
+      containerPath = "/usr/bin/nvidia-cuda-mps-server"; }
+    { hostPath = "${lib.getBin config.hardware.nvidia.package}/bin/nvidia-debugdump";
+      containerPath = "/usr/bin/nvidia-debugdump"; }
+    { hostPath = "${lib.getBin config.hardware.nvidia.package}/bin/nvidia-powerd";
+      containerPath = "/usr/bin/nvidia-powerd"; }
+    { hostPath = "${lib.getBin config.hardware.nvidia.package}/bin/nvidia-smi";
+      containerPath = "/usr/bin/nvidia-smi"; }
+    { hostPath = "${pkgs.nvidia-container-toolkit}/bin/nvidia-ctk";
+      containerPath = "/usr/bin/nvidia-ctk"; }
+    { hostPath = "${pkgs.glibc}/lib";
+      containerPath = "${pkgs.glibc}/lib"; }
+    { hostPath = "${pkgs.glibc}/lib64";
+      containerPath = "${pkgs.glibc}/lib64"; }
+  ];
+  jqAddMountExpression = ".containerEdits.mounts[.containerEdits.mounts | length] |= . +";
+  mountsToJq = lib.concatMap
+    (mount:
+      ["${pkgs.jq}/bin/jq '${jqAddMountExpression} ${builtins.toJSON (mount // mountOptions)}'"])
+    mounts;
+in ''
+#! ${pkgs.runtimeShell}
+
+function cdiGenerate {
+  ${pkgs.nvidia-container-toolkit}/bin/nvidia-ctk cdi generate \
+    --format json \
+    --ldconfig-path ${pkgs.glibc.bin}/bin/ldconfig \
+    --library-search-path ${config.hardware.nvidia.package}/lib \
+    --nvidia-ctk-path ${pkgs.nvidia-container-toolkit}/bin/nvidia-ctk
+}
+
+cdiGenerate | \
+  ${lib.concatStringsSep " | " mountsToJq} > $RUNTIME_DIRECTORY/nvidia-container-toolkit.json
+''
diff --git a/nixos/modules/services/hardware/nvidia-container-toolkit-cdi-generator/default.nix b/nixos/modules/services/hardware/nvidia-container-toolkit-cdi-generator/default.nix
new file mode 100644
index 000000000000..3c96e9c41be5
--- /dev/null
+++ b/nixos/modules/services/hardware/nvidia-container-toolkit-cdi-generator/default.nix
@@ -0,0 +1,38 @@
+{ config, lib, pkgs, ... }:
+
+{
+
+  options = {
+
+    hardware.nvidia-container-toolkit-cdi-generator.enable = lib.mkOption {
+      default = false;
+      internal = true;
+      visible = false;
+      type = lib.types.bool;
+      description = lib.mdDoc ''
+        Enable dynamic CDI configuration for NVidia devices by running
+        nvidia-container-toolkit on boot.
+      '';
+    };
+
+  };
+
+  config = {
+
+    systemd.services.nvidia-container-toolkit-cdi-generator = lib.mkIf config.hardware.nvidia-container-toolkit-cdi-generator.enable {
+      description = "Container Device Interface (CDI) for Nvidia generator";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "systemd-udev-settle.service" ];
+      serviceConfig = {
+        RuntimeDirectory = "cdi";
+        RemainAfterExit = true;
+        ExecStart = let
+          script = (pkgs.writeScriptBin "nvidia-cdi-generator"
+            (import ./cdi-generate.nix { inherit config lib pkgs; })); in (lib.getExe script);
+        Type = "oneshot";
+      };
+    };
+
+  };
+
+}
diff --git a/nixos/modules/services/hardware/pcscd.nix b/nixos/modules/services/hardware/pcscd.nix
index b5963e1d29a3..77c2d9b53f03 100644
--- a/nixos/modules/services/hardware/pcscd.nix
+++ b/nixos/modules/services/hardware/pcscd.nix
@@ -3,6 +3,7 @@
 with lib;
 
 let
+  cfg = config.services.pcscd;
   cfgFile = pkgs.writeText "reader.conf" config.services.pcscd.readerConfig;
 
   package = if config.security.polkit.enable
@@ -41,6 +42,12 @@ in
         See {manpage}`reader.conf(5)` for valid options.
       '';
     };
+
+    extraArgs = mkOption {
+      type = types.listOf types.str;
+      default = [ ];
+      description = lib.mdDoc "Extra command line arguments to be passed to the PCSC daemon.";
+    };
   };
 
   config = mkIf config.services.pcscd.enable {
@@ -64,7 +71,7 @@ in
       # around it, we force the path to the cfgFile.
       #
       # https://github.com/NixOS/nixpkgs/issues/121088
-      serviceConfig.ExecStart = [ "" "${package}/bin/pcscd -f -x -c ${cfgFile}" ];
+      serviceConfig.ExecStart = [ "" "${lib.getExe package} -f -x -c ${cfgFile} ${lib.escapeShellArgs cfg.extraArgs}" ];
     };
   };
 }
diff --git a/nixos/modules/services/hardware/thinkfan.nix b/nixos/modules/services/hardware/thinkfan.nix
index cca35f492b8e..b62fb5e9f8c9 100644
--- a/nixos/modules/services/hardware/thinkfan.nix
+++ b/nixos/modules/services/hardware/thinkfan.nix
@@ -217,8 +217,13 @@ in {
 
     systemd.services = {
       thinkfan.environment.THINKFAN_ARGS = escapeShellArgs ([ "-c" configFile ] ++ cfg.extraArgs);
-      thinkfan.serviceConfig.Restart = "on-failure";
-      thinkfan.serviceConfig.RestartSec = "30s";
+      thinkfan.serviceConfig = {
+        Restart = "on-failure";
+        RestartSec = "30s";
+
+        # Hardening
+        PrivateNetwork = true;
+      };
 
       # must be added manually, see issue #81138
       thinkfan.wantedBy = [ "multi-user.target" ];
diff --git a/nixos/modules/services/home-automation/ebusd.nix b/nixos/modules/services/home-automation/ebusd.nix
index 519d116e0e55..f68a8bdb6bfa 100644
--- a/nixos/modules/services/home-automation/ebusd.nix
+++ b/nixos/modules/services/home-automation/ebusd.nix
@@ -15,12 +15,12 @@ let
     "--port=${toString cfg.port}"
     "--configpath=${cfg.configpath}"
     "--scanconfig=${cfg.scanconfig}"
+    "--log=all:${cfg.logs.all}"
     "--log=main:${cfg.logs.main}"
     "--log=network:${cfg.logs.network}"
     "--log=bus:${cfg.logs.bus}"
     "--log=update:${cfg.logs.update}"
     "--log=other:${cfg.logs.other}"
-    "--log=all:${cfg.logs.all}"
   ] ++ lib.optionals cfg.readonly [
     "--readonly"
   ] ++ lib.optionals cfg.mqtt.enable [
diff --git a/nixos/modules/services/home-automation/matter-server.nix b/nixos/modules/services/home-automation/matter-server.nix
new file mode 100644
index 000000000000..864ef9e20083
--- /dev/null
+++ b/nixos/modules/services/home-automation/matter-server.nix
@@ -0,0 +1,125 @@
+{ lib
+, pkgs
+, config
+, ...
+}:
+
+with lib;
+
+let
+  cfg = config.services.matter-server;
+  storageDir = "matter-server";
+  storagePath = "/var/lib/${storageDir}";
+  vendorId = "4939"; # home-assistant vendor ID
+in
+
+{
+  meta.maintainers = with lib.maintainers; [ leonm1 ];
+
+  options.services.matter-server = with types; {
+    enable = mkEnableOption (lib.mdDoc "Matter-server");
+
+    package = mkPackageOptionMD pkgs "python-matter-server" { };
+
+    port = mkOption {
+      type = types.port;
+      default = 5580;
+      description = "Port to expose the matter-server service on.";
+    };
+
+    logLevel = mkOption {
+      type = types.enum [ "critical" "error" "warning" "info" "debug" ];
+      default = "info";
+      description = "Verbosity of logs from the matter-server";
+    };
+
+    extraArgs = mkOption {
+      type = listOf str;
+      default = [];
+      description = ''
+        Extra arguments to pass to the matter-server executable.
+        See https://github.com/home-assistant-libs/python-matter-server?tab=readme-ov-file#running-the-development-server for options.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.matter-server = {
+      after = [ "network-online.target" ];
+      before = [ "home-assistant.service" ];
+      wants = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+      description = "Matter Server";
+      environment.HOME = storagePath;
+      serviceConfig = {
+        ExecStart = (concatStringsSep " " [
+          "${cfg.package}/bin/matter-server"
+          "--port" (toString cfg.port)
+          "--vendorid" vendorId
+          "--storage-path" storagePath
+          "--log-level" "${cfg.logLevel}"
+          "${escapeShellArgs cfg.extraArgs}"
+        ]);
+        # Start with a clean root filesystem, and allowlist what the container
+        # is permitted to access.
+        TemporaryFileSystem = "/";
+        # Allowlist /nix/store (to allow the binary to find its dependencies)
+        # and dbus.
+        ReadOnlyPaths = "/nix/store /run/dbus";
+        # Let systemd manage `/var/lib/matter-server` for us inside the
+        # ephemeral TemporaryFileSystem.
+        StateDirectory = storageDir;
+        # `python-matter-server` writes to /data even when a storage-path is
+        # specified. This bind-mount points /data at the systemd-managed
+        # /var/lib/matter-server, so all files get dropped into the state
+        # directory.
+        BindPaths = "${storagePath}:/data";
+
+        # Hardening bits
+        AmbientCapabilities = "";
+        CapabilityBoundingSet = "";
+        DevicePolicy = "closed";
+        DynamicUser = true;
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        NoNewPrivileges = true;
+        PrivateDevices = true;
+        PrivateTmp = true;
+        PrivateUsers = true;
+        ProcSubset = "pid";
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        RestrictAddressFamilies = [
+          "AF_INET"
+          "AF_INET6"
+          "AF_NETLINK"
+        ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        SystemCallFilter = concatStringsSep " " [
+          "~" # Blocklist
+          "@clock"
+          "@cpu-emulation"
+          "@debug"
+          "@module"
+          "@mount"
+          "@obsolete"
+          "@privileged"
+          "@raw-io"
+          "@reboot"
+          "@resources"
+          "@swap"
+        ];
+        UMask = "0077";
+      };
+    };
+  };
+}
+
diff --git a/nixos/modules/services/misc/atuin.nix b/nixos/modules/services/misc/atuin.nix
index 2d6ffc510ce5..7e89929884d6 100644
--- a/nixos/modules/services/misc/atuin.nix
+++ b/nixos/modules/services/misc/atuin.nix
@@ -8,6 +8,8 @@ in
     services.atuin = {
       enable = lib.mkEnableOption (mdDoc "Atuin server for shell history sync");
 
+      package = lib.mkPackageOption pkgs "atuin" { };
+
       openRegistration = mkOption {
         type = types.bool;
         default = false;
@@ -52,10 +54,13 @@ in
         };
 
         uri = mkOption {
-          type = types.str;
+          type = types.nullOr types.str;
           default = "postgresql:///atuin?host=/run/postgresql";
           example = "postgresql://atuin@localhost:5432/atuin";
-          description = mdDoc "URI to the database";
+          description = mdDoc ''
+            URI to the database.
+            Can be set to null in which case ATUIN_DB_URI should be set through an EnvironmentFile
+          '';
         };
       };
     };
@@ -85,7 +90,7 @@ in
       wantedBy = [ "multi-user.target" ];
 
       serviceConfig = {
-        ExecStart = "${pkgs.atuin}/bin/atuin server start";
+        ExecStart = "${lib.getExe cfg.package} server start";
         RuntimeDirectory = "atuin";
         RuntimeDirectoryMode = "0700";
         DynamicUser = true;
@@ -132,9 +137,10 @@ in
         ATUIN_PORT = toString cfg.port;
         ATUIN_MAX_HISTORY_LENGTH = toString cfg.maxHistoryLength;
         ATUIN_OPEN_REGISTRATION = lib.boolToString cfg.openRegistration;
-        ATUIN_DB_URI = cfg.database.uri;
         ATUIN_PATH = cfg.path;
         ATUIN_CONFIG_DIR = "/run/atuin"; # required to start, but not used as configuration is via environment variables
+      } // lib.optionalAttrs (cfg.database.uri != null) {
+        ATUIN_DB_URI = cfg.database.uri;
       };
     };
 
diff --git a/nixos/modules/services/misc/docker-registry.nix b/nixos/modules/services/misc/docker-registry.nix
index e8fbc05423d3..78d1d6339ed6 100644
--- a/nixos/modules/services/misc/docker-registry.nix
+++ b/nixos/modules/services/misc/docker-registry.nix
@@ -63,6 +63,12 @@ in {
       type = types.port;
     };
 
+    openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc "Opens the port used by the firewall.";
+    };
+
     storagePath = mkOption {
       type = types.nullOr types.path;
       default = "/var/lib/docker-registry";
@@ -154,5 +160,9 @@ in {
         isSystemUser = true;
       };
     users.groups.docker-registry = {};
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.port ];
+    };
   };
 }
diff --git a/nixos/modules/services/misc/ollama.nix b/nixos/modules/services/misc/ollama.nix
index d9359d2b5cd4..3ac3beb4de07 100644
--- a/nixos/modules/services/misc/ollama.nix
+++ b/nixos/modules/services/misc/ollama.nix
@@ -1,27 +1,44 @@
-{ config, lib, pkgs, ... }: let
+{ config, lib, pkgs, ... }:
+let
+  inherit (lib) types;
 
   cfg = config.services.ollama;
-
-in {
-
+  ollamaPackage = cfg.package.override {
+    inherit (cfg) acceleration;
+    linuxPackages = config.boot.kernelPackages // {
+      nvidia_x11 = config.hardware.nvidia.package;
+    };
+  };
+in
+{
   options = {
     services.ollama = {
       enable = lib.mkEnableOption (
         lib.mdDoc "Server for local large language models"
       );
       listenAddress = lib.mkOption {
-        type = lib.types.str;
+        type = types.str;
         default = "127.0.0.1:11434";
         description = lib.mdDoc ''
           Specifies the bind address on which the ollama server HTTP interface listens.
         '';
       };
+      acceleration = lib.mkOption {
+        type = types.nullOr (types.enum [ "rocm" "cuda" ]);
+        default = null;
+        example = "rocm";
+        description = lib.mdDoc ''
+          Specifies the interface to use for hardware acceleration.
+
+          - `rocm`: supported by modern AMD GPUs
+          - `cuda`: supported by modern NVIDIA GPUs
+        '';
+      };
       package = lib.mkPackageOption pkgs "ollama" { };
     };
   };
 
   config = lib.mkIf cfg.enable {
-
     systemd = {
       services.ollama = {
         wantedBy = [ "multi-user.target" ];
@@ -33,7 +50,7 @@ in {
           OLLAMA_HOST = cfg.listenAddress;
         };
         serviceConfig = {
-          ExecStart = "${lib.getExe cfg.package} serve";
+          ExecStart = "${lib.getExe ollamaPackage} serve";
           WorkingDirectory = "/var/lib/ollama";
           StateDirectory = [ "ollama" ];
           DynamicUser = true;
@@ -41,10 +58,8 @@ in {
       };
     };
 
-    environment.systemPackages = [ cfg.package ];
-
+    environment.systemPackages = [ ollamaPackage ];
   };
 
-  meta.maintainers = with lib.maintainers; [ onny ];
-
+  meta.maintainers = with lib.maintainers; [ abysssol onny ];
 }
diff --git a/nixos/modules/services/misc/paperless.nix b/nixos/modules/services/misc/paperless.nix
index 1256d8315c8b..9314c4f3848d 100644
--- a/nixos/modules/services/misc/paperless.nix
+++ b/nixos/modules/services/misc/paperless.nix
@@ -307,6 +307,9 @@ in
         Restart = "on-failure";
       };
       environment = env;
+      # Allow the consumer to access the private /tmp directory of the server.
+      # This is required to support consuming files via a local folder.
+      unitConfig.JoinsNamespaceOf = "paperless-task-queue.service";
     };
 
     systemd.services.paperless-web = {
@@ -339,6 +342,7 @@ in
         User = cfg.user;
         Restart = "on-failure";
 
+        LimitNOFILE = 65536;
         # gunicorn needs setuid, liblapack needs mbind
         SystemCallFilter = defaultServiceConfig.SystemCallFilter ++ [ "@setuid mbind" ];
         # Needs to serve web page
diff --git a/nixos/modules/services/misc/sourcehut/default.nix b/nixos/modules/services/misc/sourcehut/default.nix
index 80a6162b2168..557d6d7e7168 100644
--- a/nixos/modules/services/misc/sourcehut/default.nix
+++ b/nixos/modules/services/misc/sourcehut/default.nix
@@ -790,13 +790,21 @@ in
         '';
       };
       systemd.tmpfiles.settings."10-sourcehut-gitsrht" = mkIf cfg.git.enable (
-        builtins.listToAttrs (map (name: {
-          name = "/var/log/sourcehut/gitsrht-${name}";
-          value.f = {
-            inherit (cfg.git) user group;
-            mode = "0644";
-          };
-        }) [ "keys" "shell" "update-hook" ])
+        mkMerge [
+          (builtins.listToAttrs (map (name: {
+            name = "/var/log/sourcehut/gitsrht-${name}";
+            value.f = {
+              inherit (cfg.git) user group;
+              mode = "0644";
+            };
+          }) [ "keys" "shell" "update-hook" ]))
+          {
+            ${cfg.settings."git.sr.ht".repos}.d = {
+              inherit (cfg.git) user group;
+              mode = "0644";
+            };
+          }
+        ]
       );
       systemd.services.sshd = {
         preStart = mkIf cfg.hg.enable ''
diff --git a/nixos/modules/services/misc/transfer-sh.nix b/nixos/modules/services/misc/transfer-sh.nix
new file mode 100644
index 000000000000..899d9dfc3c10
--- /dev/null
+++ b/nixos/modules/services/misc/transfer-sh.nix
@@ -0,0 +1,102 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.transfer-sh;
+  inherit (lib)
+    mkDefault mkEnableOption mkPackageOption mkIf mkOption
+    types mapAttrs isBool getExe boolToString mdDoc optionalAttrs;
+in
+{
+  options.services.transfer-sh = {
+    enable = mkEnableOption (mdDoc "Easy and fast file sharing from the command-line");
+
+    package = mkPackageOption pkgs "transfer-sh" { };
+
+    settings = mkOption {
+      type = types.submodule { freeformType = with types; attrsOf (oneOf [ bool int str ]); };
+      default = { };
+      example = {
+        LISTENER = ":8080";
+        BASEDIR = "/var/lib/transfer.sh";
+        TLS_LISTENER_ONLY = false;
+      };
+      description = mdDoc ''
+        Additional configuration for transfer-sh, see
+        <https://github.com/dutchcoders/transfer.sh#usage-1>
+        for supported values.
+
+        For secrets use secretFile option instead.
+      '';
+    };
+
+    provider = mkOption {
+      type = types.enum [ "local" "s3" "storj" "gdrive" ];
+      default = "local";
+      description = mdDoc "Storage providers to use";
+    };
+
+    secretFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      example = "/run/secrets/transfer-sh.env";
+      description = mdDoc ''
+        Path to file containing environment variables.
+        Useful for passing down secrets.
+        Some variables that can be considered secrets are:
+         - AWS_ACCESS_KEY
+         - AWS_ACCESS_KEY
+         - TLS_PRIVATE_KEY
+         - HTTP_AUTH_HTPASSWD
+      '';
+    };
+  };
+
+  config =
+    let
+      localProvider = (cfg.provider == "local");
+      stateDirectory = "/var/lib/transfer.sh";
+    in
+    mkIf cfg.enable
+      {
+        services.transfer-sh.settings = {
+          LISTENER = mkDefault ":8080";
+        } // optionalAttrs localProvider {
+          BASEDIR = mkDefault stateDirectory;
+        };
+
+        systemd.services.transfer-sh = {
+          after = [ "network.target" ];
+          wantedBy = [ "multi-user.target" ];
+          environment = mapAttrs (_: v: if isBool v then boolToString v else toString v) cfg.settings;
+          serviceConfig = {
+            CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
+            DevicePolicy = "closed";
+            DynamicUser = true;
+            ExecStart = "${getExe cfg.package} --provider ${cfg.provider}";
+            LockPersonality = true;
+            MemoryDenyWriteExecute = true;
+            PrivateDevices = true;
+            PrivateUsers = true;
+            ProtectClock = true;
+            ProtectControlGroups = true;
+            ProtectHostname = true;
+            ProtectKernelLogs = true;
+            ProtectKernelModules = true;
+            ProtectKernelTunables = true;
+            ProtectProc = "invisible";
+            RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+            RestrictNamespaces = true;
+            RestrictRealtime = true;
+            SystemCallArchitectures = [ "native" ];
+            SystemCallFilter = [ "@system-service" ];
+            StateDirectory = baseNameOf stateDirectory;
+          } // optionalAttrs (cfg.secretFile != null) {
+            EnvironmentFile = cfg.secretFile;
+          } // optionalAttrs localProvider {
+            ReadWritePaths = cfg.settings.BASEDIR;
+          };
+        };
+      };
+
+  meta.maintainers = with lib.maintainers; [ ocfox ];
+}
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/fastly.nix b/nixos/modules/services/monitoring/prometheus/exporters/fastly.nix
index 36409caccf2e..2a8b7fc0818d 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/fastly.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/fastly.nix
@@ -1,41 +1,54 @@
-{ config, lib, pkgs, options }:
+{ config
+, lib
+, pkgs
+, options
+}:
 
-with lib;
+let
+  inherit (lib)
+    escapeShellArgs
+    mkOption
+    optionals
+    types
+  ;
 
-let cfg = config.services.prometheus.exporters.fastly;
+  cfg = config.services.prometheus.exporters.fastly;
 in
 {
   port = 9118;
-  extraOpts = {
-    debug = mkEnableOption (lib.mdDoc "Debug logging mode for fastly-exporter");
-
+  extraOpts = with types; {
     configFile = mkOption {
-      type = types.nullOr types.path;
+      type = nullOr path;
       default = null;
-      description = lib.mdDoc ''
+      example = "./fastly-exporter-config.txt";
+      description = ''
         Path to a fastly-exporter configuration file.
         Example one can be generated with `fastly-exporter --config-file-example`.
       '';
-      example = "./fastly-exporter-config.txt";
     };
 
     tokenPath = mkOption {
-      type = types.nullOr types.path;
-      apply = final: if final == null then null else toString final;
-      description = lib.mdDoc ''
+      type = path;
+      description = ''
         A run-time path to the token file, which is supposed to be provisioned
         outside of Nix store.
       '';
     };
   };
   serviceOpts = {
-    script = ''
-      ${optionalString (cfg.tokenPath != null)
-      "export FASTLY_API_TOKEN=$(cat ${toString cfg.tokenPath})"}
-      ${pkgs.prometheus-fastly-exporter}/bin/fastly-exporter \
-        -listen http://${cfg.listenAddress}:${toString cfg.port}
-        ${optionalString cfg.debug "-debug true"} \
-        ${optionalString (cfg.configFile != null) "-config-file ${cfg.configFile}"}
+    serviceConfig = {
+      LoadCredential = "fastly-api-token:${cfg.tokenPath}";
+    };
+    script = let
+      call = escapeShellArgs ([
+        "${pkgs.prometheus-fastly-exporter}/bin/fastly-exporter"
+        "-listen" "${cfg.listenAddress}:${toString cfg.port}"
+      ] ++ optionals (cfg.configFile != null) [
+        "--config-file" cfg.configFile
+      ] ++ cfg.extraFlags);
+    in ''
+      export FASTLY_API_TOKEN="$(cat $CREDENTIALS_DIRECTORY/fastly-api-token)"
+      ${call}
     '';
   };
 }
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/nut.nix b/nixos/modules/services/monitoring/prometheus/exporters/nut.nix
index 1c86b48b4509..e58a394456a3 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/nut.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/nut.nix
@@ -36,6 +36,17 @@ in
         provisioned outside of Nix store.
       '';
     };
+    nutVariables = mkOption {
+      type = types.listOf types.str;
+      default = [ ];
+      description = ''
+        List of NUT variable names to monitor.
+
+        If no variables are set, all numeric variables will be exported automatically.
+        See the [upstream docs](https://github.com/DRuggeri/nut_exporter?tab=readme-ov-file#variables-and-information)
+        for more information.
+      '';
+    };
   };
   serviceOpts = {
     script = ''
@@ -44,7 +55,9 @@ in
       ${pkgs.prometheus-nut-exporter}/bin/nut_exporter \
         --nut.server=${cfg.nutServer} \
         --web.listen-address="${cfg.listenAddress}:${toString cfg.port}" \
-        ${optionalString (cfg.nutUser != "") "--nut.username=${cfg.nutUser}"}
+        ${optionalString (cfg.nutUser != "") "--nut.username=${cfg.nutUser}"} \
+        ${optionalString (cfg.nutVariables != []) "--nut.vars_enable=${concatStringsSep "," cfg.nutVariables}"} \
+        ${concatStringsSep " " cfg.extraFlags}
     '';
   };
 }
diff --git a/nixos/modules/services/monitoring/scrutiny.nix b/nixos/modules/services/monitoring/scrutiny.nix
new file mode 100644
index 000000000000..454668a9a128
--- /dev/null
+++ b/nixos/modules/services/monitoring/scrutiny.nix
@@ -0,0 +1,221 @@
+{ config, lib, pkgs, ... }:
+let
+  cfg = config.services.scrutiny;
+  # Define the settings format used for this program
+  settingsFormat = pkgs.formats.yaml { };
+in
+{
+  options = {
+    services.scrutiny = {
+      enable = lib.mkEnableOption "Enables the scrutiny web application.";
+
+      package = lib.mkPackageOptionMD pkgs "scrutiny" { };
+
+      openFirewall = lib.mkOption {
+        type = lib.types.bool;
+        default = false;
+        description = "Open the default ports in the firewall for Scrutiny.";
+      };
+
+      influxdb.enable = lib.mkOption {
+        type = lib.types.bool;
+        default = true;
+        description = lib.mdDoc ''
+          Enables InfluxDB on the host system using the `services.influxdb2` NixOS module
+          with default options.
+
+          If you already have InfluxDB configured, or wish to connect to an external InfluxDB
+          instance, disable this option.
+        '';
+      };
+
+      settings = lib.mkOption {
+        description = lib.mdDoc ''
+          Scrutiny settings to be rendered into the configuration file.
+
+          See https://github.com/AnalogJ/scrutiny/blob/master/example.scrutiny.yaml.
+        '';
+        default = { };
+        type = lib.types.submodule {
+          freeformType = settingsFormat.type;
+
+          options.web.listen.port = lib.mkOption {
+            type = lib.types.port;
+            default = 8080;
+            description = lib.mdDoc "Port for web application to listen on.";
+          };
+
+          options.web.listen.host = lib.mkOption {
+            type = lib.types.str;
+            default = "0.0.0.0";
+            description = lib.mdDoc "Interface address for web application to bind to.";
+          };
+
+          options.web.listen.basepath = lib.mkOption {
+            type = lib.types.str;
+            default = "";
+            example = "/scrutiny";
+            description = lib.mdDoc ''
+              If Scrutiny will be behind a path prefixed reverse proxy, you can override this
+              value to serve Scrutiny on a subpath.
+            '';
+          };
+
+          options.log.level = lib.mkOption {
+            type = lib.types.enum [ "INFO" "DEBUG" ];
+            default = "INFO";
+            description = lib.mdDoc "Log level for Scrutiny.";
+          };
+
+          options.web.influxdb.scheme = lib.mkOption {
+            type = lib.types.str;
+            default = "http";
+            description = lib.mdDoc "URL scheme to use when connecting to InfluxDB.";
+          };
+
+          options.web.influxdb.host = lib.mkOption {
+            type = lib.types.str;
+            default = "0.0.0.0";
+            description = lib.mdDoc "IP or hostname of the InfluxDB instance.";
+          };
+
+          options.web.influxdb.port = lib.mkOption {
+            type = lib.types.port;
+            default = 8086;
+            description = lib.mdDoc "The port of the InfluxDB instance.";
+          };
+
+          options.web.influxdb.tls.insecure_skip_verify = lib.mkOption {
+            type = lib.types.bool;
+            default = false;
+            description = lib.mdDoc "Skip TLS verification when connecting to InfluxDB.";
+          };
+
+          options.web.influxdb.token = lib.mkOption {
+            type = lib.types.nullOr lib.types.str;
+            default = null;
+            description = lib.mdDoc "Authentication token for connecting to InfluxDB.";
+          };
+
+          options.web.influxdb.org = lib.mkOption {
+            type = lib.types.nullOr lib.types.str;
+            default = null;
+            description = lib.mdDoc "InfluxDB organisation under which to store data.";
+          };
+
+          options.web.influxdb.bucket = lib.mkOption {
+            type = lib.types.nullOr lib.types.str;
+            default = null;
+            description = lib.mdDoc "InfluxDB bucket in which to store data.";
+          };
+        };
+      };
+
+      collector = {
+        enable = lib.mkEnableOption "Enables the scrutiny metrics collector.";
+
+        package = lib.mkPackageOptionMD pkgs "scrutiny-collector" { };
+
+        schedule = lib.mkOption {
+          type = lib.types.str;
+          default = "*:0/15";
+          description = lib.mdDoc ''
+            How often to run the collector in systemd calendar format.
+          '';
+        };
+
+        settings = lib.mkOption {
+          description = lib.mdDoc ''
+            Collector settings to be rendered into the collector configuration file.
+
+            See https://github.com/AnalogJ/scrutiny/blob/master/example.collector.yaml.
+          '';
+          default = { };
+          type = lib.types.submodule {
+            freeformType = settingsFormat.type;
+
+            options.host.id = lib.mkOption {
+              type = lib.types.nullOr lib.types.str;
+              default = null;
+              description = lib.mdDoc "Host ID for identifying/labelling groups of disks";
+            };
+
+            options.api.endpoint = lib.mkOption {
+              type = lib.types.str;
+              default = "http://localhost:8080";
+              description = lib.mdDoc "Scrutiny app API endpoint for sending metrics to.";
+            };
+
+            options.log.level = lib.mkOption {
+              type = lib.types.enum [ "INFO" "DEBUG" ];
+              default = "INFO";
+              description = lib.mdDoc "Log level for Scrutiny collector.";
+            };
+          };
+        };
+      };
+    };
+  };
+
+  config = lib.mkIf (cfg.enable || cfg.collector.enable) {
+    services.influxdb2.enable = cfg.influxdb.enable;
+
+    networking.firewall = lib.mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.settings.web.listen.port ];
+    };
+
+    services.smartd = lib.mkIf cfg.collector.enable {
+      enable = true;
+      extraOptions = [
+        "-A /var/log/smartd/"
+        "--interval=600"
+      ];
+    };
+
+    systemd = {
+      services = {
+        scrutiny = lib.mkIf cfg.enable {
+          description = "Hard Drive S.M.A.R.T Monitoring, Historical Trends & Real World Failure Thresholds";
+          wantedBy = [ "multi-user.target" ];
+          after = [ "network.target" ];
+          environment = {
+            SCRUTINY_VERSION = "1";
+            SCRUTINY_WEB_DATABASE_LOCATION = "/var/lib/scrutiny/scrutiny.db";
+            SCRUTINY_WEB_SRC_FRONTEND_PATH = "${cfg.package}/share/scrutiny";
+          };
+          serviceConfig = {
+            DynamicUser = true;
+            ExecStart = "${lib.getExe cfg.package} start --config ${settingsFormat.generate "scrutiny.yaml" cfg.settings}";
+            Restart = "always";
+            StateDirectory = "scrutiny";
+            StateDirectoryMode = "0750";
+          };
+        };
+
+        scrutiny-collector = lib.mkIf cfg.collector.enable {
+          description = "Scrutiny Collector Service";
+          environment = {
+            COLLECTOR_VERSION = "1";
+            COLLECTOR_API_ENDPOINT = cfg.collector.settings.api.endpoint;
+          };
+          serviceConfig = {
+            Type = "oneshot";
+            ExecStart = "${lib.getExe cfg.collector.package} run --config ${settingsFormat.generate "scrutiny-collector.yaml" cfg.collector.settings}";
+          };
+        };
+      };
+
+      timers = lib.mkIf cfg.collector.enable {
+        scrutiny-collector = {
+          timerConfig = {
+            OnCalendar = cfg.collector.schedule;
+            Persistent = true;
+            Unit = "scrutiny-collector.service";
+          };
+        };
+      };
+    };
+  };
+
+  meta.maintainers = [ lib.maintainers.jnsgruk ];
+}
diff --git a/nixos/modules/services/networking/bee-clef.nix b/nixos/modules/services/networking/bee-clef.nix
deleted file mode 100644
index 75e76f019a71..000000000000
--- a/nixos/modules/services/networking/bee-clef.nix
+++ /dev/null
@@ -1,107 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-# NOTE for now nothing is installed into /etc/bee-clef/. the config files are used as read-only from the nix store.
-
-with lib;
-let
-  cfg = config.services.bee-clef;
-in {
-  meta = {
-    maintainers = with maintainers; [ attila-lendvai ];
-  };
-
-  ### interface
-
-  options = {
-    services.bee-clef = {
-      enable = mkEnableOption (lib.mdDoc "clef external signer instance for Ethereum Swarm Bee");
-
-      dataDir = mkOption {
-        type = types.nullOr types.str;
-        default = "/var/lib/bee-clef";
-        description = lib.mdDoc ''
-          Data dir for bee-clef. Beware that some helper scripts may not work when changed!
-          The service itself should work fine, though.
-        '';
-      };
-
-      passwordFile = mkOption {
-        type = types.nullOr types.str;
-        default = "/var/lib/bee-clef/password";
-        description = lib.mdDoc "Password file for bee-clef.";
-      };
-
-      user = mkOption {
-        type = types.str;
-        default = "bee-clef";
-        description = lib.mdDoc ''
-          User the bee-clef daemon should execute under.
-        '';
-      };
-
-      group = mkOption {
-        type = types.str;
-        default = "bee-clef";
-        description = lib.mdDoc ''
-          Group the bee-clef daemon should execute under.
-        '';
-      };
-    };
-  };
-
-  ### implementation
-
-  config = mkIf cfg.enable {
-    # if we ever want to have rules.js under /etc/bee-clef/
-    # environment.etc."bee-clef/rules.js".source = ${pkgs.bee-clef}/rules.js
-
-    systemd.packages = [ pkgs.bee-clef ]; # include the upstream bee-clef.service file
-
-    systemd.tmpfiles.rules = [
-        "d '${cfg.dataDir}/'         0750 ${cfg.user} ${cfg.group}"
-        "d '${cfg.dataDir}/keystore' 0700 ${cfg.user} ${cfg.group}"
-      ];
-
-    systemd.services.bee-clef = {
-      path = [
-        # these are needed for the ensure-clef-account script
-        pkgs.coreutils
-        pkgs.gnused
-        pkgs.gawk
-      ];
-
-      wantedBy = [ "bee.service" "multi-user.target" ];
-
-      serviceConfig = {
-        User = cfg.user;
-        Group = cfg.group;
-        ExecStartPre = ''${pkgs.bee-clef}/share/bee-clef/ensure-clef-account "${cfg.dataDir}" "${pkgs.bee-clef}/share/bee-clef/"'';
-        ExecStart = [
-          "" # this hides/overrides what's in the original entry
-          "${pkgs.bee-clef}/share/bee-clef/bee-clef-service start"
-        ];
-        ExecStop = [
-          "" # this hides/overrides what's in the original entry
-          "${pkgs.bee-clef}/share/bee-clef/bee-clef-service stop"
-        ];
-        Environment = [
-          "CONFIGDIR=${cfg.dataDir}"
-          "PASSWORD_FILE=${cfg.passwordFile}"
-        ];
-      };
-    };
-
-    users.users = optionalAttrs (cfg.user == "bee-clef") {
-      bee-clef = {
-        group = cfg.group;
-        home = cfg.dataDir;
-        isSystemUser = true;
-        description = "Daemon user for the bee-clef service";
-      };
-    };
-
-    users.groups = optionalAttrs (cfg.group == "bee-clef") {
-      bee-clef = {};
-    };
-  };
-}
diff --git a/nixos/modules/services/networking/bee.nix b/nixos/modules/services/networking/bee.nix
index 962cfd30c3fe..a4d20494bf6b 100644
--- a/nixos/modules/services/networking/bee.nix
+++ b/nixos/modules/services/networking/bee.nix
@@ -8,7 +8,7 @@ let
 in {
   meta = {
     # doc = ./bee.xml;
-    maintainers = with maintainers; [ attila-lendvai ];
+    maintainers = with maintainers; [ ];
   };
 
   ### interface
@@ -73,13 +73,10 @@ in {
       }
     ];
 
-    warnings = optional (! config.services.bee-clef.enable) "The bee service requires an external signer. Consider setting `config.services.bee-clef.enable` = true";
-
     services.bee.settings = {
       data-dir             = lib.mkDefault "/var/lib/bee";
       password-file        = lib.mkDefault "/var/lib/bee/password";
       clef-signer-enable   = lib.mkDefault true;
-      clef-signer-endpoint = lib.mkDefault "/var/lib/bee-clef/clef.ipc";
       swap-endpoint        = lib.mkDefault "https://rpc.slock.it/goerli";
     };
 
@@ -90,9 +87,6 @@ in {
     ];
 
     systemd.services.bee = {
-      requires = optional config.services.bee-clef.enable
-        "bee-clef.service";
-
       wantedBy = [ "multi-user.target" ];
 
       serviceConfig = {
@@ -120,7 +114,6 @@ Bee has SWAP enabled by default and it needs ethereum endpoint to operate.
 It is recommended to use external signer with bee.
 Check documentation for more info:
 - SWAP https://docs.ethswarm.org/docs/installation/manual#swap-bandwidth-incentives
-- External signer https://docs.ethswarm.org/docs/installation/bee-clef
 
 After you finish configuration run 'sudo bee-get-addr'."
         fi
@@ -133,8 +126,6 @@ After you finish configuration run 'sudo bee-get-addr'."
         home = cfg.settings.data-dir;
         isSystemUser = true;
         description = "Daemon user for Ethereum Swarm Bee";
-        extraGroups = optional config.services.bee-clef.enable
-          config.services.bee-clef.group;
       };
     };
 
diff --git a/nixos/modules/services/networking/bird-lg.nix b/nixos/modules/services/networking/bird-lg.nix
index be9f4101e6ab..1c59f7a6ae7c 100644
--- a/nixos/modules/services/networking/bird-lg.nix
+++ b/nixos/modules/services/networking/bird-lg.nix
@@ -194,8 +194,8 @@ in
         allowedIPs = mkOption {
           type = types.listOf types.str;
           default = [ ];
-          example = [ "192.168.25.52" "192.168.25.53" ];
-          description = lib.mdDoc "List of IPs to allow (default all allowed).";
+          example = [ "192.168.25.52" "192.168.25.53" "192.168.0.0/24" ];
+          description = lib.mdDoc "List of IPs or networks to allow (default all allowed).";
         };
 
         birdSocket = mkOption {
diff --git a/nixos/modules/services/networking/dhcpcd.nix b/nixos/modules/services/networking/dhcpcd.nix
index 266a7ea1435e..8d5ac02ba88b 100644
--- a/nixos/modules/services/networking/dhcpcd.nix
+++ b/nixos/modules/services/networking/dhcpcd.nix
@@ -13,6 +13,8 @@ let
   enableDHCP = config.networking.dhcpcd.enable &&
         (config.networking.useDHCP || any (i: i.useDHCP == true) interfaces);
 
+  enableNTPService = (config.services.ntp.enable || config.services.ntpd-rs.enable || config.services.openntpd.enable || config.services.chrony.enable);
+
   # Don't start dhcpcd on explicitly configured interfaces or on
   # interfaces that are part of a bridge, bond or sit device.
   ignoredInterfaces =
@@ -89,20 +91,22 @@ let
       ${cfg.extraConfig}
     '';
 
-  exitHook = pkgs.writeText "dhcpcd.exit-hook"
-    ''
+  exitHook = pkgs.writeText "dhcpcd.exit-hook" ''
+    ${optionalString enableNTPService ''
       if [ "$reason" = BOUND -o "$reason" = REBOOT ]; then
-          # Restart ntpd.  We need to restart it to make sure that it
-          # will actually do something: if ntpd cannot resolve the
-          # server hostnames in its config file, then it will never do
-          # anything ever again ("couldn't resolve ..., giving up on
-          # it"), so we silently lose time synchronisation. This also
-          # applies to openntpd.
-          /run/current-system/systemd/bin/systemctl try-reload-or-restart ntpd.service openntpd.service chronyd.service ntpd-rs.service || true
+        # Restart ntpd. We need to restart it to make sure that it will actually do something:
+        # if ntpd cannot resolve the server hostnames in its config file, then it will never do
+        # anything ever again ("couldn't resolve ..., giving up on it"), so we silently lose
+        # time synchronisation. This also applies to openntpd.
+        ${optionalString config.services.ntp.enable "/run/current-system/systemd/bin/systemctl try-reload-or-restart ntpd.service || true"}
+        ${optionalString config.services.ntpd-rs.enable "/run/current-system/systemd/bin/systemctl try-reload-or-restart ntpd-rs.service || true"}
+        ${optionalString config.services.openntpd.enable "/run/current-system/systemd/bin/systemctl try-reload-or-restart openntpd.service || true"}
+        ${optionalString config.services.chrony.enable "/run/current-system/systemd/bin/systemctl try-reload-or-restart chronyd.service || true"}
       fi
+    ''}
 
-      ${cfg.runHook}
-    '';
+    ${cfg.runHook}
+  '';
 
 in
 
@@ -232,7 +236,7 @@ in
         wants = [ "network.target" ];
         before = [ "network-online.target" ];
 
-        restartTriggers = [ exitHook ];
+        restartTriggers = optional (enableNTPService || cfg.runHook != "") [ exitHook ];
 
         # Stopping dhcpcd during a reconfiguration is undesirable
         # because it brings down the network interfaces configured by
@@ -261,7 +265,9 @@ in
 
     environment.systemPackages = [ dhcpcd ];
 
-    environment.etc."dhcpcd.exit-hook".source = exitHook;
+    environment.etc."dhcpcd.exit-hook" = mkIf (enableNTPService || cfg.runHook != "") {
+      source = exitHook;
+    };
 
     powerManagement.resumeCommands = mkIf config.systemd.services.dhcpcd.enable
       ''
diff --git a/nixos/modules/services/networking/go-camo.nix b/nixos/modules/services/networking/go-camo.nix
new file mode 100644
index 000000000000..cb3b6eade464
--- /dev/null
+++ b/nixos/modules/services/networking/go-camo.nix
@@ -0,0 +1,73 @@
+{ lib, pkgs, config, ... }:
+
+let
+  cfg = config.services.go-camo;
+  inherit (lib) mkOption mkEnableOption mkIf mkMerge types optionalString;
+in
+{
+  options.services.go-camo = {
+    enable = mkEnableOption "go-camo service";
+    listen = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = "Address:Port to bind to for HTTP (default: 0.0.0.0:8080).";
+      apply = v: optionalString (v != null) "--listen=${v}";
+    };
+    sslListen = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = "Address:Port to bind to for HTTPS.";
+      apply = v: optionalString (v != null) "--ssl-listen=${v}";
+    };
+    sslKey = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = "Path to TLS private key.";
+      apply = v: optionalString (v != null) "--ssl-key=${v}";
+    };
+    sslCert = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = "Path to TLS certificate.";
+      apply = v: optionalString (v != null) "--ssl-cert=${v}";
+    };
+    keyFile = mkOption {
+      type = types.path;
+      default = null;
+      description = ''
+        A file containing the HMAC key to use for signing URLs.
+        The file can contain any string. Can be generated using "openssl rand -base64 18 > the_file".
+      '';
+    };
+    extraOptions = mkOption {
+      type = with types; listOf str;
+      default = [];
+      description = "Extra options passed to the go-camo command.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.go-camo = {
+      description = "go-camo service";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      environment = {
+        GOCAMO_HMAC_FILE = "%d/hmac";
+      };
+      script = ''
+        export GOCAMO_HMAC=$(cat "$GOCAMO_HMAC_FILE")
+        exec ${lib.escapeShellArgs(lib.lists.remove "" ([ "${pkgs.go-camo}/bin/go-camo" cfg.listen cfg.sslListen cfg.sslKey cfg.sslCert ] ++ cfg.extraOptions))}
+      '';
+      serviceConfig = {
+        NoNewPrivileges = true;
+        ProtectSystem = "strict";
+        DynamicUser = true;
+        User = "gocamo";
+        Group = "gocamo";
+        LoadCredential = [
+          "hmac:${cfg.keyFile}"
+        ];
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/mosquitto.nix b/nixos/modules/services/networking/mosquitto.nix
index ad9eefb42252..4a08f5ed2370 100644
--- a/nixos/modules/services/networking/mosquitto.nix
+++ b/nixos/modules/services/networking/mosquitto.nix
@@ -177,17 +177,6 @@ let
            ''
            ++ hashedLines));
 
-  makeACLFile = idx: users: supplement:
-    pkgs.writeText "mosquitto-acl-${toString idx}.conf"
-      (concatStringsSep
-        "\n"
-        (flatten [
-          supplement
-          (mapAttrsToList
-            (n: u: [ "user ${n}" ] ++ map (t: "topic ${t}") u.acl)
-            users)
-        ]));
-
   authPluginOptions = with types; submodule {
     options = {
       plugin = mkOption {
@@ -342,7 +331,7 @@ let
   formatListener = idx: listener:
     [
       "listener ${toString listener.port} ${toString listener.address}"
-      "acl_file ${makeACLFile idx listener.users listener.acl}"
+      "acl_file /etc/mosquitto/acl-${toString idx}.conf"
     ]
     ++ optional (! listener.omitPasswordAuth) "password_file ${cfg.dataDir}/passwd-${toString idx}"
     ++ formatFreeform {} listener.settings
@@ -698,6 +687,27 @@ in
             cfg.listeners);
     };
 
+    environment.etc = listToAttrs (
+      imap0
+        (idx: listener: {
+          name = "mosquitto/acl-${toString idx}.conf";
+          value = {
+            user = config.users.users.mosquitto.name;
+            group = config.users.users.mosquitto.group;
+            mode = "0400";
+            text = (concatStringsSep
+              "\n"
+              (flatten [
+                listener.acl
+                (mapAttrsToList
+                  (n: u: [ "user ${n}" ] ++ map (t: "topic ${t}") u.acl)
+                  listener.users)
+              ]));
+          };
+        })
+        cfg.listeners
+    );
+
     users.users.mosquitto = {
       description = "Mosquitto MQTT Broker Daemon owner";
       group = "mosquitto";
diff --git a/nixos/modules/services/networking/sabnzbd.nix b/nixos/modules/services/networking/sabnzbd.nix
index cff2622b38e9..2f0d17ad3d17 100644
--- a/nixos/modules/services/networking/sabnzbd.nix
+++ b/nixos/modules/services/networking/sabnzbd.nix
@@ -36,6 +36,14 @@ in
         default = "sabnzbd";
         description = lib.mdDoc "Group to run the service as";
       };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Open ports in the firewall for the sabnzbd web interface
+        '';
+      };
     };
   };
 
@@ -43,17 +51,16 @@ in
   ###### implementation
 
   config = mkIf cfg.enable {
-
-    users.users.sabnzbd = {
-          uid = config.ids.uids.sabnzbd;
-          group = "sabnzbd";
-          description = "sabnzbd user";
-          home = "/var/lib/sabnzbd/";
-          createHome = true;
+    users.users = mkIf (cfg.user == "sabnzbd") {
+      sabnzbd = {
+        uid = config.ids.uids.sabnzbd;
+        group = cfg.group;
+        description = "sabnzbd user";
+      };
     };
 
-    users.groups.sabnzbd = {
-      gid = config.ids.gids.sabnzbd;
+    users.groups = mkIf (cfg.group == "sabnzbd") {
+      sabnzbd.gid = config.ids.gids.sabnzbd;
     };
 
     systemd.services.sabnzbd = {
@@ -63,10 +70,15 @@ in
         serviceConfig = {
           Type = "forking";
           GuessMainPID = "no";
-          User = "${cfg.user}";
-          Group = "${cfg.group}";
+          User = cfg.user;
+          Group = cfg.group;
+          StateDirectory = "sabnzbd";
           ExecStart = "${lib.getBin cfg.package}/bin/sabnzbd -d -f ${cfg.configFile}";
         };
     };
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ 8080 ];
+    };
   };
 }
diff --git a/nixos/modules/services/networking/searx.nix b/nixos/modules/services/networking/searx.nix
index 938d585e3179..5bbf875f0d57 100644
--- a/nixos/modules/services/networking/searx.nix
+++ b/nixos/modules/services/networking/searx.nix
@@ -213,7 +213,7 @@ in
       serviceConfig = {
         User  = "searx";
         Group = "searx";
-        ExecStart = "${cfg.package}/bin/searx-run";
+        ExecStart = lib.getExe cfg.package;
       } // optionalAttrs (cfg.environmentFile != null)
         { EnvironmentFile = builtins.toPath cfg.environmentFile; };
       environment = {
diff --git a/nixos/modules/services/networking/tailscale.nix b/nixos/modules/services/networking/tailscale.nix
index f11fe57d6ce5..972299a4697a 100644
--- a/nixos/modules/services/networking/tailscale.nix
+++ b/nixos/modules/services/networking/tailscale.nix
@@ -66,6 +66,13 @@ in {
       default = [];
       example = ["--ssh"];
     };
+
+    extraDaemonFlags = mkOption {
+      description = lib.mdDoc "Extra flags to pass to {command}`tailscaled`.";
+      type = types.listOf types.str;
+      default = [];
+      example = ["--no-logs-no-support"];
+    };
   };
 
   config = mkIf cfg.enable {
@@ -80,7 +87,7 @@ in {
       ] ++ lib.optional config.networking.resolvconf.enable config.networking.resolvconf.package;
       serviceConfig.Environment = [
         "PORT=${toString cfg.port}"
-        ''"FLAGS=--tun ${lib.escapeShellArg cfg.interfaceName}"''
+        ''"FLAGS=--tun ${lib.escapeShellArg cfg.interfaceName} ${lib.concatStringsSep " " cfg.extraDaemonFlags}"''
       ] ++ (lib.optionals (cfg.permitCertUid != null) [
         "TS_PERMIT_CERT_UID=${cfg.permitCertUid}"
       ]);
diff --git a/nixos/modules/services/networking/unbound.nix b/nixos/modules/services/networking/unbound.nix
index 616b32f11797..8438e472e11e 100644
--- a/nixos/modules/services/networking/unbound.nix
+++ b/nixos/modules/services/networking/unbound.nix
@@ -24,12 +24,24 @@ let
   confNoServer = concatStringsSep "\n" ((mapAttrsToList (toConf "") (builtins.removeAttrs cfg.settings [ "server" ])) ++ [""]);
   confServer = concatStringsSep "\n" (mapAttrsToList (toConf "  ") (builtins.removeAttrs cfg.settings.server [ "define-tag" ]));
 
-  confFile = pkgs.writeText "unbound.conf" ''
+  confFileUnchecked = pkgs.writeText "unbound.conf" ''
     server:
     ${optionalString (cfg.settings.server.define-tag != "") (toOption "  " "define-tag" cfg.settings.server.define-tag)}
     ${confServer}
     ${confNoServer}
   '';
+  confFile = if cfg.checkconf then pkgs.runCommandLocal "unbound-checkconf" { } ''
+    cp ${confFileUnchecked} unbound.conf
+
+    # fake stateDir which is not accesible in the sandbox
+    mkdir -p $PWD/state
+    sed -i unbound.conf \
+      -e '/auto-trust-anchor-file/d' \
+      -e "s|${cfg.stateDir}|$PWD/state|"
+    ${cfg.package}/bin/unbound-checkconf unbound.conf
+
+    cp ${confFileUnchecked} $out
+  '' else confFileUnchecked;
 
   rootTrustAnchorFile = "${cfg.stateDir}/root.key";
 
@@ -62,6 +74,17 @@ in {
         description = lib.mdDoc "Directory holding all state for unbound to run.";
       };
 
+      checkconf = mkOption {
+        type = types.bool;
+        default = !cfg.settings ? include;
+        defaultText = "!config.services.unbound.settings ? include";
+        description = lib.mdDoc ''
+          Wether to check the resulting config file with unbound checkconf for syntax errors.
+
+          If settings.include is used, then this options is disabled, as the import can likely not be resolved at build time.
+        '';
+      };
+
       resolveLocalQueries = mkOption {
         type = types.bool;
         default = true;
diff --git a/nixos/modules/services/security/kanidm.nix b/nixos/modules/services/security/kanidm.nix
index c659d93b4087..9d074c3027d0 100644
--- a/nixos/modules/services/security/kanidm.nix
+++ b/nixos/modules/services/security/kanidm.nix
@@ -132,6 +132,28 @@ in
             default = "WriteReplica";
             type = lib.types.enum [ "WriteReplica" "WriteReplicaNoUI" "ReadOnlyReplica" ];
           };
+          online_backup = {
+            path = lib.mkOption {
+              description = lib.mdDoc "Path to the output directory for backups.";
+              type = lib.types.path;
+              default = "/var/lib/kanidm/backups";
+            };
+            schedule = lib.mkOption {
+              description = lib.mdDoc "The schedule for backups in cron format.";
+              type = lib.types.str;
+              default = "00 22 * * *";
+            };
+            versions = lib.mkOption {
+              description = lib.mdDoc ''
+                Number of backups to keep.
+
+                The default is set to `0`, in order to disable backups by default.
+              '';
+              type = lib.types.ints.unsigned;
+              default = 0;
+              example = 7;
+            };
+          };
         };
       };
       default = { };
@@ -233,6 +255,14 @@ in
 
     environment.systemPackages = lib.mkIf cfg.enableClient [ cfg.package ];
 
+    systemd.tmpfiles.settings."10-kanidm" = {
+      ${cfg.serverSettings.online_backup.path}.d = {
+        mode = "0700";
+        user = "kanidm";
+        group = "kanidm";
+      };
+    };
+
     systemd.services.kanidm = lib.mkIf cfg.enableServer {
       description = "kanidm identity management daemon";
       wantedBy = [ "multi-user.target" ];
@@ -253,6 +283,8 @@ in
           BindPaths = [
             # To create the socket
             "/run/kanidmd:/run/kanidmd"
+            # To store backups
+            cfg.serverSettings.online_backup.path
           ];
 
           AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
diff --git a/nixos/modules/services/system/automatic-timezoned.nix b/nixos/modules/services/system/automatic-timezoned.nix
index 8934ed3a7ef2..7d3cd004a7ba 100644
--- a/nixos/modules/services/system/automatic-timezoned.nix
+++ b/nixos/modules/services/system/automatic-timezoned.nix
@@ -50,7 +50,7 @@ in
         serviceConfig = {
           Type = "exec";
           User = "automatic-timezoned";
-          ExecStart = "${cfg.package}/bin/automatic-timezoned --zoneinfo-path=${pkgs.tzdata}/share/zoneinfo/zone1970.tab";
+          ExecStart = "${cfg.package}/bin/automatic-timezoned";
         };
         wantedBy = [ "default.target" ];
       };
diff --git a/nixos/modules/services/torrent/transmission.nix b/nixos/modules/services/torrent/transmission.nix
index 5dd02eb33163..a9fb123b981e 100644
--- a/nixos/modules/services/torrent/transmission.nix
+++ b/nixos/modules/services/torrent/transmission.nix
@@ -74,7 +74,7 @@ in
             description = lib.mdDoc "";
           };
           options.message-level = mkOption {
-            type = types.ints.between 0 3;
+            type = types.ints.between 0 6;
             default = 2;
             description = lib.mdDoc "Set verbosity of transmission messages.";
           };
diff --git a/nixos/modules/services/web-apps/mastodon.nix b/nixos/modules/services/web-apps/mastodon.nix
index aea1c9228676..7fc710c6fcec 100644
--- a/nixos/modules/services/web-apps/mastodon.nix
+++ b/nixos/modules/services/web-apps/mastodon.nix
@@ -4,7 +4,8 @@ let
   cfg = config.services.mastodon;
   opt = options.services.mastodon;
 
-  # We only want to create a database if we're actually going to connect to it.
+  # We only want to create a Redis and PostgreSQL databases if we're actually going to connect to it local.
+  redisActuallyCreateLocally = cfg.redis.createLocally && (cfg.redis.host == "127.0.0.1" || cfg.redis.enableUnixSocket);
   databaseActuallyCreateLocally = cfg.database.createLocally && cfg.database.host == "/run/postgresql";
 
   env = {
@@ -117,11 +118,11 @@ let
       threads = toString (if processCfg.threads == null then cfg.sidekiqThreads else processCfg.threads);
     in {
       after = [ "network.target" "mastodon-init-dirs.service" ]
-        ++ lib.optional cfg.redis.createLocally "redis-mastodon.service"
+        ++ lib.optional redisActuallyCreateLocally "redis-mastodon.service"
         ++ lib.optional databaseActuallyCreateLocally "postgresql.service"
         ++ lib.optional cfg.automaticMigrations "mastodon-init-db.service";
       requires = [ "mastodon-init-dirs.service" ]
-        ++ lib.optional cfg.redis.createLocally "redis-mastodon.service"
+        ++ lib.optional redisActuallyCreateLocally "redis-mastodon.service"
         ++ lib.optional databaseActuallyCreateLocally "postgresql.service"
         ++ lib.optional cfg.automaticMigrations "mastodon-init-db.service";
       description = "Mastodon sidekiq${jobClassLabel}";
@@ -149,11 +150,11 @@ let
         name = "mastodon-streaming-${toString i}";
         value = {
           after = [ "network.target" "mastodon-init-dirs.service" ]
-            ++ lib.optional cfg.redis.createLocally "redis-mastodon.service"
+            ++ lib.optional redisActuallyCreateLocally "redis-mastodon.service"
             ++ lib.optional databaseActuallyCreateLocally "postgresql.service"
             ++ lib.optional cfg.automaticMigrations "mastodon-init-db.service";
           requires = [ "mastodon-init-dirs.service" ]
-            ++ lib.optional cfg.redis.createLocally "redis-mastodon.service"
+            ++ lib.optional redisActuallyCreateLocally "redis-mastodon.service"
             ++ lib.optional databaseActuallyCreateLocally "postgresql.service"
             ++ lib.optional cfg.automaticMigrations "mastodon-init-db.service";
           wantedBy = [ "mastodon.target" "mastodon-streaming.target" ];
@@ -410,6 +411,13 @@ in {
           default = 31637;
         };
 
+        passwordFile = lib.mkOption {
+          description = lib.mdDoc "A file containing the password for Redis database.";
+          type = lib.types.nullOr lib.types.path;
+          default = null;
+          example = "/run/keys/mastodon-redis-password";
+        };
+
         enableUnixSocket = lib.mkOption {
           description = lib.mdDoc "Use Unix socket";
           type = lib.types.bool;
@@ -624,6 +632,13 @@ in {
   config = lib.mkIf cfg.enable (lib.mkMerge [{
     assertions = [
       {
+        assertion = redisActuallyCreateLocally -> (!cfg.redis.enableUnixSocket || cfg.redis.passwordFile == null);
+        message = ''
+          <option>services.mastodon.redis.enableUnixSocket</option> needs to be disabled if
+            <option>services.mastodon.redis.passwordFile</option> is used.
+        '';
+      }
+      {
         assertion = databaseActuallyCreateLocally -> (cfg.user == cfg.database.user && cfg.database.user == cfg.database.name);
         message = ''
           For local automatic database provisioning (services.mastodon.database.createLocally == true) with peer
@@ -700,6 +715,8 @@ in {
         OTP_SECRET="$(cat ${cfg.otpSecretFile})"
         VAPID_PRIVATE_KEY="$(cat ${cfg.vapidPrivateKeyFile})"
         VAPID_PUBLIC_KEY="$(cat ${cfg.vapidPublicKeyFile})"
+      '' + lib.optionalString (cfg.redis.passwordFile != null)''
+        REDIS_PASSWORD="$(cat ${cfg.redis.passwordFile})"
       '' + lib.optionalString (cfg.database.passwordFile != null) ''
         DB_PASS="$(cat ${cfg.database.passwordFile})"
       '' + lib.optionalString cfg.smtp.authenticate ''
@@ -762,11 +779,11 @@ in {
 
     systemd.services.mastodon-web = {
       after = [ "network.target" "mastodon-init-dirs.service" ]
-        ++ lib.optional cfg.redis.createLocally "redis-mastodon.service"
+        ++ lib.optional redisActuallyCreateLocally "redis-mastodon.service"
         ++ lib.optional databaseActuallyCreateLocally "postgresql.service"
         ++ lib.optional cfg.automaticMigrations "mastodon-init-db.service";
       requires = [ "mastodon-init-dirs.service" ]
-        ++ lib.optional cfg.redis.createLocally "redis-mastodon.service"
+        ++ lib.optional redisActuallyCreateLocally "redis-mastodon.service"
         ++ lib.optional databaseActuallyCreateLocally "postgresql.service"
         ++ lib.optional cfg.automaticMigrations "mastodon-init-db.service";
       wantedBy = [ "mastodon.target" ];
@@ -847,7 +864,7 @@ in {
       enable = true;
       hostname = lib.mkDefault "${cfg.localDomain}";
     };
-    services.redis.servers.mastodon = lib.mkIf cfg.redis.createLocally (lib.mkMerge [
+    services.redis.servers.mastodon = lib.mkIf redisActuallyCreateLocally (lib.mkMerge [
       {
         enable = true;
       }
diff --git a/nixos/modules/services/web-apps/mealie.nix b/nixos/modules/services/web-apps/mealie.nix
new file mode 100644
index 000000000000..8bb7542c6b56
--- /dev/null
+++ b/nixos/modules/services/web-apps/mealie.nix
@@ -0,0 +1,79 @@
+{ config, lib, pkgs, ...}:
+let
+  cfg = config.services.mealie;
+  pkg = cfg.package;
+in
+{
+  options.services.mealie = {
+    enable = lib.mkEnableOption "Mealie, a recipe manager and meal planner";
+
+    package = lib.mkPackageOption pkgs "mealie" { };
+
+    listenAddress = lib.mkOption {
+      type = lib.types.str;
+      default = "0.0.0.0";
+      description = "Address on which the service should listen.";
+    };
+
+    port = lib.mkOption {
+      type = lib.types.port;
+      default = 9000;
+      description = "Port on which to serve the Mealie service.";
+    };
+
+    settings = lib.mkOption {
+      type = with lib.types; attrsOf anything;
+      default = {};
+      description = lib.mdDoc ''
+        Configuration of the Mealie service.
+
+        See [the mealie documentation](https://nightly.mealie.io/documentation/getting-started/installation/backend-config/) for available options and default values.
+
+        In addition to the official documentation, you can set {env}`MEALIE_LOG_FILE`.
+      '';
+      example = {
+        ALLOW_SIGNUP = "false";
+      };
+    };
+
+    credentialsFile = lib.mkOption {
+      type = with lib.types; nullOr path;
+      default = null;
+      example = "/run/secrets/mealie-credentials.env";
+      description = ''
+        File containing credentials used in mealie such as {env}`POSTGRES_PASSWORD`
+        or sensitive LDAP options.
+
+        Expects the format of an `EnvironmentFile=`, as described by {manpage}`systemd.exec(5)`.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.mealie = {
+      description = "Mealie, a self hosted recipe manager and meal planner";
+
+      after = [ "network-online.target" ];
+      wants = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      environment = {
+        PRODUCTION = "true";
+        ALEMBIC_CONFIG_FILE="${pkg}/config/alembic.ini";
+        API_PORT = toString cfg.port;
+        DATA_DIR = "/var/lib/mealie";
+        CRF_MODEL_PATH = "/var/lib/mealie/model.crfmodel";
+      } // (builtins.mapAttrs (_: val: toString val) cfg.settings);
+
+      serviceConfig = {
+        DynamicUser = true;
+        User = "mealie";
+        ExecStartPre = "${pkg}/libexec/init_db";
+        ExecStart = "${lib.getExe pkg} -b ${cfg.listenAddress}:${builtins.toString cfg.port}";
+        EnvironmentFile = lib.mkIf (cfg.credentialsFile != null) cfg.credentialsFile;
+        StateDirectory = "mealie";
+        StandardOutput="journal";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
index 08f90dcf59d8..5cda4a00a9de 100644
--- a/nixos/modules/services/web-apps/nextcloud.nix
+++ b/nixos/modules/services/web-apps/nextcloud.nix
@@ -45,7 +45,7 @@ let
     };
   };
 
-  webroot = pkgs.runCommand
+  webroot = pkgs.runCommandLocal
     "${cfg.package.name or "nextcloud"}-with-apps"
     { }
     ''
diff --git a/nixos/modules/services/web-apps/photoprism.nix b/nixos/modules/services/web-apps/photoprism.nix
index ccf995fccf3e..39eb7c65c635 100644
--- a/nixos/modules/services/web-apps/photoprism.nix
+++ b/nixos/modules/services/web-apps/photoprism.nix
@@ -104,6 +104,7 @@ in
         StateDirectory = "photoprism";
         WorkingDirectory = "/var/lib/photoprism";
         RuntimeDirectory = "photoprism";
+        ReadWritePaths = [ cfg.originalsPath cfg.importPath cfg.storagePath ];
 
         LoadCredential = lib.optionalString (cfg.passwordFile != null)
           "PHOTOPRISM_ADMIN_PASSWORD:${cfg.passwordFile}";
diff --git a/nixos/modules/services/web-apps/vikunja.nix b/nixos/modules/services/web-apps/vikunja.nix
index b893f2c1f33c..efa9c676d9a5 100644
--- a/nixos/modules/services/web-apps/vikunja.nix
+++ b/nixos/modules/services/web-apps/vikunja.nix
@@ -9,10 +9,13 @@ let
   useMysql = cfg.database.type == "mysql";
   usePostgresql = cfg.database.type == "postgres";
 in {
+  imports = [
+    (mkRemovedOptionModule [ "services" "vikunja" "setupNginx" ] "services.vikunja no longer supports the automatic set up of a nginx virtual host. Set up your own webserver config with a proxy pass to the vikunja service.")
+  ];
+
   options.services.vikunja = with lib; {
     enable = mkEnableOption (lib.mdDoc "vikunja service");
-    package-api = mkPackageOption pkgs "vikunja-api" { };
-    package-frontend = mkPackageOption pkgs "vikunja-frontend" { };
+    package = mkPackageOption pkgs "vikunja" { };
     environmentFiles = mkOption {
       type = types.listOf types.path;
       default = [ ];
@@ -21,25 +24,10 @@ in {
         For example passwords should be set in one of these files.
       '';
     };
-    setupNginx = mkOption {
-      type = types.bool;
-      default = config.services.nginx.enable;
-      defaultText = literalExpression "config.services.nginx.enable";
-      description = lib.mdDoc ''
-        Whether to setup NGINX.
-        Further nginx configuration can be done by changing
-        {option}`services.nginx.virtualHosts.<frontendHostname>`.
-        This does not enable TLS or ACME by default. To enable this, set the
-        {option}`services.nginx.virtualHosts.<frontendHostname>.enableACME` to
-        `true` and if appropriate do the same for
-        {option}`services.nginx.virtualHosts.<frontendHostname>.forceSSL`.
-      '';
-    };
     frontendScheme = mkOption {
       type = types.enum [ "http" "https" ];
       description = lib.mdDoc ''
         Whether the site is available via http or https.
-        This does not configure https or ACME in nginx!
       '';
     };
     frontendHostname = mkOption {
@@ -104,42 +92,27 @@ in {
       };
     };
 
-    systemd.services.vikunja-api = {
-      description = "vikunja-api";
+    systemd.services.vikunja = {
+      description = "vikunja";
       after = [ "network.target" ] ++ lib.optional usePostgresql "postgresql.service" ++ lib.optional useMysql "mysql.service";
       wantedBy = [ "multi-user.target" ];
-      path = [ cfg.package-api ];
+      path = [ cfg.package ];
       restartTriggers = [ configFile ];
 
       serviceConfig = {
         Type = "simple";
         DynamicUser = true;
         StateDirectory = "vikunja";
-        ExecStart = "${cfg.package-api}/bin/vikunja";
+        ExecStart = "${cfg.package}/bin/vikunja";
         Restart = "always";
         EnvironmentFile = cfg.environmentFiles;
       };
     };
 
-    services.nginx.virtualHosts."${cfg.frontendHostname}" = mkIf cfg.setupNginx {
-      locations = {
-        "/" = {
-          root = cfg.package-frontend;
-          tryFiles = "try_files $uri $uri/ /";
-        };
-        "~* ^/(api|dav|\\.well-known)/" = {
-          proxyPass = "http://localhost:${toString cfg.port}";
-          extraConfig = ''
-            client_max_body_size 20M;
-          '';
-        };
-      };
-    };
-
     environment.etc."vikunja/config.yaml".source = configFile;
 
     environment.systemPackages = [
-      cfg.package-api # for admin `vikunja` CLI
+      cfg.package # for admin `vikunja` CLI
     ];
   };
 }
diff --git a/nixos/modules/services/web-servers/zope2.nix b/nixos/modules/services/web-servers/zope2.nix
deleted file mode 100644
index 29731b29eea4..000000000000
--- a/nixos/modules/services/web-servers/zope2.nix
+++ /dev/null
@@ -1,262 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  cfg = config.services.zope2;
-
-  zope2Opts = { name, ... }: {
-    options = {
-
-      name = mkOption {
-        default = "${name}";
-        type = types.str;
-        description = lib.mdDoc "The name of the zope2 instance. If undefined, the name of the attribute set will be used.";
-      };
-
-      threads = mkOption {
-        default = 2;
-        type = types.int;
-        description = lib.mdDoc "Specify the number of threads that Zope's ZServer web server will use to service requests. ";
-      };
-
-      http_address = mkOption {
-        default = "localhost:8080";
-        type = types.str;
-        description = lib.mdDoc "Give a port and address for the HTTP server.";
-      };
-
-      user = mkOption {
-        default = "zope2";
-        type = types.str;
-        description = lib.mdDoc "The name of the effective user for the Zope process.";
-      };
-
-      clientHome = mkOption {
-        default = "/var/lib/zope2/${name}";
-        type = types.path;
-        description = lib.mdDoc "Home directory of zope2 instance.";
-      };
-      extra = mkOption {
-        default =
-          ''
-          <zodb_db main>
-            mount-point /
-            cache-size 30000
-            <blobstorage>
-                blob-dir /var/lib/zope2/${name}/blobstorage
-                <filestorage>
-                path /var/lib/zope2/${name}/filestorage/Data.fs
-                </filestorage>
-            </blobstorage>
-          </zodb_db>
-          '';
-        type = types.lines;
-        description = lib.mdDoc "Extra zope.conf";
-      };
-
-      packages = mkOption {
-        type = types.listOf types.package;
-        description = lib.mdDoc "The list of packages you want to make available to the zope2 instance.";
-      };
-
-    };
-  };
-
-in
-
-{
-
-  ###### interface
-
-  options = {
-
-    services.zope2.instances = mkOption {
-      default = {};
-      type = with types; attrsOf (submodule zope2Opts);
-      example = literalExpression ''
-        {
-          plone01 = {
-            http_address = "127.0.0.1:8080";
-            extra =
-              '''
-              <zodb_db main>
-                mount-point /
-                cache-size 30000
-                <blobstorage>
-                    blob-dir /var/lib/zope2/plone01/blobstorage
-                    <filestorage>
-                    path /var/lib/zope2/plone01/filestorage/Data.fs
-                    </filestorage>
-                </blobstorage>
-              </zodb_db>
-              ''';
-          };
-        }
-      '';
-      description = lib.mdDoc "zope2 instances to be created automatically by the system.";
-    };
-  };
-
-  ###### implementation
-
-  config = mkIf (cfg.instances != {}) {
-
-    users.users.zope2 = {
-      isSystemUser = true;
-      group = "zope2";
-    };
-    users.groups.zope2 = {};
-
-    systemd.services =
-      let
-
-        createZope2Instance = opts: name:
-          let
-            interpreter = pkgs.writeScript "interpreter"
-              ''
-              import sys
-
-              _interactive = True
-              if len(sys.argv) > 1:
-                  _options, _args = __import__("getopt").getopt(sys.argv[1:], 'ic:m:')
-                  _interactive = False
-                  for (_opt, _val) in _options:
-                      if _opt == '-i':
-                          _interactive = True
-                      elif _opt == '-c':
-                          exec _val
-                      elif _opt == '-m':
-                          sys.argv[1:] = _args
-                          _args = []
-                          __import__("runpy").run_module(
-                              _val, {}, "__main__", alter_sys=True)
-
-                  if _args:
-                      sys.argv[:] = _args
-                      __file__ = _args[0]
-                      del _options, _args
-                      execfile(__file__)
-
-              if _interactive:
-                  del _interactive
-                  __import__("code").interact(banner="", local=globals())
-              '';
-            env = pkgs.buildEnv {
-              name = "zope2-${name}-env";
-              paths = [
-                pkgs.python27
-                pkgs.python27Packages.recursive-pth-loader
-                pkgs.python27Packages."plone.recipe.zope2instance"
-              ] ++ attrValues pkgs.python27.modules
-                ++ opts.packages;
-              postBuild =
-                ''
-                echo "#!$out/bin/python" > $out/bin/interpreter
-                cat ${interpreter} >> $out/bin/interpreter
-                '';
-            };
-            conf = pkgs.writeText "zope2-${name}-conf"
-              ''
-              %define INSTANCEHOME ${env}
-              instancehome $INSTANCEHOME
-              %define CLIENTHOME ${opts.clientHome}/${opts.name}
-              clienthome $CLIENTHOME
-
-              debug-mode off
-              security-policy-implementation C
-              verbose-security off
-              default-zpublisher-encoding utf-8
-              zserver-threads ${toString opts.threads}
-              effective-user ${opts.user}
-
-              pid-filename ${opts.clientHome}/${opts.name}/pid
-              lock-filename ${opts.clientHome}/${opts.name}/lock
-              python-check-interval 1000
-              enable-product-installation off
-
-              <environment>
-                zope_i18n_compile_mo_files false
-              </environment>
-
-              <eventlog>
-              level INFO
-              <logfile>
-                  path /var/log/zope2/${name}.log
-                  level INFO
-              </logfile>
-              </eventlog>
-
-              <logger access>
-              level WARN
-              <logfile>
-                  path /var/log/zope2/${name}-Z2.log
-                  format %(message)s
-              </logfile>
-              </logger>
-
-              <http-server>
-              address ${opts.http_address}
-              </http-server>
-
-              <zodb_db temporary>
-              <temporarystorage>
-                  name temporary storage for sessioning
-              </temporarystorage>
-              mount-point /temp_folder
-              container-class Products.TemporaryFolder.TemporaryContainer
-              </zodb_db>
-
-              ${opts.extra}
-              '';
-            ctlScript = pkgs.writeScript "zope2-${name}-ctl-script"
-              ''
-              #!${env}/bin/python
-
-              import sys
-              import plone.recipe.zope2instance.ctl
-
-              if __name__ == '__main__':
-                  sys.exit(plone.recipe.zope2instance.ctl.main(
-                      ["-C", "${conf}"]
-                      + sys.argv[1:]))
-              '';
-
-            ctl = pkgs.writeScript "zope2-${name}-ctl"
-              ''
-              #!${pkgs.bash}/bin/bash -e
-              export PYTHONHOME=${env}
-              exec ${ctlScript} "$@"
-              '';
-          in {
-            #description = "${name} instance";
-            after = [ "network.target" ];  # with RelStorage also add "postgresql.service"
-            wantedBy = [ "multi-user.target" ];
-            path = opts.packages;
-            preStart =
-              ''
-              mkdir -p /var/log/zope2/
-              touch /var/log/zope2/${name}.log
-              touch /var/log/zope2/${name}-Z2.log
-              chown ${opts.user} /var/log/zope2/${name}.log
-              chown ${opts.user} /var/log/zope2/${name}-Z2.log
-
-              mkdir -p ${opts.clientHome}/filestorage ${opts.clientHome}/blobstorage
-              mkdir -p ${opts.clientHome}/${opts.name}
-              chown ${opts.user} ${opts.clientHome} -R
-
-              ${ctl} adduser admin admin
-              '';
-
-            serviceConfig.Type = "forking";
-            serviceConfig.ExecStart = "${ctl} start";
-            serviceConfig.ExecStop = "${ctl} stop";
-            serviceConfig.ExecReload = "${ctl} restart";
-          };
-
-      in listToAttrs (map (name: { name = "zope2-${name}"; value = createZope2Instance (builtins.getAttr name cfg.instances) name; }) (builtins.attrNames cfg.instances));
-
-  };
-
-}
diff --git a/nixos/modules/services/x11/desktop-managers/budgie.nix b/nixos/modules/services/x11/desktop-managers/budgie.nix
index 463c45675cee..fe39097a22e8 100644
--- a/nixos/modules/services/x11/desktop-managers/budgie.nix
+++ b/nixos/modules/services/x11/desktop-managers/budgie.nix
@@ -39,6 +39,10 @@ let
     '';
     destination = "/share/gnome-background-properties/nixos.xml";
   };
+
+  budgie-control-center = pkgs.budgie.budgie-control-center.override {
+    enableSshSocket = config.services.openssh.startWhenNeeded;
+  };
 in {
   options = {
     services.xserver.desktopManager.budgie = {
@@ -114,7 +118,7 @@ in {
       [
         # Budgie Desktop.
         budgie.budgie-backgrounds
-        budgie.budgie-control-center
+        budgie-control-center
         (budgie.budgie-desktop-with-plugins.override { plugins = cfg.extraPlugins; })
         budgie.budgie-desktop-view
         budgie.budgie-screensaver
@@ -233,8 +237,8 @@ in {
     services.gvfs.enable = mkDefault true;
 
     # Register packages for DBus.
-    services.dbus.packages = with pkgs; [
-      budgie.budgie-control-center
+    services.dbus.packages = [
+      budgie-control-center
     ];
 
     # Register packages for udev.
diff --git a/nixos/modules/services/x11/desktop-managers/deepin.nix b/nixos/modules/services/x11/desktop-managers/deepin.nix
index 7d3acada6073..0824d6e30a8a 100644
--- a/nixos/modules/services/x11/desktop-managers/deepin.nix
+++ b/nixos/modules/services/x11/desktop-managers/deepin.nix
@@ -173,19 +173,20 @@ in
           ];
           optionalPackages = [
             onboard # dde-dock plugin
-            deepin-camera
             deepin-calculator
             deepin-compressor
             deepin-editor
             deepin-picker
             deepin-draw
-            deepin-album
-            deepin-image-viewer
             deepin-music
             deepin-movie-reborn
             deepin-system-monitor
-            deepin-screen-recorder
             deepin-shortcut-viewer
+            # freeimage has knownVulnerabilties, don't install packages using freeiamge by default
+            # deepin-album
+            # deepin-camera
+            # deepin-image-viewer
+            # deepin-screen-recorder
           ];
         in
         requiredPackages
diff --git a/nixos/modules/services/x11/desktop-managers/default.nix b/nixos/modules/services/x11/desktop-managers/default.nix
index 66cb4ee29c0a..ecb8d1e91bde 100644
--- a/nixos/modules/services/x11/desktop-managers/default.nix
+++ b/nixos/modules/services/x11/desktop-managers/default.nix
@@ -18,7 +18,7 @@ in
   # determines the default: later modules (if enabled) are preferred.
   # E.g., if Plasma 5 is enabled, it supersedes xterm.
   imports = [
-    ./none.nix ./xterm.nix ./phosh.nix ./xfce.nix ./plasma5.nix ./lumina.nix
+    ./none.nix ./xterm.nix ./phosh.nix ./xfce.nix ./plasma5.nix ./plasma6.nix ./lumina.nix
     ./lxqt.nix ./enlightenment.nix ./gnome.nix ./retroarch.nix ./kodi.nix
     ./mate.nix ./pantheon.nix ./surf-display.nix ./cde.nix
     ./cinnamon.nix ./budgie.nix ./deepin.nix
diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix
index 0eb492ce4684..7645b3070369 100644
--- a/nixos/modules/services/x11/desktop-managers/plasma5.nix
+++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix
@@ -362,7 +362,7 @@ in
 
       security.pam.services.kde = { allowNullPassword = true; };
 
-      security.pam.services.login.enableKwallet = true;
+      security.pam.services.login.kwallet.enable = true;
 
       systemd.user.services = {
         plasma-early-setup = mkIf cfg.runUsingSystemd {
diff --git a/nixos/modules/services/x11/desktop-managers/plasma6.nix b/nixos/modules/services/x11/desktop-managers/plasma6.nix
new file mode 100644
index 000000000000..1237261e0af7
--- /dev/null
+++ b/nixos/modules/services/x11/desktop-managers/plasma6.nix
@@ -0,0 +1,291 @@
+{
+  config,
+  lib,
+  pkgs,
+  utils,
+  ...
+}: let
+  xcfg = config.services.xserver;
+  cfg = xcfg.desktopManager.plasma6;
+
+  inherit (pkgs) kdePackages;
+  inherit (lib) literalExpression mkDefault mkIf mkOption mkPackageOptionMD types;
+
+  activationScript = ''
+    # will be rebuilt automatically
+    rm -fv $HOME/.cache/ksycoca*
+  '';
+in {
+  options = {
+    services.xserver.desktopManager.plasma6 = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc "Enable the Plasma 6 (KDE 6) desktop environment.";
+      };
+
+      enableQt5Integration = mkOption {
+        type = types.bool;
+        default = true;
+        description = lib.mdDoc "Enable Qt 5 integration (theming, etc). Disable for a pure Qt 6 system.";
+      };
+
+      notoPackage = mkPackageOptionMD pkgs "Noto fonts - used for UI by default" {
+        default = ["noto-fonts"];
+        example = "noto-fonts-lgc-plus";
+      };
+    };
+
+    environment.plasma6.excludePackages = mkOption {
+      description = lib.mdDoc "List of default packages to exclude from the configuration";
+      type = types.listOf types.package;
+      default = [];
+      example = literalExpression "[ pkgs.kdePackages.elisa ]";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = cfg.enable -> !config.services.xserver.desktopManager.plasma5.enable;
+        message = "Cannot enable plasma5 and plasma6 at the same time!";
+      }
+    ];
+
+    qt.enable = true;
+    environment.systemPackages = with kdePackages; let
+      requiredPackages = [
+        # Hack? To make everything run on Wayland
+        qtwayland
+        # Needed to render SVG icons
+        qtsvg
+
+        # Frameworks with globally loadable bits
+        frameworkintegration # provides Qt plugin
+        kauth # provides helper service
+        kcoreaddons # provides extra mime type info
+        kded # provides helper service
+        kfilemetadata # provides Qt plugins
+        kguiaddons # provides geo URL handlers
+        kiconthemes # provides Qt plugins
+        kimageformats # provides Qt plugins
+        kio # provides helper service + a bunch of other stuff
+        kpackage # provides kpackagetool tool
+        kservice # provides kbuildsycoca6 tool
+        kwallet # provides helper service
+        kwallet-pam # provides helper service
+        kwalletmanager # provides KCMs and stuff
+        plasma-activities # provides plasma-activities-cli tool
+        solid # provides solid-hardware6 tool
+        phonon-vlc # provides Phonon plugin
+
+        # Core Plasma parts
+        kwin
+        pkgs.xwayland
+
+        kscreen
+        libkscreen
+
+        kscreenlocker
+
+        kactivitymanagerd
+        kde-cli-tools
+        kglobalacceld
+        kwrited # wall message proxy, not to be confused with kwrite
+
+        milou
+        polkit-kde-agent-1
+
+        plasma-desktop
+        plasma-workspace
+
+        # Crash handler
+        drkonqi
+
+        # Application integration
+        libplasma # provides Kirigami platform theme
+        plasma-integration # provides Qt platform theme
+        kde-gtk-config
+
+        # Artwork + themes
+        breeze
+        breeze-icons
+        breeze-gtk
+        ocean-sound-theme
+        plasma-workspace-wallpapers
+        pkgs.hicolor-icon-theme # fallback icons
+        qqc2-breeze-style
+        qqc2-desktop-style
+
+        # misc Plasma extras
+        kdeplasma-addons
+
+        pkgs.xdg-user-dirs # recommended upstream
+
+        # Plasma utilities
+        kmenuedit
+
+        kinfocenter
+        plasma-systemmonitor
+        ksystemstats
+        libksysguard
+
+        spectacle
+        systemsettings
+        kcmutils
+
+        # Gear
+        baloo
+        dolphin
+        dolphin-plugins
+        ffmpegthumbs
+        kdegraphics-thumbnailers
+        kde-inotify-survey
+        kio-admin
+        kio-extras
+        kio-fuse
+      ];
+      optionalPackages = [
+        plasma-browser-integration
+        konsole
+        (lib.getBin qttools) # Expose qdbus in PATH
+
+        ark
+        elisa
+        gwenview
+        okular
+        kate
+        khelpcenter
+        print-manager
+      ];
+    in
+      requiredPackages
+      ++ utils.removePackagesByName optionalPackages config.environment.plasma6.excludePackages
+      ++ lib.optionals config.services.xserver.desktopManager.plasma6.enableQt5Integration [
+        breeze.qt5
+        plasma-integration.qt5
+        pkgs.plasma5Packages.kwayland-integration
+        kio-extras-kf5
+      ]
+      # Optional hardware support features
+      ++ lib.optionals config.hardware.bluetooth.enable [bluedevil bluez-qt pkgs.openobex pkgs.obexftp]
+      ++ lib.optional config.networking.networkmanager.enable plasma-nm
+      ++ lib.optional config.hardware.pulseaudio.enable plasma-pa
+      ++ lib.optional config.services.pipewire.pulse.enable plasma-pa
+      ++ lib.optional config.powerManagement.enable powerdevil
+      ++ lib.optional config.services.colord.enable colord-kde
+      ++ lib.optional config.services.hardware.bolt.enable plasma-thunderbolt
+      ++ lib.optionals config.services.samba.enable [kdenetwork-filesharing pkgs.samba]
+      ++ lib.optional config.services.xserver.wacom.enable wacomtablet
+      ++ lib.optional config.services.flatpak.enable flatpak-kcm;
+
+    environment.pathsToLink = [
+      # FIXME: modules should link subdirs of `/share` rather than relying on this
+      "/share"
+      "/libexec" # for drkonqi
+    ];
+
+    environment.etc."X11/xkb".source = xcfg.xkb.dir;
+
+    # Add ~/.config/kdedefaults to XDG_CONFIG_DIRS for shells, since Plasma sets that.
+    # FIXME: maybe we should append to XDG_CONFIG_DIRS in /etc/set-environment instead?
+    environment.sessionVariables.XDG_CONFIG_DIRS = ["$HOME/.config/kdedefaults"];
+
+    # Needed for things that depend on other store.kde.org packages to install correctly,
+    # notably Plasma look-and-feel packages (a.k.a. Global Themes)
+    #
+    # FIXME: this is annoyingly impure and should really be fixed at source level somehow,
+    # but kpackage is a library so we can't just wrap the one thing invoking it and be done.
+    # This also means things won't work for people not on Plasma, but at least this way it
+    # works for SOME people.
+    environment.sessionVariables.KPACKAGE_DEP_RESOLVERS_PATH = "${kdePackages.frameworkintegration.out}/libexec/kf6/kpackagehandlers";
+
+    # Enable GTK applications to load SVG icons
+    services.xserver.gdk-pixbuf.modulePackages = [pkgs.librsvg];
+
+    fonts.packages = [cfg.notoPackage pkgs.hack-font];
+    fonts.fontconfig.defaultFonts = {
+      monospace = ["Hack" "Noto Sans Mono"];
+      sansSerif = ["Noto Sans"];
+      serif = ["Noto Serif"];
+    };
+
+    programs.ssh.askPassword = mkDefault "${kdePackages.ksshaskpass.out}/bin/ksshaskpass";
+
+    # Enable helpful DBus services.
+    services.accounts-daemon.enable = true;
+    # when changing an account picture the accounts-daemon reads a temporary file containing the image which systemsettings5 may place under /tmp
+    systemd.services.accounts-daemon.serviceConfig.PrivateTmp = false;
+
+    services.power-profiles-daemon.enable = mkDefault true;
+    services.system-config-printer.enable = mkIf config.services.printing.enable (mkDefault true);
+    services.udisks2.enable = true;
+    services.upower.enable = config.powerManagement.enable;
+    services.xserver.libinput.enable = mkDefault true;
+
+    # Extra UDEV rules used by Solid
+    services.udev.packages = [
+      # libmtp has "bin", "dev", "out" outputs. UDEV rules file is in "out".
+      pkgs.libmtp.out
+      pkgs.media-player-info
+    ];
+
+    # Set up Dr. Konqi as crash handler
+    systemd.packages = [kdePackages.drkonqi];
+    systemd.services."drkonqi-coredump-processor@".wantedBy = ["systemd-coredump@.service"];
+
+    xdg.portal.enable = true;
+    xdg.portal.extraPortals = [kdePackages.xdg-desktop-portal-kde];
+    xdg.portal.configPackages = mkDefault [kdePackages.xdg-desktop-portal-kde];
+    services.pipewire.enable = mkDefault true;
+
+    services.xserver.displayManager = {
+      sessionPackages = [kdePackages.plasma-workspace];
+      defaultSession = mkDefault "plasma";
+    };
+    services.xserver.displayManager.sddm = {
+      package = kdePackages.sddm;
+      theme = mkDefault "breeze";
+      extraPackages = with kdePackages; [
+        breeze-icons
+        kirigami
+        plasma5support
+        qtsvg
+        qtvirtualkeyboard
+      ];
+    };
+
+    security.pam.services = {
+      login.kwallet = {
+        enable = true;
+        package = kdePackages.kwallet-pam;
+      };
+      kde.kwallet = {
+        enable = true;
+        package = kdePackages.kwallet-pam;
+      };
+      kde-fingerprint = lib.mkIf config.services.fprintd.enable { fprintAuth = true; };
+      kde-smartcard = lib.mkIf config.security.pam.p11.enable { p11Auth = true; };
+    };
+
+    programs.dconf.enable = true;
+
+    programs.firefox.nativeMessagingHosts.packages = [kdePackages.plasma-browser-integration];
+
+    programs.chromium = {
+      enablePlasmaBrowserIntegration = true;
+      plasmaBrowserIntegrationPackage = pkgs.kdePackages.plasma-browser-integration;
+    };
+
+    programs.kdeconnect.package = kdePackages.kdeconnect-kde;
+
+    # FIXME: ugly hack. See #292632 for details.
+    system.userActivationScripts.rebuildSycoca = activationScript;
+    systemd.user.services.nixos-rebuild-sycoca = {
+      description = "Rebuild KDE system configuration cache";
+      wantedBy = [ "graphical-session-pre.target" ];
+      serviceConfig.Type = "oneshot";
+      script = activationScript;
+    };
+  };
+}
diff --git a/nixos/modules/services/x11/display-managers/sddm.nix b/nixos/modules/services/x11/display-managers/sddm.nix
index 0576619cc8d2..5b7f4bc58d80 100644
--- a/nixos/modules/services/x11/display-managers/sddm.nix
+++ b/nixos/modules/services/x11/display-managers/sddm.nix
@@ -7,7 +7,10 @@ let
   cfg = dmcfg.sddm;
   xEnv = config.systemd.services.display-manager.environment;
 
-  sddm = cfg.package;
+  sddm = cfg.package.override(old: {
+    withWayland = cfg.wayland.enable;
+    extraPackages = old.extraPackages or [] ++ cfg.extraPackages;
+  });
 
   iniFmt = pkgs.formats.ini { };
 
@@ -140,6 +143,15 @@ in
         '';
       };
 
+      extraPackages = mkOption {
+        type = types.listOf types.package;
+        default = [];
+        defaultText = "[]";
+        description = lib.mdDoc ''
+          Extra Qt plugins / QML libraries to add to the environment.
+        '';
+      };
+
       autoNumlock = mkOption {
         type = types.bool;
         default = false;
@@ -211,7 +223,7 @@ in
                 keymap_variant = xcfg.xkb.variant;
                 keymap_options = xcfg.xkb.options;
               };
-            }; in "${pkgs.weston}/bin/weston --shell=fullscreen-shell.so -c ${westonIni}";
+            }; in "${pkgs.weston}/bin/weston --shell=kiosk -c ${westonIni}";
           description = lib.mdDoc "Command used to start the selected compositor";
         };
       };
@@ -235,15 +247,7 @@ in
       }
     ];
 
-    services.xserver.displayManager.job = {
-      environment = {
-        # Load themes from system environment
-        QT_PLUGIN_PATH = "/run/current-system/sw/" + pkgs.qt5.qtbase.qtPluginPrefix;
-        QML2_IMPORT_PATH = "/run/current-system/sw/" + pkgs.qt5.qtbase.qtQmlPrefix;
-      };
-
-      execCmd = "exec /run/current-system/sw/bin/sddm";
-    };
+    services.xserver.displayManager.job.execCmd = "exec /run/current-system/sw/bin/sddm";
 
     security.pam.services = {
       sddm.text = ''
diff --git a/nixos/modules/system/boot/kernel.nix b/nixos/modules/system/boot/kernel.nix
index a46331ccd431..b0ac857feb4b 100644
--- a/nixos/modules/system/boot/kernel.nix
+++ b/nixos/modules/system/boot/kernel.nix
@@ -81,6 +81,13 @@ in
             extraStructuredConfig.FOO = lib.kernel.yes;
             features.foo = true;
           }
+          {
+            name = "foo-ml-mbox";
+            patch = (fetchurl {
+              url = "https://lore.kernel.org/lkml/19700205182810.58382-1-email@domain/t.mbox.gz";
+              hash = "sha256-...";
+            });
+          }
         ]
       '';
       description = lib.mdDoc ''
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 a9978d7adf80..03bff1dee5b9 100644
--- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
@@ -17,6 +17,9 @@ from dataclasses import dataclass
 
 # These values will be replaced with actual values during the package build
 EFI_SYS_MOUNT_POINT = "@efiSysMountPoint@"
+BOOT_MOUNT_POINT = "@bootMountPoint@"
+LOADER_CONF = f"{EFI_SYS_MOUNT_POINT}/loader/loader.conf"  # Always stored on the ESP
+NIXOS_DIR = "@nixosDir@"
 TIMEOUT = "@timeout@"
 EDITOR = "@editor@" == "1"
 CONSOLE_MODE = "@consoleMode@"
@@ -28,6 +31,7 @@ CONFIGURATION_LIMIT = int("@configurationLimit@")
 CAN_TOUCH_EFI_VARIABLES = "@canTouchEfiVariables@"
 GRACEFUL = "@graceful@"
 COPY_EXTRA_FILES = "@copyExtraFiles@"
+CHECK_MOUNTPOINTS = "@checkMountpoints@"
 
 @dataclass
 class BootSpec:
@@ -39,6 +43,7 @@ class BootSpec:
     system: str
     toplevel: str
     specialisations: Dict[str, "BootSpec"]
+    sortKey: str
     initrdSecrets: str | None = None
 
 
@@ -69,6 +74,7 @@ def system_dir(profile: str | None, generation: int, specialisation: str | None)
         return d
 
 BOOT_ENTRY = """title {title}
+sort-key {sort_key}
 version Generation {generation} {description}
 linux {kernel}
 initrd {initrd}
@@ -87,7 +93,7 @@ def generation_conf_filename(profile: str | None, generation: int, specialisatio
 
 
 def write_loader_conf(profile: str | None, generation: int, specialisation: str | None) -> None:
-    with open(f"{EFI_SYS_MOUNT_POINT}/loader/loader.conf.tmp", 'w') as f:
+    with open(f"{LOADER_CONF}.tmp", 'w') as f:
         if TIMEOUT != "":
             f.write(f"timeout {TIMEOUT}\n")
         f.write("default %s\n" % generation_conf_filename(profile, generation, specialisation))
@@ -96,7 +102,7 @@ def write_loader_conf(profile: str | None, generation: int, specialisation: str
         f.write(f"console-mode {CONSOLE_MODE}\n")
         f.flush()
         os.fsync(f.fileno())
-    os.rename(f"{EFI_SYS_MOUNT_POINT}/loader/loader.conf.tmp", f"{EFI_SYS_MOUNT_POINT}/loader/loader.conf")
+    os.rename(f"{LOADER_CONF}.tmp", LOADER_CONF)
 
 
 def get_bootspec(profile: str | None, generation: int) -> BootSpec:
@@ -119,16 +125,22 @@ def get_bootspec(profile: str | None, generation: int) -> BootSpec:
 def bootspec_from_json(bootspec_json: Dict) -> BootSpec:
     specialisations = bootspec_json['org.nixos.specialisation.v1']
     specialisations = {k: bootspec_from_json(v) for k, v in specialisations.items()}
-    return BootSpec(**bootspec_json['org.nixos.bootspec.v1'], specialisations=specialisations)
+    systemdBootExtension = bootspec_json.get('org.nixos.systemd-boot', {})
+    sortKey = systemdBootExtension.get('sortKey', 'nixos')
+    return BootSpec(
+        **bootspec_json['org.nixos.bootspec.v1'],
+        specialisations=specialisations,
+        sortKey=sortKey
+    )
 
 
 def copy_from_file(file: str, dry_run: bool = False) -> str:
     store_file_path = os.path.realpath(file)
     suffix = os.path.basename(store_file_path)
     store_dir = os.path.basename(os.path.dirname(store_file_path))
-    efi_file_path = "/efi/nixos/%s-%s.efi" % (store_dir, suffix)
+    efi_file_path = f"{NIXOS_DIR}/{store_dir}-{suffix}.efi"
     if not dry_run:
-        copy_if_not_exists(store_file_path, f"{EFI_SYS_MOUNT_POINT}%s" % (efi_file_path))
+        copy_if_not_exists(store_file_path, f"{BOOT_MOUNT_POINT}{efi_file_path}")
     return efi_file_path
 
 def write_entry(profile: str | None, generation: int, specialisation: str | None,
@@ -145,7 +157,7 @@ def write_entry(profile: str | None, generation: int, specialisation: str | None
 
     try:
         if bootspec.initrdSecrets is not None:
-            subprocess.check_call([bootspec.initrdSecrets, f"{EFI_SYS_MOUNT_POINT}%s" % (initrd)])
+            subprocess.check_call([bootspec.initrdSecrets, f"{BOOT_MOUNT_POINT}%s" % (initrd)])
     except subprocess.CalledProcessError:
         if current:
             print("failed to create initrd secrets!", file=sys.stderr)
@@ -155,7 +167,7 @@ def write_entry(profile: str | None, generation: int, specialisation: str | None
                   f'for "{title} - Configuration {generation}", an older generation', file=sys.stderr)
             print("note: this is normal after having removed "
                   "or renamed a file in `boot.initrd.secrets`", file=sys.stderr)
-    entry_file = f"{EFI_SYS_MOUNT_POINT}/loader/entries/%s" % (
+    entry_file = f"{BOOT_MOUNT_POINT}/loader/entries/%s" % (
         generation_conf_filename(profile, generation, specialisation))
     tmp_path = "%s.tmp" % (entry_file)
     kernel_params = "init=%s " % bootspec.init
@@ -166,6 +178,7 @@ def write_entry(profile: str | None, generation: int, specialisation: str | None
 
     with open(tmp_path, 'w') as f:
         f.write(BOOT_ENTRY.format(title=title,
+                    sort_key=bootspec.sortKey,
                     generation=generation,
                     kernel=kernel,
                     initrd=initrd,
@@ -202,14 +215,14 @@ def get_generations(profile: str | None = None) -> list[SystemIdentifier]:
 
 
 def remove_old_entries(gens: list[SystemIdentifier]) -> None:
-    rex_profile = re.compile(r"^" + re.escape(EFI_SYS_MOUNT_POINT) + "/loader/entries/nixos-(.*)-generation-.*\.conf$")
-    rex_generation = re.compile(r"^" + re.escape(EFI_SYS_MOUNT_POINT) + "/loader/entries/nixos.*-generation-([0-9]+)(-specialisation-.*)?\.conf$")
+    rex_profile = re.compile(r"^" + re.escape(BOOT_MOUNT_POINT) + "/loader/entries/nixos-(.*)-generation-.*\.conf$")
+    rex_generation = re.compile(r"^" + re.escape(BOOT_MOUNT_POINT) + "/loader/entries/nixos.*-generation-([0-9]+)(-specialisation-.*)?\.conf$")
     known_paths = []
     for gen in gens:
         bootspec = get_bootspec(gen.profile, gen.generation)
         known_paths.append(copy_from_file(bootspec.kernel, True))
         known_paths.append(copy_from_file(bootspec.initrd, True))
-    for path in glob.iglob(f"{EFI_SYS_MOUNT_POINT}/loader/entries/nixos*-generation-[1-9]*.conf"):
+    for path in glob.iglob(f"{BOOT_MOUNT_POINT}/loader/entries/nixos*-generation-[1-9]*.conf"):
         if rex_profile.match(path):
             prof = rex_profile.sub(r"\1", path)
         else:
@@ -220,11 +233,18 @@ def remove_old_entries(gens: list[SystemIdentifier]) -> None:
             continue
         if not (prof, gen_number, None) in gens:
             os.unlink(path)
-    for path in glob.iglob(f"{EFI_SYS_MOUNT_POINT}/efi/nixos/*"):
+    for path in glob.iglob(f"{BOOT_MOUNT_POINT}/{NIXOS_DIR}/*"):
         if not path in known_paths and not os.path.isdir(path):
             os.unlink(path)
 
 
+def cleanup_esp() -> None:
+    for path in glob.iglob(f"{EFI_SYS_MOUNT_POINT}/loader/entries/nixos*"):
+        os.unlink(path)
+    if os.path.isdir(f"{EFI_SYS_MOUNT_POINT}/{NIXOS_DIR}"):
+        shutil.rmtree(f"{EFI_SYS_MOUNT_POINT}/{NIXOS_DIR}")
+
+
 def get_profiles() -> list[str]:
     if os.path.isdir("/nix/var/nix/profiles/system-profiles/"):
         return [x
@@ -255,6 +275,9 @@ def install_bootloader(args: argparse.Namespace) -> None:
     # flags to pass to bootctl install/update
     bootctl_flags = []
 
+    if BOOT_MOUNT_POINT != EFI_SYS_MOUNT_POINT:
+        bootctl_flags.append(f"--boot-path={BOOT_MOUNT_POINT}")
+
     if CAN_TOUCH_EFI_VARIABLES != "1":
         bootctl_flags.append("--no-variables")
 
@@ -263,8 +286,8 @@ def install_bootloader(args: argparse.Namespace) -> None:
 
     if os.getenv("NIXOS_INSTALL_BOOTLOADER") == "1":
         # bootctl uses fopen() with modes "wxe" and fails if the file exists.
-        if os.path.exists(f"{EFI_SYS_MOUNT_POINT}/loader/loader.conf"):
-            os.unlink(f"{EFI_SYS_MOUNT_POINT}/loader/loader.conf")
+        if os.path.exists(LOADER_CONF):
+            os.unlink(LOADER_CONF)
 
         subprocess.check_call([f"{SYSTEMD}/bin/bootctl", f"--esp-path={EFI_SYS_MOUNT_POINT}"] + bootctl_flags + ["install"])
     else:
@@ -291,13 +314,15 @@ def install_bootloader(args: argparse.Namespace) -> None:
             print("updating systemd-boot from %s to %s" % (installed_version, available_version))
             subprocess.check_call([f"{SYSTEMD}/bin/bootctl", f"--esp-path={EFI_SYS_MOUNT_POINT}"] + bootctl_flags + ["update"])
 
-    os.makedirs(f"{EFI_SYS_MOUNT_POINT}/efi/nixos", exist_ok=True)
-    os.makedirs(f"{EFI_SYS_MOUNT_POINT}/loader/entries", exist_ok=True)
+    os.makedirs(f"{BOOT_MOUNT_POINT}/{NIXOS_DIR}", exist_ok=True)
+    os.makedirs(f"{BOOT_MOUNT_POINT}/loader/entries", exist_ok=True)
 
     gens = get_generations()
     for profile in get_profiles():
         gens += get_generations(profile)
+
     remove_old_entries(gens)
+
     for gen in gens:
         try:
             bootspec = get_bootspec(gen.profile, gen.generation)
@@ -315,9 +340,15 @@ def install_bootloader(args: argparse.Namespace) -> None:
             else:
                 raise e
 
-    for root, _, files in os.walk(f"{EFI_SYS_MOUNT_POINT}/efi/nixos/.extra-files", topdown=False):
-        relative_root = root.removeprefix(f"{EFI_SYS_MOUNT_POINT}/efi/nixos/.extra-files").removeprefix("/")
-        actual_root = os.path.join(f"{EFI_SYS_MOUNT_POINT}", relative_root)
+    if BOOT_MOUNT_POINT != EFI_SYS_MOUNT_POINT:
+        # Cleanup any entries in ESP if xbootldrMountPoint is set.
+        # If the user later unsets xbootldrMountPoint, entries in XBOOTLDR will not be cleaned up
+        # automatically, as we don't have information about the mount point anymore.
+        cleanup_esp()
+
+    for root, _, files in os.walk(f"{BOOT_MOUNT_POINT}/{NIXOS_DIR}/.extra-files", topdown=False):
+        relative_root = root.removeprefix(f"{BOOT_MOUNT_POINT}/{NIXOS_DIR}/.extra-files").removeprefix("/")
+        actual_root = os.path.join(f"{BOOT_MOUNT_POINT}", relative_root)
 
         for file in files:
             actual_file = os.path.join(actual_root, file)
@@ -330,7 +361,7 @@ def install_bootloader(args: argparse.Namespace) -> None:
             os.rmdir(actual_root)
         os.rmdir(root)
 
-    os.makedirs(f"{EFI_SYS_MOUNT_POINT}/efi/nixos/.extra-files", exist_ok=True)
+    os.makedirs(f"{BOOT_MOUNT_POINT}/{NIXOS_DIR}/.extra-files", exist_ok=True)
 
     subprocess.check_call(COPY_EXTRA_FILES)
 
@@ -340,6 +371,8 @@ def main() -> None:
     parser.add_argument('default_config', metavar='DEFAULT-CONFIG', help=f"The default {DISTRO_NAME} config to boot")
     args = parser.parse_args()
 
+    subprocess.check_call(CHECK_MOUNTPOINTS)
+
     try:
         install_bootloader(args)
     finally:
@@ -347,9 +380,14 @@ def main() -> None:
         # it can leave the system in an unbootable state, when a crash/outage
         # happens shortly after an update. To decrease the likelihood of this
         # event sync the efi filesystem after each update.
-        rc = libc.syncfs(os.open(f"{EFI_SYS_MOUNT_POINT}", os.O_RDONLY))
+        rc = libc.syncfs(os.open(f"{BOOT_MOUNT_POINT}", os.O_RDONLY))
         if rc != 0:
-            print(f"could not sync {EFI_SYS_MOUNT_POINT}: {os.strerror(rc)}", file=sys.stderr)
+            print(f"could not sync {BOOT_MOUNT_POINT}: {os.strerror(rc)}", file=sys.stderr)
+
+        if BOOT_MOUNT_POINT != EFI_SYS_MOUNT_POINT:
+            rc = libc.syncfs(os.open(EFI_SYS_MOUNT_POINT, os.O_RDONLY))
+            if rc != 0:
+                print(f"could not sync {EFI_SYS_MOUNT_POINT}: {os.strerror(rc)}", file=sys.stderr)
 
 
 if __name__ == '__main__':
diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
index ea4553b8208f..ba07506266e2 100644
--- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
@@ -7,7 +7,7 @@ let
 
   efi = config.boot.loader.efi;
 
-  systemdBootBuilder = pkgs.substituteAll {
+  systemdBootBuilder = pkgs.substituteAll rec {
     src = ./systemd-boot-builder.py;
 
     isExecutable = true;
@@ -28,33 +28,48 @@ let
 
     inherit (efi) efiSysMountPoint canTouchEfiVariables;
 
+    bootMountPoint = if cfg.xbootldrMountPoint != null
+      then cfg.xbootldrMountPoint
+      else efi.efiSysMountPoint;
+
+    nixosDir = "/EFI/nixos";
+
     inherit (config.system.nixos) distroName;
 
     memtest86 = optionalString cfg.memtest86.enable pkgs.memtest86plus;
 
     netbootxyz = optionalString cfg.netbootxyz.enable pkgs.netbootxyz-efi;
 
+    checkMountpoints = pkgs.writeShellScript "check-mountpoints" ''
+      fail() {
+        echo "$1 = '$2' is not a mounted partition. Is the path configured correctly?" >&2
+        exit 1
+      }
+      ${pkgs.util-linuxMinimal}/bin/findmnt ${efiSysMountPoint} > /dev/null || fail efiSysMountPoint ${efiSysMountPoint}
+      ${lib.optionalString
+        (cfg.xbootldrMountPoint != null)
+        "${pkgs.util-linuxMinimal}/bin/findmnt ${cfg.xbootldrMountPoint} > /dev/null || fail xbootldrMountPoint ${cfg.xbootldrMountPoint}"}
+    '';
+
     copyExtraFiles = pkgs.writeShellScript "copy-extra-files" ''
       empty_file=$(${pkgs.coreutils}/bin/mktemp)
 
       ${concatStrings (mapAttrsToList (n: v: ''
-        ${pkgs.coreutils}/bin/install -Dp "${v}" "${efi.efiSysMountPoint}/"${escapeShellArg n}
-        ${pkgs.coreutils}/bin/install -D $empty_file "${efi.efiSysMountPoint}/efi/nixos/.extra-files/"${escapeShellArg n}
+        ${pkgs.coreutils}/bin/install -Dp "${v}" "${bootMountPoint}/"${escapeShellArg n}
+        ${pkgs.coreutils}/bin/install -D $empty_file "${bootMountPoint}/${nixosDir}/.extra-files/"${escapeShellArg n}
       '') cfg.extraFiles)}
 
       ${concatStrings (mapAttrsToList (n: v: ''
-        ${pkgs.coreutils}/bin/install -Dp "${pkgs.writeText n v}" "${efi.efiSysMountPoint}/loader/entries/"${escapeShellArg n}
-        ${pkgs.coreutils}/bin/install -D $empty_file "${efi.efiSysMountPoint}/efi/nixos/.extra-files/loader/entries/"${escapeShellArg n}
+        ${pkgs.coreutils}/bin/install -Dp "${pkgs.writeText n v}" "${bootMountPoint}/loader/entries/"${escapeShellArg n}
+        ${pkgs.coreutils}/bin/install -D $empty_file "${bootMountPoint}/${nixosDir}/.extra-files/loader/entries/"${escapeShellArg n}
       '') cfg.extraEntries)}
     '';
   };
 
-  checkedSystemdBootBuilder = pkgs.runCommand "systemd-boot" {
-    nativeBuildInputs = [ pkgs.mypy ];
-  } ''
+  checkedSystemdBootBuilder = pkgs.runCommand "systemd-boot" { } ''
     mkdir -p $out/bin
     install -m755 ${systemdBootBuilder} $out/bin/systemd-boot-builder
-    mypy \
+    ${lib.getExe pkgs.buildPackages.mypy} \
       --no-implicit-optional \
       --disallow-untyped-calls \
       --disallow-untyped-defs \
@@ -72,6 +87,16 @@ in {
 
   imports =
     [ (mkRenamedOptionModule [ "boot" "loader" "gummiboot" "enable" ] [ "boot" "loader" "systemd-boot" "enable" ])
+      (lib.mkChangedOptionModule
+        [ "boot" "loader" "systemd-boot" "memtest86" "entryFilename" ]
+        [ "boot" "loader" "systemd-boot" "memtest86" "sortKey" ]
+        (config: lib.strings.removeSuffix ".conf" config.boot.loader.systemd-boot.memtest86.entryFilename)
+      )
+      (lib.mkChangedOptionModule
+        [ "boot" "loader" "systemd-boot" "netbootxyz" "entryFilename" ]
+        [ "boot" "loader" "systemd-boot" "netbootxyz" "sortKey" ]
+        (config: lib.strings.removeSuffix ".conf" config.boot.loader.systemd-boot.netbootxyz.entryFilename)
+      )
     ];
 
   options.boot.loader.systemd-boot = {
@@ -87,6 +112,35 @@ in {
       '';
     };
 
+    sortKey = mkOption {
+      default = "nixos";
+      type = lib.types.str;
+      description = ''
+        The sort key used for the NixOS bootloader entries.
+        This key determines sorting relative to non-NixOS entries.
+        See also https://uapi-group.org/specifications/specs/boot_loader_specification/#sorting
+
+        This option can also be used to control the sorting of NixOS specialisations.
+
+        By default, specialisations inherit the sort key of their parent generation
+        and will have the same value for both the sort-key and the version (i.e. the generation number),
+        systemd-boot will therefore sort them based on their file name, meaning that
+        in your boot menu you will have each main generation directly followed by
+        its specialisations sorted alphabetically by their names.
+
+        If you want a different ordering for a specialisation, you can override
+        its sort-key which will cause the specialisation to be uncoupled from its
+        parent generation. It will then be sorted by its new sort-key just like
+        any other boot entry.
+
+        The sort-key is stored in the generation's bootspec, which means that
+        generations keep their sort-keys even if the original definition of the
+        generation was removed from the NixOS configuration.
+        It also means that updating the sort-key will only affect new generations,
+        while old ones will keep the sort-key that they were originally built with.
+      '';
+    };
+
     editor = mkOption {
       default = true;
 
@@ -101,6 +155,18 @@ in {
       '';
     };
 
+    xbootldrMountPoint = mkOption {
+      default = null;
+      type = types.nullOr types.str;
+      description = lib.mdDoc ''
+        Where the XBOOTLDR partition is mounted.
+
+        If set, this partition will be used as $BOOT to store boot loader entries and extra files
+        instead of the EFI partition. As per the bootloader specification, it is recommended that
+        the EFI and XBOOTLDR partitions be mounted at `/efi` and `/boot`, respectively.
+      '';
+    };
+
     configurationLimit = mkOption {
       default = null;
       example = 120;
@@ -110,7 +176,7 @@ in {
         Useful to prevent boot partition running out of disk space.
 
         `null` means no limit i.e. all generations
-        that were not garbage collected yet.
+        that have not been garbage collected yet.
       '';
     };
 
@@ -157,13 +223,15 @@ in {
         '';
       };
 
-      entryFilename = mkOption {
-        default = "memtest86.conf";
+      sortKey = mkOption {
+        default = "o_memtest86";
         type = types.str;
         description = lib.mdDoc ''
-          `systemd-boot` orders the menu entries by the config file names,
+          `systemd-boot` orders the menu entries by their sort keys,
           so if you want something to appear after all the NixOS entries,
           it should start with {file}`o` or onwards.
+
+          See also {option}`boot.loader.systemd-boot.sortKey`.
         '';
       };
     };
@@ -180,13 +248,15 @@ in {
         '';
       };
 
-      entryFilename = mkOption {
-        default = "o_netbootxyz.conf";
+      sortKey = mkOption {
+        default = "o_netbootxyz";
         type = types.str;
         description = lib.mdDoc ''
-          `systemd-boot` orders the menu entries by the config file names,
+          `systemd-boot` orders the menu entries by their sort keys,
           so if you want something to appear after all the NixOS entries,
           it should start with {file}`o` or onwards.
+
+          See also {option}`boot.loader.systemd-boot.sortKey`.
         '';
       };
     };
@@ -198,17 +268,19 @@ in {
         { "memtest86.conf" = '''
           title Memtest86+
           efi /efi/memtest86/memtest.efi
+          sort-key z_memtest
         '''; }
       '';
       description = lib.mdDoc ''
         Any additional entries you want added to the `systemd-boot` menu.
-        These entries will be copied to {file}`/boot/loader/entries`.
+        These entries will be copied to {file}`$BOOT/loader/entries`.
         Each attribute name denotes the destination file name,
         and the corresponding attribute value is the contents of the entry.
 
-        `systemd-boot` orders the menu entries by the config file names,
-        so if you want something to appear after all the NixOS entries,
-        it should start with {file}`o` or onwards.
+        To control the ordering of the entry in the boot menu, use the sort-key
+        field, see
+        https://uapi-group.org/specifications/specs/boot_loader_specification/#sorting
+        and {option}`boot.loader.systemd-boot.sortKey`.
       '';
     };
 
@@ -219,9 +291,9 @@ in {
         { "efi/memtest86/memtest.efi" = "''${pkgs.memtest86plus}/memtest.efi"; }
       '';
       description = lib.mdDoc ''
-        A set of files to be copied to {file}`/boot`.
+        A set of files to be copied to {file}`$BOOT`.
         Each attribute name denotes the destination file name in
-        {file}`/boot`, while the corresponding
+        {file}`$BOOT`, while the corresponding
         attribute value specifies the source file.
       '';
     };
@@ -246,6 +318,18 @@ in {
   config = mkIf cfg.enable {
     assertions = [
       {
+        assertion = (hasPrefix "/" efi.efiSysMountPoint);
+        message = "The ESP mount point '${efi.efiSysMountPoint}' must be an absolute path";
+      }
+      {
+        assertion = cfg.xbootldrMountPoint == null || (hasPrefix "/" cfg.xbootldrMountPoint);
+        message = "The XBOOTLDR mount point '${cfg.xbootldrMountPoint}' must be an absolute path";
+      }
+      {
+        assertion = cfg.xbootldrMountPoint != efi.efiSysMountPoint;
+        message = "The XBOOTLDR mount point '${cfg.xbootldrMountPoint}' cannot be the same as the ESP mount point '${efi.efiSysMountPoint}'";
+      }
+      {
         assertion = (config.boot.kernelPackages.kernel.features or { efiBootStub = true; }) ? efiBootStub;
         message = "This kernel does not support the EFI boot stub";
       }
@@ -289,19 +373,25 @@ in {
 
     boot.loader.systemd-boot.extraEntries = mkMerge [
       (mkIf cfg.memtest86.enable {
-        "${cfg.memtest86.entryFilename}" = ''
+        "memtest86.conf" = ''
           title  Memtest86+
           efi    /efi/memtest86/memtest.efi
+          sort-key ${cfg.memtest86.sortKey}
         '';
       })
       (mkIf cfg.netbootxyz.enable {
-        "${cfg.netbootxyz.entryFilename}" = ''
+        "netbootxyz.conf" = ''
           title  netboot.xyz
           efi    /efi/netbootxyz/netboot.xyz.efi
+          sort-key ${cfg.netbootxyz.sortKey}
         '';
       })
     ];
 
+    boot.bootspec.extensions."org.nixos.systemd-boot" = {
+      inherit (config.boot.loader.systemd-boot) sortKey;
+    };
+
     system = {
       build.installBootLoader = finalSystemdBootBuilder;
 
diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix
index a7399bd55e77..88d6a2ded873 100644
--- a/nixos/modules/system/boot/networkd.nix
+++ b/nixos/modules/system/boot/networkd.nix
@@ -647,9 +647,9 @@ let
           "BatmanAdvanced"
         ])
         # Note: For DHCP the values both, none, v4, v6 are deprecated
-        (assertValueOneOf "DHCP" ["yes" "no" "ipv4" "ipv6"])
+        (assertValueOneOf "DHCP" (boolValues ++ ["ipv4" "ipv6"]))
         (assertValueOneOf "DHCPServer" boolValues)
-        (assertValueOneOf "LinkLocalAddressing" ["yes" "no" "ipv4" "ipv6" "fallback" "ipv4-fallback"])
+        (assertValueOneOf "LinkLocalAddressing" (boolValues ++ ["ipv4" "ipv6" "fallback" "ipv4-fallback"]))
         (assertValueOneOf "IPv6LinkLocalAddressGenerationMode" ["eui64" "none" "stable-privacy" "random"])
         (assertValueOneOf "IPv4LLRoute" boolValues)
         (assertValueOneOf "DefaultRouteOnDevice" boolValues)
diff --git a/nixos/modules/system/boot/plymouth.nix b/nixos/modules/system/boot/plymouth.nix
index b041b8951fa3..16bca40993ae 100644
--- a/nixos/modules/system/boot/plymouth.nix
+++ b/nixos/modules/system/boot/plymouth.nix
@@ -186,6 +186,8 @@ in
           # module might come from a theme
           cp ${themesEnv}/lib/plymouth/*.so $out
           cp ${plymouth}/lib/plymouth/renderers/*.so $out/renderers
+          # useless in the initrd, and adds several megabytes to the closure
+          rm $out/renderers/x11.so
         '';
         "/etc/plymouth/themes".source = pkgs.runCommand "plymouth-initrd-themes" {} ''
           # Check if the actual requested theme is here
@@ -271,6 +273,8 @@ in
       # module might come from a theme
       cp ${themesEnv}/lib/plymouth/*.so $out/lib/plymouth
       cp ${plymouth}/lib/plymouth/renderers/*.so $out/lib/plymouth/renderers
+      # useless in the initrd, and adds several megabytes to the closure
+      rm $out/lib/plymouth/renderers/x11.so
 
       mkdir -p $out/share/plymouth/themes
       cp ${plymouth}/share/plymouth/plymouthd.defaults $out/share/plymouth
diff --git a/nixos/modules/system/boot/stage-1.nix b/nixos/modules/system/boot/stage-1.nix
index 8f3f3612805f..90a74c0ac578 100644
--- a/nixos/modules/system/boot/stage-1.nix
+++ b/nixos/modules/system/boot/stage-1.nix
@@ -3,7 +3,7 @@
 # the modules necessary to mount the root file system, then calls the
 # init in the root file system to start the second boot stage.
 
-{ config, lib, utils, pkgs, ... }:
+{ config, options, lib, utils, pkgs, ... }:
 
 with lib;
 
@@ -636,10 +636,8 @@ in
       };
 
     boot.initrd.supportedFilesystems = mkOption {
-      default = [ ];
-      example = [ "btrfs" ];
-      type = types.listOf types.str;
-      description = lib.mdDoc "Names of supported filesystem types in the initial ramdisk.";
+      default = { };
+      inherit (options.boot.supportedFilesystems) example type description;
     };
 
     boot.initrd.verbose = mkOption {
diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix
index e29fa49ea23b..49090423e078 100644
--- a/nixos/modules/system/boot/systemd.nix
+++ b/nixos/modules/system/boot/systemd.nix
@@ -97,6 +97,7 @@ let
 
       # Maintaining state across reboots.
       "systemd-random-seed.service"
+      "systemd-boot-random-seed.service"
       "systemd-backlight@.service"
       "systemd-rfkill.service"
       "systemd-rfkill.socket"
@@ -667,7 +668,6 @@ in
 
     # Don't bother with certain units in containers.
     systemd.services.systemd-remount-fs.unitConfig.ConditionVirtualization = "!container";
-    systemd.services.systemd-random-seed.unitConfig.ConditionVirtualization = "!container";
 
     # Increase numeric PID range (set directly instead of copying a one-line file from systemd)
     # https://github.com/systemd/systemd/pull/12226
diff --git a/nixos/modules/system/boot/systemd/coredump.nix b/nixos/modules/system/boot/systemd/coredump.nix
index 03ef00e5683c..271d8f86d0e6 100644
--- a/nixos/modules/system/boot/systemd/coredump.nix
+++ b/nixos/modules/system/boot/systemd/coredump.nix
@@ -52,7 +52,7 @@ in {
           # See: https://github.com/NixOS/nixpkgs/issues/213408
           pkgs.substitute {
             src = "${systemd}/example/sysctl.d/50-coredump.conf";
-            replacements = [
+            substitutions = [
               "--replace"
               "${systemd}"
               "${pkgs.symlinkJoin { name = "systemd"; paths = [ systemd ]; }}"
diff --git a/nixos/modules/system/boot/systemd/initrd.nix b/nixos/modules/system/boot/systemd/initrd.nix
index 9641921fc795..f83837fbc6d4 100644
--- a/nixos/modules/system/boot/systemd/initrd.nix
+++ b/nixos/modules/system/boot/systemd/initrd.nix
@@ -90,8 +90,6 @@ let
     inherit (cfg) packages package;
   };
 
-  fileSystems = filter utils.fsNeededForBoot config.system.build.fileSystems;
-
   kernel-name = config.boot.kernelPackages.kernel.name or "kernel";
   modulesTree = config.system.modulesTree.override { name = kernel-name + "-modules"; };
   firmware = config.hardware.firmware;
diff --git a/nixos/modules/system/boot/systemd/repart.nix b/nixos/modules/system/boot/systemd/repart.nix
index 3be744acd0b3..6cc387cb6f43 100644
--- a/nixos/modules/system/boot/systemd/repart.nix
+++ b/nixos/modules/system/boot/systemd/repart.nix
@@ -10,6 +10,20 @@ let
     "repart.d"
     format
     (lib.mapAttrs (_n: v: { Partition = v; }) cfg.partitions);
+
+  partitionAssertions = lib.mapAttrsToList (fileName: definition:
+    let
+      maxLabelLength = 36; # GPT_LABEL_MAX defined in systemd's gpt.h
+      labelLength = builtins.stringLength definition.Label;
+    in
+    {
+      assertion = definition ? Label -> maxLabelLength >= labelLength;
+      message = ''
+        The partition label '${definition.Label}' defined for '${fileName}' is ${toString labelLength}
+        characters long, but the maximum label length supported by systemd is ${toString maxLabelLength}.
+      '';
+    }
+  ) cfg.partitions;
 in
 {
   options = {
@@ -81,7 +95,7 @@ in
           'boot.initrd.systemd.repart.enable' requires 'boot.initrd.systemd.enable' to be enabled.
         '';
       }
-    ];
+    ] ++ partitionAssertions;
 
     # systemd-repart uses loopback devices for partition creation
     boot.initrd.availableKernelModules = lib.optional initrdCfg.enable "loop";
diff --git a/nixos/modules/system/boot/uki.nix b/nixos/modules/system/boot/uki.nix
index 63a7cbc5967b..ce00ac8e6397 100644
--- a/nixos/modules/system/boot/uki.nix
+++ b/nixos/modules/system/boot/uki.nix
@@ -27,6 +27,20 @@ in
         description = lib.mdDoc "Version of the image or generation the UKI belongs to";
       };
 
+      tries = lib.mkOption {
+        type = lib.types.nullOr lib.types.ints.unsigned;
+        default = null;
+        description = lib.mdDoc ''
+          Number of boot attempts before this UKI is considered bad.
+
+          If no tries are specified (the default) automatic boot assessment remains inactive.
+
+          See documentation on [Automatic Boot Assessment](https://systemd.io/AUTOMATIC_BOOT_ASSESSMENT/) and
+          [boot counting](https://uapi-group.org/specifications/specs/boot_loader_specification/#boot-counting)
+          for more information.
+        '';
+      };
+
       settings = lib.mkOption {
         type = format.type;
         description = lib.mdDoc ''
@@ -69,8 +83,9 @@ in
         name = config.boot.uki.name;
         version = config.boot.uki.version;
         versionInfix = if version != null then "_${version}" else "";
+        triesInfix = if cfg.tries != null then "+${builtins.toString cfg.tries}" else "";
       in
-      name + versionInfix + ".efi";
+      name + versionInfix + triesInfix + ".efi";
 
     system.build.uki = pkgs.runCommand config.system.boot.loader.ukiFile { } ''
       mkdir -p $out
diff --git a/nixos/modules/system/etc/build-composefs-dump.py b/nixos/modules/system/etc/build-composefs-dump.py
index bf4ec791ecf7..bba454dd888d 100644
--- a/nixos/modules/system/etc/build-composefs-dump.py
+++ b/nixos/modules/system/etc/build-composefs-dump.py
@@ -199,7 +199,8 @@ def main() -> None:
                         size=os.stat(source).st_size,
                         filetype=FileType.file,
                         mode=mode,
-                        payload=target,
+                        # payload needs to be relative path in this case
+                        payload=target.lstrip("/"),
                     )
             paths[target] = composefs_path
             add_leading_directories(target, attrs, paths)
diff --git a/nixos/modules/system/etc/etc.nix b/nixos/modules/system/etc/etc.nix
index baf37ba6def3..9f735364196c 100644
--- a/nixos/modules/system/etc/etc.nix
+++ b/nixos/modules/system/etc/etc.nix
@@ -238,7 +238,9 @@ in
       # this should not run because /etc is mounted via a systemd mount unit
       # instead. To a large extent this mimics what composefs does. Because
       # it's relatively simple, however, we avoid the composefs dependency.
-      if [[ ! $IN_NIXOS_SYSTEMD_STAGE1 ]]; then
+      # Since this script is not idempotent, it should not run when etc hasn't
+      # changed.
+      if [[ ! $IN_NIXOS_SYSTEMD_STAGE1 ]] && [[ "${config.system.build.etc}/etc" != "$(readlink -f /run/current-system/etc)" ]]; then
         echo "remounting /etc..."
 
         tmpMetadataMount=$(mktemp --directory)
diff --git a/nixos/modules/tasks/filesystems.nix b/nixos/modules/tasks/filesystems.nix
index 1378a0090c1d..191b46271194 100644
--- a/nixos/modules/tasks/filesystems.nix
+++ b/nixos/modules/tasks/filesystems.nix
@@ -105,7 +105,7 @@ let
         type = types.bool;
         description = lib.mdDoc ''
           If the device does not currently contain a filesystem (as
-          determined by {command}`blkid`, then automatically
+          determined by {command}`blkid`), then automatically
           format it with the filesystem type specified in
           {option}`fsType`.  Use with caution.
         '';
@@ -246,10 +246,23 @@ in
     };
 
     boot.supportedFilesystems = mkOption {
-      default = [ ];
-      example = [ "btrfs" ];
-      type = types.listOf types.str;
-      description = lib.mdDoc "Names of supported filesystem types.";
+      default = { };
+      example = lib.literalExpression ''
+        {
+          btrfs = true;
+          zfs = lib.mkForce false;
+        }
+      '';
+      type = types.coercedTo
+        (types.listOf types.str)
+        (enabled: lib.listToAttrs (map (fs: lib.nameValuePair fs true) enabled))
+        (types.attrsOf types.bool);
+      description = lib.mdDoc ''
+        Names of supported filesystem types, or an attribute set of file system types
+        and their state. The set form may be used together with `lib.mkForce` to
+        explicitly disable support for specific filesystems, e.g. to disable ZFS
+        with an unsupported kernel.
+      '';
     };
 
     boot.specialFileSystems = mkOption {
diff --git a/nixos/modules/tasks/filesystems/apfs.nix b/nixos/modules/tasks/filesystems/apfs.nix
index 2f2be351df61..980a3ad0f9c4 100644
--- a/nixos/modules/tasks/filesystems/apfs.nix
+++ b/nixos/modules/tasks/filesystems/apfs.nix
@@ -4,12 +4,12 @@ with lib;
 
 let
 
-  inInitrd = any (fs: fs == "apfs") config.boot.initrd.supportedFilesystems;
+  inInitrd = config.boot.initrd.supportedFilesystems.apfs or false;
 
 in
 
 {
-  config = mkIf (any (fs: fs == "apfs") config.boot.supportedFilesystems) {
+  config = mkIf (config.boot.supportedFilesystems.apfs or false) {
 
     system.fsPackages = [ pkgs.apfsprogs ];
 
diff --git a/nixos/modules/tasks/filesystems/bcachefs.nix b/nixos/modules/tasks/filesystems/bcachefs.nix
index 3b990ce30b21..ba33edd702f7 100644
--- a/nixos/modules/tasks/filesystems/bcachefs.nix
+++ b/nixos/modules/tasks/filesystems/bcachefs.nix
@@ -118,7 +118,7 @@ let
 in
 
 {
-  config = lib.mkIf (lib.elem "bcachefs" config.boot.supportedFilesystems) (lib.mkMerge [
+  config = lib.mkIf (config.boot.supportedFilesystems.bcachefs or false) (lib.mkMerge [
     {
       inherit assertions;
       # needed for systemd-remount-fs
@@ -133,7 +133,7 @@ in
       };
     }
 
-    (lib.mkIf ((lib.elem "bcachefs" config.boot.initrd.supportedFilesystems) || (bootFs != {})) {
+    (lib.mkIf ((config.boot.initrd.supportedFilesystems.bcachefs or false) || (bootFs != {})) {
       inherit assertions;
       # chacha20 and poly1305 are required only for decryption attempts
       boot.initrd.availableKernelModules = [ "bcachefs" "sha256" "chacha20" "poly1305" ];
diff --git a/nixos/modules/tasks/filesystems/btrfs.nix b/nixos/modules/tasks/filesystems/btrfs.nix
index 87fe326c0974..8494a06f97a2 100644
--- a/nixos/modules/tasks/filesystems/btrfs.nix
+++ b/nixos/modules/tasks/filesystems/btrfs.nix
@@ -4,8 +4,8 @@ with lib;
 
 let
 
-  inInitrd = any (fs: fs == "btrfs") config.boot.initrd.supportedFilesystems;
-  inSystem = any (fs: fs == "btrfs") config.boot.supportedFilesystems;
+  inInitrd = config.boot.initrd.supportedFilesystems.btrfs or false;
+  inSystem = config.boot.supportedFilesystems.btrfs or false;
 
   cfgScrub = config.services.btrfs.autoScrub;
 
diff --git a/nixos/modules/tasks/filesystems/cifs.nix b/nixos/modules/tasks/filesystems/cifs.nix
index 837b9e19bfb9..5a562b2940f7 100644
--- a/nixos/modules/tasks/filesystems/cifs.nix
+++ b/nixos/modules/tasks/filesystems/cifs.nix
@@ -4,14 +4,14 @@ with lib;
 
 let
 
-  inInitrd = any (fs: fs == "cifs") config.boot.initrd.supportedFilesystems;
+  inInitrd = config.boot.initrd.supportedFilesystems.cifs or false;
 
 in
 
 {
   config = {
 
-    system.fsPackages = mkIf (any (fs: fs == "cifs") config.boot.supportedFilesystems) [ pkgs.cifs-utils ];
+    system.fsPackages = mkIf (config.boot.supportedFilesystems.cifs or false) [ pkgs.cifs-utils ];
 
     boot.initrd.availableKernelModules = mkIf inInitrd
       [ "cifs" "nls_utf8" "hmac" "md4" "ecb" "des_generic" "sha256" ];
diff --git a/nixos/modules/tasks/filesystems/ecryptfs.nix b/nixos/modules/tasks/filesystems/ecryptfs.nix
index 8138e6591610..f966a1be1536 100644
--- a/nixos/modules/tasks/filesystems/ecryptfs.nix
+++ b/nixos/modules/tasks/filesystems/ecryptfs.nix
@@ -4,7 +4,7 @@
 with lib;
 
 {
-  config = mkIf (any (fs: fs == "ecryptfs") config.boot.supportedFilesystems) {
+  config = mkIf (config.boot.supportedFilesystems.ecryptfs or false) {
     system.fsPackages = [ pkgs.ecryptfs ];
     security.wrappers = {
       "mount.ecryptfs_private" =
diff --git a/nixos/modules/tasks/filesystems/erofs.nix b/nixos/modules/tasks/filesystems/erofs.nix
index a3d657669350..b13fa2531557 100644
--- a/nixos/modules/tasks/filesystems/erofs.nix
+++ b/nixos/modules/tasks/filesystems/erofs.nix
@@ -2,8 +2,8 @@
 
 let
 
-  inInitrd = lib.any (fs: fs == "erofs") config.boot.initrd.supportedFilesystems;
-  inSystem = lib.any (fs: fs == "erofs") config.boot.supportedFilesystems;
+  inInitrd = config.boot.initrd.supportedFilesystems.erofs or false;
+  inSystem = config.boot.supportedFilesystems.erofs or false;
 
 in
 
diff --git a/nixos/modules/tasks/filesystems/exfat.nix b/nixos/modules/tasks/filesystems/exfat.nix
index 540b9b91c3ec..4011653c00df 100644
--- a/nixos/modules/tasks/filesystems/exfat.nix
+++ b/nixos/modules/tasks/filesystems/exfat.nix
@@ -3,7 +3,7 @@
 with lib;
 
 {
-  config = mkIf (any (fs: fs == "exfat") config.boot.supportedFilesystems) {
+  config = mkIf (config.boot.supportedFilesystems.exfat or false) {
     system.fsPackages = if config.boot.kernelPackages.kernelOlder "5.7" then [
       pkgs.exfat # FUSE
     ] else [
diff --git a/nixos/modules/tasks/filesystems/ext.nix b/nixos/modules/tasks/filesystems/ext.nix
index 1c34ee2c7035..165fe9474c3e 100644
--- a/nixos/modules/tasks/filesystems/ext.nix
+++ b/nixos/modules/tasks/filesystems/ext.nix
@@ -2,8 +2,10 @@
 
 let
 
-  inInitrd = lib.any (fs: fs == "ext2" || fs == "ext3" || fs == "ext4") config.boot.initrd.supportedFilesystems;
-  inSystem = lib.any (fs: fs == "ext2" || fs == "ext3" || fs == "ext4") config.boot.supportedFilesystems;
+  hasExtX = s: s.ext2 or s.ext3 or s.ext4 or false;
+
+  inInitrd = hasExtX config.boot.initrd.supportedFilesystems;
+  inSystem = hasExtX config.boot.supportedFilesystems;
 
 in
 
diff --git a/nixos/modules/tasks/filesystems/f2fs.nix b/nixos/modules/tasks/filesystems/f2fs.nix
index 4f99f9a57fa6..f4f5fcab9cae 100644
--- a/nixos/modules/tasks/filesystems/f2fs.nix
+++ b/nixos/modules/tasks/filesystems/f2fs.nix
@@ -3,11 +3,10 @@
 with lib;
 
 let
-  inInitrd = any (fs: fs == "f2fs") config.boot.initrd.supportedFilesystems;
-  fileSystems = filter (x: x.fsType == "f2fs") config.system.build.fileSystems;
+  inInitrd = config.boot.initrd.supportedFilesystems.f2fs or false;
 in
 {
-  config = mkIf (any (fs: fs == "f2fs") config.boot.supportedFilesystems) {
+  config = mkIf (config.boot.supportedFilesystems.f2fs or false) {
 
     system.fsPackages = [ pkgs.f2fs-tools ];
 
diff --git a/nixos/modules/tasks/filesystems/glusterfs.nix b/nixos/modules/tasks/filesystems/glusterfs.nix
index e8c7fa8efbae..02ef95262dbd 100644
--- a/nixos/modules/tasks/filesystems/glusterfs.nix
+++ b/nixos/modules/tasks/filesystems/glusterfs.nix
@@ -3,7 +3,7 @@
 with lib;
 
 {
-  config = mkIf (any (fs: fs == "glusterfs") config.boot.supportedFilesystems) {
+  config = mkIf (config.boot.supportedFilesystems.glusterfs or false) {
 
     system.fsPackages = [ pkgs.glusterfs ];
 
diff --git a/nixos/modules/tasks/filesystems/jfs.nix b/nixos/modules/tasks/filesystems/jfs.nix
index b5132b4caa33..73ddb0fb18bb 100644
--- a/nixos/modules/tasks/filesystems/jfs.nix
+++ b/nixos/modules/tasks/filesystems/jfs.nix
@@ -3,10 +3,10 @@
 with lib;
 
 let
-  inInitrd = any (fs: fs == "jfs") config.boot.initrd.supportedFilesystems;
+  inInitrd = config.boot.initrd.supportedFilesystems.jfs or false;
 in
 {
-  config = mkIf (any (fs: fs == "jfs") config.boot.supportedFilesystems) {
+  config = mkIf (config.boot.supportedFilesystems.jfs or false) {
 
     system.fsPackages = [ pkgs.jfsutils ];
 
diff --git a/nixos/modules/tasks/filesystems/nfs.nix b/nixos/modules/tasks/filesystems/nfs.nix
index 8c631f0772db..462568b5db3e 100644
--- a/nixos/modules/tasks/filesystems/nfs.nix
+++ b/nixos/modules/tasks/filesystems/nfs.nix
@@ -4,7 +4,7 @@ with lib;
 
 let
 
-  inInitrd = any (fs: fs == "nfs") config.boot.initrd.supportedFilesystems;
+  inInitrd = config.boot.initrd.supportedFilesystems.nfs or false;
 
   nfsStateDir = "/var/lib/nfs";
 
@@ -58,7 +58,7 @@ in
 
   ###### implementation
 
-  config = mkIf (any (fs: fs == "nfs" || fs == "nfs4") config.boot.supportedFilesystems) {
+  config = mkIf (config.boot.supportedFilesystems.nfs or config.boot.supportedFilesystems.nfs4 or false) {
 
     services.rpcbind.enable = true;
 
diff --git a/nixos/modules/tasks/filesystems/ntfs.nix b/nixos/modules/tasks/filesystems/ntfs.nix
index c40d2a1a80bc..99ba494a7a39 100644
--- a/nixos/modules/tasks/filesystems/ntfs.nix
+++ b/nixos/modules/tasks/filesystems/ntfs.nix
@@ -3,7 +3,7 @@
 with lib;
 
 {
-  config = mkIf (any (fs: fs == "ntfs" || fs == "ntfs-3g") config.boot.supportedFilesystems) {
+  config = mkIf (config.boot.supportedFilesystems.ntfs or config.boot.supportedFilesystems.ntfs-3g or false) {
 
     system.fsPackages = [ pkgs.ntfs3g ];
 
diff --git a/nixos/modules/tasks/filesystems/reiserfs.nix b/nixos/modules/tasks/filesystems/reiserfs.nix
index 3c6a0f0cd917..f3f5e6aaa10b 100644
--- a/nixos/modules/tasks/filesystems/reiserfs.nix
+++ b/nixos/modules/tasks/filesystems/reiserfs.nix
@@ -4,12 +4,12 @@ with lib;
 
 let
 
-  inInitrd = any (fs: fs == "reiserfs") config.boot.initrd.supportedFilesystems;
+  inInitrd = config.boot.initrd.supportedFilesystems.reiserfs or false;
 
 in
 
 {
-  config = mkIf (any (fs: fs == "reiserfs") config.boot.supportedFilesystems) {
+  config = mkIf (config.boot.supportedFilesystems.reiserfs or false) {
 
     system.fsPackages = [ pkgs.reiserfsprogs ];
 
diff --git a/nixos/modules/tasks/filesystems/squashfs.nix b/nixos/modules/tasks/filesystems/squashfs.nix
index 10d45a21d3ca..a0fac904766a 100644
--- a/nixos/modules/tasks/filesystems/squashfs.nix
+++ b/nixos/modules/tasks/filesystems/squashfs.nix
@@ -2,7 +2,7 @@
 
 let
 
-  inInitrd = lib.any (fs: fs == "squashfs") config.boot.initrd.supportedFilesystems;
+  inInitrd = config.boot.initrd.supportedFilesystems.squashfs or false;
 
 in
 
diff --git a/nixos/modules/tasks/filesystems/sshfs.nix b/nixos/modules/tasks/filesystems/sshfs.nix
index cd71dda16d8b..63ff7f2b6b39 100644
--- a/nixos/modules/tasks/filesystems/sshfs.nix
+++ b/nixos/modules/tasks/filesystems/sshfs.nix
@@ -1,7 +1,11 @@
 { config, lib, pkgs, ... }:
 
 {
-  config = lib.mkIf (lib.any (fs: fs == "sshfs" || fs == "fuse.sshfs") config.boot.supportedFilesystems) {
-    system.fsPackages = [ pkgs.sshfs ];
-  };
+  config = lib.mkIf
+    (config.boot.supportedFilesystems.sshfs
+      or config.boot.supportedFilesystems."fuse.sshfs"
+      or false)
+    {
+      system.fsPackages = [ pkgs.sshfs ];
+    };
 }
diff --git a/nixos/modules/tasks/filesystems/unionfs-fuse.nix b/nixos/modules/tasks/filesystems/unionfs-fuse.nix
index f9954b5182f9..929454ff1529 100644
--- a/nixos/modules/tasks/filesystems/unionfs-fuse.nix
+++ b/nixos/modules/tasks/filesystems/unionfs-fuse.nix
@@ -3,7 +3,7 @@
 {
   config = lib.mkMerge [
 
-    (lib.mkIf (lib.any (fs: fs == "unionfs-fuse") config.boot.initrd.supportedFilesystems) {
+    (lib.mkIf (config.boot.initrd.supportedFilesystems.unionfs-fuse or false) {
       boot.initrd.kernelModules = [ "fuse" ];
 
       boot.initrd.extraUtilsCommands = lib.mkIf (!config.boot.initrd.systemd.enable) ''
@@ -35,7 +35,7 @@
       };
     })
 
-    (lib.mkIf (lib.any (fs: fs == "unionfs-fuse") config.boot.supportedFilesystems) {
+    (lib.mkIf (config.boot.supportedFilesystems.unionfs-fuse or false) {
       system.fsPackages = [ pkgs.unionfs-fuse ];
     })
 
diff --git a/nixos/modules/tasks/filesystems/vboxsf.nix b/nixos/modules/tasks/filesystems/vboxsf.nix
index 5497194f6a8d..00245b5af252 100644
--- a/nixos/modules/tasks/filesystems/vboxsf.nix
+++ b/nixos/modules/tasks/filesystems/vboxsf.nix
@@ -4,7 +4,7 @@ with lib;
 
 let
 
-  inInitrd = any (fs: fs == "vboxsf") config.boot.initrd.supportedFilesystems;
+  inInitrd = config.boot.initrd.supportedFilesystems.vboxsf or false;
 
   package = pkgs.runCommand "mount.vboxsf" { preferLocalBuild = true; } ''
     mkdir -p $out/bin
@@ -13,7 +13,7 @@ let
 in
 
 {
-  config = mkIf (any (fs: fs == "vboxsf") config.boot.supportedFilesystems) {
+  config = mkIf (config.boot.supportedFilesystems.vboxsf or false) {
 
     system.fsPackages = [ package ];
 
diff --git a/nixos/modules/tasks/filesystems/vfat.nix b/nixos/modules/tasks/filesystems/vfat.nix
index 9281b34633c2..d7acc0c9e50b 100644
--- a/nixos/modules/tasks/filesystems/vfat.nix
+++ b/nixos/modules/tasks/filesystems/vfat.nix
@@ -4,12 +4,12 @@ with lib;
 
 let
 
-  inInitrd = any (fs: fs == "vfat") config.boot.initrd.supportedFilesystems;
+  inInitrd = config.boot.initrd.supportedFilesystems.vfat or false;
 
 in
 
 {
-  config = mkIf (any (fs: fs == "vfat") config.boot.supportedFilesystems) {
+  config = mkIf (config.boot.supportedFilesystems.vfat or false) {
 
     system.fsPackages = [ pkgs.dosfstools pkgs.mtools ];
 
diff --git a/nixos/modules/tasks/filesystems/xfs.nix b/nixos/modules/tasks/filesystems/xfs.nix
index 76f31e660ad3..50dc1b3340aa 100644
--- a/nixos/modules/tasks/filesystems/xfs.nix
+++ b/nixos/modules/tasks/filesystems/xfs.nix
@@ -4,12 +4,12 @@ with lib;
 
 let
 
-  inInitrd = any (fs: fs == "xfs") config.boot.initrd.supportedFilesystems;
+  inInitrd = config.boot.initrd.supportedFilesystems.xfs or false;
 
 in
 
 {
-  config = mkIf (any (fs: fs == "xfs") config.boot.supportedFilesystems) {
+  config = mkIf (config.boot.supportedFilesystems.xfs or false) {
 
     system.fsPackages = [ pkgs.xfsprogs.bin ];
 
diff --git a/nixos/modules/tasks/filesystems/zfs.nix b/nixos/modules/tasks/filesystems/zfs.nix
index b289d2151eb7..d11424c11c81 100644
--- a/nixos/modules/tasks/filesystems/zfs.nix
+++ b/nixos/modules/tasks/filesystems/zfs.nix
@@ -20,8 +20,8 @@ let
   clevisDatasets = map (e: e.device) (filter (e: e.device != null && (hasAttr e.device config.boot.initrd.clevis.devices) && e.fsType == "zfs" && (fsNeededForBoot e)) config.system.build.fileSystems);
 
 
-  inInitrd = any (fs: fs == "zfs") config.boot.initrd.supportedFilesystems;
-  inSystem = any (fs: fs == "zfs") config.boot.supportedFilesystems;
+  inInitrd = config.boot.initrd.supportedFilesystems.zfs or false;
+  inSystem = config.boot.supportedFilesystems.zfs or false;
 
   autosnapPkg = pkgs.zfstools.override {
     zfs = cfgZfs.package;
@@ -211,6 +211,7 @@ in
 
   imports = [
     (mkRemovedOptionModule [ "boot" "zfs" "enableLegacyCrypto" ] "The corresponding package was removed from nixpkgs.")
+    (mkRemovedOptionModule [ "boot" "zfs" "enableUnstable" ] "Instead set `boot.zfs.package = pkgs.zfs_unstable;`")
   ];
 
   ###### interface
@@ -219,9 +220,9 @@ in
     boot.zfs = {
       package = mkOption {
         type = types.package;
-        default = if cfgZfs.enableUnstable then pkgs.zfsUnstable else pkgs.zfs;
-        defaultText = literalExpression "if zfsUnstable is enabled then pkgs.zfsUnstable else pkgs.zfs";
-        description = lib.mdDoc "Configured ZFS userland tools package, use `pkgs.zfsUnstable` if you want to track the latest staging ZFS branch.";
+        default = pkgs.zfs;
+        defaultText = literalExpression "pkgs.zfs";
+        description = lib.mdDoc "Configured ZFS userland tools package, use `pkgs.zfs_unstable` if you want to track the latest staging ZFS branch.";
       };
 
       modulePackage = mkOption {
@@ -239,19 +240,6 @@ in
         description = lib.mdDoc "True if ZFS filesystem support is enabled";
       };
 
-      enableUnstable = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          Use the unstable zfs package. This might be an option, if the latest
-          kernel is not yet supported by a published release of ZFS. Enabling
-          this option will install a development version of ZFS on Linux. The
-          version will have already passed an extensive test suite, but it is
-          more likely to hit an undiscovered bug compared to running a released
-          version of ZFS on Linux.
-          '';
-      };
-
       allowHibernation = mkOption {
         type = types.bool;
         default = false;
@@ -347,24 +335,12 @@ in
       removeLinuxDRM = lib.mkOption {
         type = types.bool;
         default = false;
-        description = lib.mdDoc ''
-          Linux 6.2 dropped some kernel symbols required on aarch64 required by zfs.
-          Enabling this option will bring them back to allow this kernel version.
-          Note that in some jurisdictions this may be illegal as it might be considered
-          removing copyright protection from the code.
-          See https://www.ifross.org/?q=en/artikel/ongoing-dispute-over-value-exportsymbolgpl-function for further information.
+        description = ''
+          Patch the kernel to change symbols needed by ZFS from
+          EXPORT_SYMBOL_GPL to EXPORT_SYMBOL.
 
-          If configure your kernel package with `zfs.latestCompatibleLinuxPackages`, you will need to also pass removeLinuxDRM to that package like this:
-
-          ```
-          { pkgs, ... }: {
-            boot.kernelPackages = (pkgs.zfs.override {
-              removeLinuxDRM = pkgs.hostPlatform.isAarch64;
-            }).latestCompatibleLinuxPackages;
-
-            boot.zfs.removeLinuxDRM = true;
-          }
-          ```
+          Currently has no effect, but may again in future if a kernel
+          update breaks ZFS due to symbols being newly changed to GPL.
         '';
       };
     };
@@ -588,7 +564,7 @@ in
         kernelParams = lib.optionals (!config.boot.zfs.allowHibernation) [ "nohibernate" ];
 
         extraModulePackages = [
-          (cfgZfs.modulePackage.override { inherit (cfgZfs) removeLinuxDRM; })
+          cfgZfs.modulePackage
         ];
       };
 
@@ -725,21 +701,6 @@ in
       services.udev.packages = [ cfgZfs.package ]; # to hook zvol naming, etc.
       systemd.packages = [ cfgZfs.package ];
 
-      # Export kernel_neon_* symbols again.
-      # This change is necessary until ZFS figures out a solution
-      # with upstream or in their build system to fill the gap for
-      # this symbol.
-      # In the meantime, we restore what was once a working piece of code
-      # in the kernel.
-      boot.kernelPatches = lib.optional (cfgZfs.removeLinuxDRM && pkgs.stdenv.hostPlatform.system == "aarch64-linux") {
-        name = "export-neon-symbols-as-gpl";
-        patch = pkgs.fetchpatch {
-          url = "https://github.com/torvalds/linux/commit/aaeca98456431a8d9382ecf48ac4843e252c07b3.patch";
-          hash = "sha256-L2g4G1tlWPIi/QRckMuHDcdWBcKpObSWSRTvbHRIwIk=";
-          revert = true;
-        };
-      };
-
       systemd.services = let
         createImportService' = pool: createImportService {
           inherit pool;
diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix
index 3e33cabf2660..b3d81078eb34 100644
--- a/nixos/modules/virtualisation/containers.nix
+++ b/nixos/modules/virtualisation/containers.nix
@@ -28,6 +28,43 @@ in
       description = lib.mdDoc "Enable the OCI seccomp BPF hook";
     };
 
+    cdi = {
+      dynamic.nvidia.enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Enable dynamic CDI configuration for NVidia devices by running nvidia-container-toolkit on boot.
+        '';
+      };
+
+      static = mkOption {
+        type = types.attrs;
+        default = { };
+        description = lib.mdDoc ''
+          Declarative CDI specification. Each key of the attribute set
+          will be mapped to a file in /etc/cdi. It is required for every
+          key to be provided in JSON format.
+        '';
+        example = {
+          some-vendor = builtins.fromJSON ''
+              {
+                "cdiVersion": "0.5.0",
+                "kind": "some-vendor.com/foo",
+                "devices": [],
+                "containerEdits": []
+              }
+            '';
+
+          some-other-vendor = {
+            cdiVersion = "0.5.0";
+            kind = "some-other-vendor.com/bar";
+            devices = [];
+            containerEdits = [];
+          };
+        };
+      };
+    };
+
     containersConf.settings = mkOption {
       type = toml.type;
       default = { };
@@ -113,6 +150,8 @@ in
 
   config = lib.mkIf cfg.enable {
 
+    hardware.nvidia-container-toolkit-cdi-generator.enable = lib.mkIf cfg.cdi.dynamic.nvidia.enable true;
+
     virtualisation.containers.containersConf.cniPlugins = [ pkgs.cni-plugins ];
 
     virtualisation.containers.containersConf.settings = {
@@ -124,19 +163,28 @@ in
       };
     };
 
-    environment.etc."containers/containers.conf".source =
-      toml.generate "containers.conf" cfg.containersConf.settings;
-
-    environment.etc."containers/storage.conf".source =
-      toml.generate "storage.conf" cfg.storage.settings;
+    environment.etc = let
+      cdiStaticConfigurationFiles = (lib.attrsets.mapAttrs'
+        (name: value:
+          lib.attrsets.nameValuePair "cdi/${name}.json"
+            { text = builtins.toJSON value; })
+        cfg.cdi.static);
+    in {
+      "containers/containers.conf".source =
+        toml.generate "containers.conf" cfg.containersConf.settings;
+
+      "containers/storage.conf".source =
+        toml.generate "storage.conf" cfg.storage.settings;
+
+      "containers/registries.conf".source = toml.generate "registries.conf" {
+        registries = lib.mapAttrs (n: v: { registries = v; }) cfg.registries;
+      };
 
-    environment.etc."containers/registries.conf".source = toml.generate "registries.conf" {
-      registries = lib.mapAttrs (n: v: { registries = v; }) cfg.registries;
-    };
+      "containers/policy.json".source =
+        if cfg.policy != { } then pkgs.writeText "policy.json" (builtins.toJSON cfg.policy)
+        else "${pkgs.skopeo.policy}/default-policy.json";
+    } // cdiStaticConfigurationFiles;
 
-    environment.etc."containers/policy.json".source =
-      if cfg.policy != { } then pkgs.writeText "policy.json" (builtins.toJSON cfg.policy)
-      else "${pkgs.skopeo.policy}/default-policy.json";
   };
 
 }
diff --git a/nixos/modules/virtualisation/cri-o.nix b/nixos/modules/virtualisation/cri-o.nix
index dacd700537c7..417cf516c7f4 100644
--- a/nixos/modules/virtualisation/cri-o.nix
+++ b/nixos/modules/virtualisation/cri-o.nix
@@ -6,7 +6,7 @@ let
 
   crioPackage = pkgs.cri-o.override {
     extraPackages = cfg.extraPackages
-      ++ lib.optional (builtins.elem "zfs" config.boot.supportedFilesystems) config.boot.zfs.package;
+      ++ lib.optional (config.boot.supportedFilesystems.zfs or false) config.boot.zfs.package;
   };
 
   format = pkgs.formats.toml { };
diff --git a/nixos/modules/virtualisation/docker.nix b/nixos/modules/virtualisation/docker.nix
index d4d34d13a94d..cceb186e0b36 100644
--- a/nixos/modules/virtualisation/docker.nix
+++ b/nixos/modules/virtualisation/docker.nix
@@ -72,6 +72,8 @@ in
         type = types.bool;
         default = false;
         description = lib.mdDoc ''
+          **Deprecated**, please use virtualisation.containers.cdi.dynamic.nvidia.enable instead.
+
           Enable nvidia-docker wrapper, supporting NVIDIA GPUs inside docker containers.
         '';
       };
@@ -185,6 +187,16 @@ in
       users.groups.docker.gid = config.ids.gids.docker;
       systemd.packages = [ cfg.package ];
 
+      # Docker 25.0.0 supports CDI by default
+      # (https://docs.docker.com/engine/release-notes/25.0/#new). Encourage
+      # moving to CDI as opposed to having deprecated runtime
+      # wrappers.
+      warnings = lib.optionals (cfg.enableNvidia && (lib.strings.versionAtLeast cfg.package.version "25")) [
+        ''
+          You have set virtualisation.docker.enableNvidia. This option is deprecated, please set virtualisation.containers.cdi.dynamic.nvidia.enable instead.
+        ''
+      ];
+
       systemd.services.docker = {
         wantedBy = optional cfg.enableOnBoot "multi-user.target";
         after = [ "network.target" "docker.socket" ];
diff --git a/nixos/modules/virtualisation/hyperv-image.nix b/nixos/modules/virtualisation/hyperv-image.nix
index efaea0c110d2..fddff7bf1c69 100644
--- a/nixos/modules/virtualisation/hyperv-image.nix
+++ b/nixos/modules/virtualisation/hyperv-image.nix
@@ -60,7 +60,6 @@ in {
     boot.growPartition = true;
 
     boot.loader.grub = {
-      version = 2;
       device = "nodev";
       efiSupport = true;
       efiInstallAsRemovable = true;
diff --git a/nixos/modules/virtualisation/incus.nix b/nixos/modules/virtualisation/incus.nix
index bbe5b48b95bb..a561c5682ae5 100644
--- a/nixos/modules/virtualisation/incus.nix
+++ b/nixos/modules/virtualisation/incus.nix
@@ -97,10 +97,23 @@ in
           considered failed and systemd will attempt to restart it.
         '';
       };
+
+      ui = {
+        enable = lib.mkEnableOption (lib.mdDoc "(experimental) Incus UI");
+
+        package = lib.mkPackageOption pkgs [ "incus" "ui" ] { };
+      };
     };
   };
 
   config = lib.mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = !(config.networking.firewall.enable && !config.networking.nftables.enable && config.virtualisation.incus.enable);
+        message = "Incus on NixOS is unsupported using iptables. Set `networking.nftables.enable = true;`";
+      }
+    ];
+
     # https://github.com/lxc/incus/blob/f145309929f849b9951658ad2ba3b8f10cbe69d1/doc/reference/server_settings.md
     boot.kernel.sysctl = {
       "fs.aio-max-nr" = lib.mkDefault 524288;
@@ -165,10 +178,12 @@ in
         "${config.boot.zfs.package}/lib/udev"
       ];
 
-      environment = {
+      environment = lib.mkMerge [ {
         # Override Path to the LXC template configuration directory
         INCUS_LXC_TEMPLATE_CONFIG = "${pkgs.lxcfs}/share/lxc/config";
-      };
+      } (lib.mkIf (cfg.ui.enable) {
+        "INCUS_UI" = cfg.ui.package;
+      }) ];
 
       serviceConfig = {
         ExecStart = "${cfg.package}/bin/incusd --group incus-admin";
diff --git a/nixos/modules/virtualisation/linode-config.nix b/nixos/modules/virtualisation/linode-config.nix
index bbf81bda9c02..209bff57ea8b 100644
--- a/nixos/modules/virtualisation/linode-config.nix
+++ b/nixos/modules/virtualisation/linode-config.nix
@@ -59,7 +59,6 @@ with lib;
 
       grub = {
         enable = true;
-        version = 2;
         forceInstall = true;
         device = "nodev";
 
diff --git a/nixos/modules/virtualisation/lxc-container.nix b/nixos/modules/virtualisation/lxc-container.nix
index 8d3a480e6dc8..95e3083ff9ed 100644
--- a/nixos/modules/virtualisation/lxc-container.nix
+++ b/nixos/modules/virtualisation/lxc-container.nix
@@ -14,7 +14,9 @@
 
   options = { };
 
-  config = {
+  config = let
+    initScript = if config.boot.initrd.systemd.enable then "prepare-root" else "init";
+  in {
     boot.isContainer = true;
     boot.postBootCommands =
       ''
@@ -41,7 +43,7 @@
 
       contents = [
         {
-          source = config.system.build.toplevel + "/init";
+          source = config.system.build.toplevel + "/${initScript}";
           target = "/sbin/init";
         }
         # Technically this is not required for lxc, but having also make this configuration work with systemd-nspawn.
@@ -65,7 +67,7 @@
 
       pseudoFiles = [
         "/sbin d 0755 0 0"
-        "/sbin/init s 0555 0 0 ${config.system.build.toplevel}/init"
+        "/sbin/init s 0555 0 0 ${config.system.build.toplevel}/${initScript}"
         "/dev d 0755 0 0"
         "/proc d 0555 0 0"
         "/sys d 0555 0 0"
@@ -74,7 +76,7 @@
 
     system.build.installBootLoader = pkgs.writeScript "install-lxd-sbin-init.sh" ''
       #!${pkgs.runtimeShell}
-      ${pkgs.coreutils}/bin/ln -fs "$1/init" /sbin/init
+      ${pkgs.coreutils}/bin/ln -fs "$1/${initScript}" /sbin/init
     '';
 
     # networkd depends on this, but systemd module disables this for containers
@@ -83,7 +85,7 @@
     systemd.packages = [ pkgs.distrobuilder.generator ];
 
     system.activationScripts.installInitScript = lib.mkForce ''
-      ln -fs $systemConfig/init /sbin/init
+      ln -fs $systemConfig/${initScript} /sbin/init
     '';
   };
 }
diff --git a/nixos/modules/virtualisation/oci-containers.nix b/nixos/modules/virtualisation/oci-containers.nix
index b6a7b1154c4a..a88715587d65 100644
--- a/nixos/modules/virtualisation/oci-containers.nix
+++ b/nixos/modules/virtualisation/oci-containers.nix
@@ -252,10 +252,13 @@ let
       text = ''
         ${cfg.backend} rm -f ${name} || true
         ${optionalString (isValidLogin container.login) ''
+          # try logging in, if it fails, check if image exists locally
           ${cfg.backend} login \
           ${container.login.registry} \
           --username ${container.login.username} \
-          --password-stdin < ${container.login.passwordFile}
+          --password-stdin < ${container.login.passwordFile} \
+          || ${cfg.backend} image inspect ${container.image} >/dev/null \
+          || { echo "image doesn't exist locally and login failed" >&2 ; exit 1; }
         ''}
         ${optionalString (container.imageFile != null) ''
           ${cfg.backend} load -i ${container.imageFile}
diff --git a/nixos/modules/virtualisation/podman/default.nix b/nixos/modules/virtualisation/podman/default.nix
index 47382f9beab0..a97739054216 100644
--- a/nixos/modules/virtualisation/podman/default.nix
+++ b/nixos/modules/virtualisation/podman/default.nix
@@ -9,7 +9,7 @@ let
     extraPackages = cfg.extraPackages
       # setuid shadow
       ++ [ "/run/wrappers" ]
-      ++ lib.optional (builtins.elem "zfs" config.boot.supportedFilesystems) config.boot.zfs.package;
+      ++ lib.optional (config.boot.supportedFilesystems.zfs or false) config.boot.zfs.package;
   });
 
   # Provides a fake "docker" binary mapping to podman
@@ -82,6 +82,8 @@ in
       type = types.bool;
       default = false;
       description = lib.mdDoc ''
+        **Deprecated**, please use virtualisation.containers.cdi.dynamic.nvidia.enable instead.
+
         Enable use of NVidia GPUs from within podman containers.
       '';
     };
@@ -166,6 +168,12 @@ in
       inherit (networkConfig) dns_enabled network_interface;
     in
     lib.mkIf cfg.enable {
+      warnings = lib.optionals cfg.enableNvidia [
+        ''
+          You have set virtualisation.podman.enableNvidia. This option is deprecated, please set virtualisation.containers.cdi.dynamic.nvidia.enable instead.
+        ''
+      ];
+
       environment.systemPackages = [ cfg.package ]
         ++ lib.optional cfg.dockerCompat dockerCompat;
 
@@ -208,9 +216,11 @@ in
         requires = [ "podman.service" ];
       };
 
+      systemd.services.podman.environment = config.networking.proxy.envVars;
       systemd.sockets.podman.wantedBy = [ "sockets.target" ];
       systemd.sockets.podman.socketConfig.SocketGroup = "podman";
 
+      systemd.user.services.podman.environment = config.networking.proxy.envVars;
       systemd.user.sockets.podman.wantedBy = [ "sockets.target" ];
 
       systemd.timers.podman-prune.timerConfig = lib.mkIf cfg.autoPrune.enable {
diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix
index 55a214325118..b5a8b08eee70 100644
--- a/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixos/modules/virtualisation/qemu-vm.nix
@@ -1183,6 +1183,10 @@ in
         "-tpmdev emulator,id=tpm_dev_0,chardev=chrtpm"
         "-device ${cfg.tpm.deviceModel},tpmdev=tpm_dev_0"
       ])
+      (mkIf (pkgs.stdenv.hostPlatform.isx86 && cfg.efi.OVMF.systemManagementModeRequired) [
+        "-machine" "q35,smm=on"
+        "-global" "driver=cfi.pflash01,property=secure,value=on"
+      ])
     ];
 
     virtualisation.qemu.drives = mkMerge [
diff --git a/nixos/modules/virtualisation/vmware-image.nix b/nixos/modules/virtualisation/vmware-image.nix
index a38713b4d4ee..3674b37d0b97 100644
--- a/nixos/modules/virtualisation/vmware-image.nix
+++ b/nixos/modules/virtualisation/vmware-image.nix
@@ -80,7 +80,6 @@ in {
     boot.growPartition = true;
 
     boot.loader.grub = {
-      version = 2;
       device = "nodev";
       efiSupport = true;
       efiInstallAsRemovable = true;
diff --git a/nixos/release-combined.nix b/nixos/release-combined.nix
index 7700441b1d6b..459700514ffc 100644
--- a/nixos/release-combined.nix
+++ b/nixos/release-combined.nix
@@ -142,7 +142,6 @@ in rec {
         (onFullSupported "nixos.tests.networking.networkd.virtual")
         (onFullSupported "nixos.tests.networking.networkd.vlan")
         (onFullSupported "nixos.tests.systemd-networkd-ipv6-prefix-delegation")
-        (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 6204dc731ad9..cac20b63925f 100644
--- a/nixos/release-small.nix
+++ b/nixos/release-small.nix
@@ -43,7 +43,7 @@ in rec {
         login
         misc
         nat
-        nfs3
+        nfs4
         openssh
         php
         predictable-interface-names
@@ -125,7 +125,7 @@ in rec {
         "nixos.tests.misc"
         "nixos.tests.nat.firewall"
         "nixos.tests.nat.standalone"
-        "nixos.tests.nfs3.simple"
+        "nixos.tests.nfs4.simple"
         "nixos.tests.openssh"
         "nixos.tests.php.fpm"
         "nixos.tests.php.pcre"
diff --git a/nixos/release.nix b/nixos/release.nix
index 2acc5ade7848..ff60b0b79f6d 100644
--- a/nixos/release.nix
+++ b/nixos/release.nix
@@ -177,6 +177,12 @@ in rec {
     inherit system;
   });
 
+  iso_plasma6 = forMatchingSystems supportedSystems (system: makeIso {
+    module = ./modules/installer/cd-dvd/installation-cd-graphical-calamares-plasma6.nix;
+    type = "plasma6";
+    inherit system;
+  });
+
   iso_gnome = forMatchingSystems supportedSystems (system: makeIso {
     module = ./modules/installer/cd-dvd/installation-cd-graphical-calamares-gnome.nix;
     type = "gnome";
diff --git a/nixos/tests/acme.nix b/nixos/tests/acme.nix
index 272782dc2f62..d63a77fcdd23 100644
--- a/nixos/tests/acme.nix
+++ b/nixos/tests/acme.nix
@@ -1,4 +1,7 @@
-{ pkgs, lib, ... }: let
+{ config, lib, ... }: let
+
+  pkgs = config.node.pkgs;
+
   commonConfig = ./common/acme/client;
 
   dnsServerIP = nodes: nodes.dnsserver.networking.primaryIPAddress;
diff --git a/nixos/tests/activation/etc-overlay-immutable.nix b/nixos/tests/activation/etc-overlay-immutable.nix
index 70c3623b929c..f347f9cf8efe 100644
--- a/nixos/tests/activation/etc-overlay-immutable.nix
+++ b/nixos/tests/activation/etc-overlay-immutable.nix
@@ -20,11 +20,17 @@
   };
 
   testScript = ''
-    machine.succeed("findmnt --kernel --type overlay /etc")
-    machine.fail("stat /etc/newgen")
+    with subtest("/etc is mounted as an overlay"):
+      machine.succeed("findmnt --kernel --type overlay /etc")
 
-    machine.succeed("/run/current-system/specialisation/new-generation/bin/switch-to-configuration switch")
+    with subtest("switching to the same generation"):
+      machine.succeed("/run/current-system/bin/switch-to-configuration test")
 
-    assert machine.succeed("cat /etc/newgen") == "newgen"
+    with subtest("switching to a new generation"):
+      machine.fail("stat /etc/newgen")
+
+      machine.succeed("/run/current-system/specialisation/new-generation/bin/switch-to-configuration switch")
+
+      assert machine.succeed("cat /etc/newgen") == "newgen"
   '';
 }
diff --git a/nixos/tests/activation/etc-overlay-mutable.nix b/nixos/tests/activation/etc-overlay-mutable.nix
index cfe7604fceb8..087c06408a71 100644
--- a/nixos/tests/activation/etc-overlay-mutable.nix
+++ b/nixos/tests/activation/etc-overlay-mutable.nix
@@ -18,13 +18,19 @@
   };
 
   testScript = ''
-    machine.succeed("findmnt --kernel --type overlay /etc")
-    machine.fail("stat /etc/newgen")
-    machine.succeed("echo -n 'mutable' > /etc/mutable")
+    with subtest("/etc is mounted as an overlay"):
+      machine.succeed("findmnt --kernel --type overlay /etc")
 
-    machine.succeed("/run/current-system/specialisation/new-generation/bin/switch-to-configuration switch")
+    with subtest("switching to the same generation"):
+      machine.succeed("/run/current-system/bin/switch-to-configuration test")
 
-    assert machine.succeed("cat /etc/newgen") == "newgen"
-    assert machine.succeed("cat /etc/mutable") == "mutable"
+    with subtest("switching to a new generation"):
+      machine.fail("stat /etc/newgen")
+      machine.succeed("echo -n 'mutable' > /etc/mutable")
+
+      machine.succeed("/run/current-system/specialisation/new-generation/bin/switch-to-configuration switch")
+
+      assert machine.succeed("cat /etc/newgen") == "newgen"
+      assert machine.succeed("cat /etc/mutable") == "mutable"
   '';
 }
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 31af6ec64214..7376cd40b910 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -78,8 +78,9 @@ let
     #       it with `allowAliases = false`?
     # warnIf pkgs.config.allowAliases "nixosTests: pkgs includes aliases."
     {
+      _file = "${__curPos.file} readOnlyPkgs";
       _class = "nixosTest";
-      node.pkgs = pkgs;
+      node.pkgs = pkgs.pkgsLinux;
     };
 
 in {
@@ -128,6 +129,7 @@ in {
   appliance-repart-image = runTest ./appliance-repart-image.nix;
   apparmor = handleTest ./apparmor.nix {};
   archi = handleTest ./archi.nix {};
+  armagetronad = handleTest ./armagetronad.nix {};
   atd = handleTest ./atd.nix {};
   atop = handleTest ./atop.nix {};
   atuin = handleTest ./atuin.nix {};
@@ -497,6 +499,7 @@ in {
   lxd = pkgs.recurseIntoAttrs (handleTest ./lxd { inherit handleTestOn; });
   lxd-image-server = handleTest ./lxd-image-server.nix {};
   #logstash = handleTest ./logstash.nix {};
+  lomiri-system-settings = handleTest ./lomiri-system-settings.nix {};
   lorri = handleTest ./lorri/default.nix {};
   maddy = discoverTests (import ./maddy { inherit handleTest; });
   maestral = handleTest ./maestral.nix {};
@@ -510,12 +513,14 @@ in {
   mastodon = discoverTests (import ./web-apps/mastodon { inherit handleTestOn; });
   pixelfed = discoverTests (import ./web-apps/pixelfed { inherit handleTestOn; });
   mate = handleTest ./mate.nix {};
+  matter-server = handleTest ./matter-server.nix {};
   matomo = handleTest ./matomo.nix {};
   matrix-appservice-irc = handleTest ./matrix/appservice-irc.nix {};
   matrix-conduit = handleTest ./matrix/conduit.nix {};
   matrix-synapse = handleTest ./matrix/synapse.nix {};
   matrix-synapse-workers = handleTest ./matrix/synapse-workers.nix {};
   mattermost = handleTest ./mattermost.nix {};
+  mealie = handleTest ./mealie.nix {};
   mediamtx = handleTest ./mediamtx.nix {};
   mediatomb = handleTest ./mediatomb.nix {};
   mediawiki = handleTest ./mediawiki.nix {};
@@ -535,10 +540,12 @@ in {
   mobilizon = handleTest ./mobilizon.nix {};
   mod_perl = handleTest ./mod_perl.nix {};
   molly-brown = handleTest ./molly-brown.nix {};
+  monado = handleTest ./monado.nix {};
   monica = handleTest ./web-apps/monica.nix {};
   mongodb = handleTest ./mongodb.nix {};
   moodle = handleTest ./moodle.nix {};
   moonraker = handleTest ./moonraker.nix {};
+  morph-browser = handleTest ./morph-browser.nix { };
   morty = handleTest ./morty.nix {};
   mosquitto = handleTest ./mosquitto.nix {};
   moosefs = handleTest ./moosefs.nix {};
@@ -691,6 +698,7 @@ in {
   plantuml-server = handleTest ./plantuml-server.nix {};
   plasma-bigscreen = handleTest ./plasma-bigscreen.nix {};
   plasma5 = handleTest ./plasma5.nix {};
+  plasma6 = handleTest ./plasma6.nix {};
   plasma5-systemd-start = handleTest ./plasma5-systemd-start.nix {};
   plausible = handleTest ./plausible.nix {};
   please = handleTest ./please.nix {};
@@ -771,6 +779,7 @@ in {
   sanoid = handleTest ./sanoid.nix {};
   scaphandre = handleTest ./scaphandre.nix {};
   schleuder = handleTest ./schleuder.nix {};
+  scrutiny = handleTest ./scrutiny.nix {};
   sddm = handleTest ./sddm.nix {};
   seafile = handleTest ./seafile.nix {};
   searx = handleTest ./searx.nix {};
@@ -799,7 +808,7 @@ in {
   solanum = handleTest ./solanum.nix {};
   sonarr = handleTest ./sonarr.nix {};
   sonic-server = handleTest ./sonic-server.nix {};
-  sourcehut = handleTest ./sourcehut.nix {};
+  sourcehut = handleTest ./sourcehut {};
   spacecookie = handleTest ./spacecookie.nix {};
   spark = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./spark {};
   sqlite3-to-mysql = handleTest ./sqlite3-to-mysql.nix {};
@@ -909,6 +918,7 @@ in {
   tor = handleTest ./tor.nix {};
   traefik = handleTestOn ["aarch64-linux" "x86_64-linux"] ./traefik.nix {};
   trafficserver = handleTest ./trafficserver.nix {};
+  transfer-sh = handleTest ./transfer-sh.nix {};
   transmission = handleTest ./transmission.nix { transmission = pkgs.transmission; };
   transmission_4 = handleTest ./transmission.nix { transmission = pkgs.transmission_4; };
   # tracee requires bpf
diff --git a/nixos/tests/armagetronad.nix b/nixos/tests/armagetronad.nix
new file mode 100644
index 000000000000..ff2841dedd21
--- /dev/null
+++ b/nixos/tests/armagetronad.nix
@@ -0,0 +1,272 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+
+let
+  user = "alice";
+
+  client =
+    { pkgs, ... }:
+
+    { imports = [ ./common/user-account.nix ./common/x11.nix ];
+      hardware.opengl.driSupport = true;
+      virtualisation.memorySize = 256;
+      environment = {
+        systemPackages = [ pkgs.armagetronad ];
+        variables.XAUTHORITY = "/home/${user}/.Xauthority";
+      };
+      test-support.displayManager.auto.user = user;
+    };
+
+in {
+  name = "armagetronad";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ numinit ];
+  };
+
+  enableOCR = true;
+
+  nodes =
+    {
+      server = {
+        services.armagetronad.servers = {
+          high-rubber = {
+            enable = true;
+            name = "Smoke Test High Rubber Server";
+            port = 4534;
+            settings = {
+              SERVER_OPTIONS = "High Rubber server made to run smoke tests.";
+              CYCLE_RUBBER = 40;
+              SIZE_FACTOR = 0.5;
+            };
+            roundSettings = {
+              SAY = [
+                "NixOS Smoke Test Server"
+                "https://nixos.org"
+              ];
+            };
+          };
+          sty = {
+            enable = true;
+            name = "Smoke Test sty+ct+ap Server";
+            package = pkgs.armagetronad."0.2.9-sty+ct+ap".dedicated;
+            port = 4535;
+            settings = {
+              SERVER_OPTIONS = "sty+ct+ap server made to run smoke tests.";
+              CYCLE_RUBBER = 20;
+              SIZE_FACTOR = 0.5;
+            };
+            roundSettings = {
+              SAY = [
+                "NixOS Smoke Test sty+ct+ap Server"
+                "https://nixos.org"
+              ];
+            };
+          };
+          trunk = {
+            enable = true;
+            name = "Smoke Test trunk Server";
+            package = pkgs.armagetronad."0.4".dedicated;
+            port = 4536;
+            settings = {
+              SERVER_OPTIONS = "0.4 server made to run smoke tests.";
+              CYCLE_RUBBER = 20;
+              SIZE_FACTOR = 0.5;
+            };
+            roundSettings = {
+              SAY = [
+                "NixOS Smoke Test 0.4 Server"
+                "https://nixos.org"
+              ];
+            };
+          };
+        };
+      };
+
+      client1 = client;
+      client2 = client;
+    };
+
+  testScript = let
+    xdo = name: text: let
+      xdoScript = pkgs.writeText "${name}.xdo" text;
+    in "${pkgs.xdotool}/bin/xdotool ${xdoScript}";
+  in
+    ''
+      import shlex
+      import threading
+      from collections import namedtuple
+
+      class Client(namedtuple('Client', ('node', 'name'))):
+        def send(self, *keys):
+          for key in keys:
+            self.node.send_key(key)
+
+        def send_on(self, text, *keys):
+          self.node.wait_for_text(text)
+          self.send(*keys)
+
+      Server = namedtuple('Server', ('node', 'name', 'address', 'port', 'welcome', 'attacker', 'victim', 'coredump_delay'))
+
+      # Clients and their in-game names
+      clients = (
+        Client(client1, 'Arduino'),
+        Client(client2, 'SmOoThIcE')
+      )
+
+      # Server configs.
+      servers = (
+        Server(server, 'high-rubber', 'server', 4534, 'NixOS Smoke Test Server', 'SmOoThIcE', 'Arduino', 8),
+        Server(server, 'sty', 'server', 4535, 'NixOS Smoke Test sty+ct+ap Server', 'Arduino', 'SmOoThIcE', 8),
+        Server(server, 'trunk', 'server', 4536, 'NixOS Smoke Test 0.4 Server', 'Arduino', 'SmOoThIcE', 8)
+      )
+
+      """
+      Runs a command as the client user.
+      """
+      def run(cmd):
+        return "su - ${user} -c " + shlex.quote(cmd)
+
+      screenshot_idx = 1
+
+      """
+      Takes screenshots on all clients.
+      """
+      def take_screenshots(screenshot_idx):
+        for client in clients:
+          client.node.screenshot(f"screen_{client.name}_{screenshot_idx}")
+        return screenshot_idx + 1
+
+      # Wait for the servers to come up.
+      start_all()
+      for srv in servers:
+        srv.node.wait_for_unit(f"armagetronad-{srv.name}")
+        srv.node.wait_until_succeeds(f"ss --numeric --udp --listening | grep -q {srv.port}")
+
+      # Make sure console commands work through the named pipe we created.
+      for srv in servers:
+        srv.node.succeed(
+          f"echo 'say Testing!' >> /var/lib/armagetronad/{srv.name}/input"
+        )
+        srv.node.succeed(
+          f"echo 'say Testing again!' >> /var/lib/armagetronad/{srv.name}/input"
+        )
+        srv.node.wait_until_succeeds(
+          f"journalctl -u armagetronad-{srv.name} -e | grep -q 'Admin: Testing!'"
+        )
+        srv.node.wait_until_succeeds(
+          f"journalctl -u armagetronad-{srv.name} -e | grep -q 'Admin: Testing again!'"
+        )
+
+      """
+      Sets up a client, waiting for the given barrier on completion.
+      """
+      def client_setup(client, servers, barrier):
+        client.node.wait_for_x()
+
+        # Configure Armagetron.
+        client.node.succeed(
+          run("mkdir -p ~/.armagetronad/var"),
+          run(f"echo 'PLAYER_1 {client.name}' >> ~/.armagetronad/var/autoexec.cfg")
+        )
+        for idx, srv in enumerate(servers):
+          client.node.succeed(
+            run(f"echo 'BOOKMARK_{idx+1}_ADDRESS {srv.address}' >> ~/.armagetronad/var/autoexec.cfg"),
+            run(f"echo 'BOOKMARK_{idx+1}_NAME {srv.name}' >> ~/.armagetronad/var/autoexec.cfg"),
+            run(f"echo 'BOOKMARK_{idx+1}_PORT {srv.port}' >> ~/.armagetronad/var/autoexec.cfg")
+          )
+
+        # Start Armagetron.
+        client.node.succeed(run("ulimit -c unlimited; armagetronad >&2 & disown"))
+        client.node.wait_until_succeeds(
+          run(
+            "${xdo "create_new_win-select_main_window" ''
+              search --onlyvisible --name "Armagetron Advanced"
+              windowfocus --sync
+              windowactivate --sync
+            ''}"
+          )
+        )
+
+        # Get through the tutorial.
+        client.send_on('Language Settings', 'ret')
+        client.send_on('First Setup', 'ret')
+        client.send_on('Welcome to Armagetron Advanced', 'ret')
+        client.send_on('round 1', 'esc')
+        client.send_on('Menu', 'up', 'up', 'ret')
+        client.send_on('We hope you', 'ret')
+        client.send_on('Armagetron Advanced', 'ret')
+        client.send_on('Play Game', 'ret')
+
+        # Online > LAN > Network Setup > Mates > Server Bookmarks
+        client.send_on('Multiplayer', 'down', 'down', 'down', 'down', 'ret')
+
+        barrier.wait()
+
+      # Get to the Server Bookmarks screen on both clients. This takes a while so do it asynchronously.
+      barrier = threading.Barrier(3, timeout=120)
+      for client in clients:
+        threading.Thread(target=client_setup, args=(client, servers, barrier)).start()
+      barrier.wait()
+
+      # Main testing loop. Iterates through each server bookmark and connects to them in sequence.
+      # Assumes that the game is currently on the Server Bookmarks screen.
+      for srv in servers:
+        screenshot_idx = take_screenshots(screenshot_idx)
+
+        # Connect both clients at once, one second apart.
+        for client in clients:
+          client.send('ret')
+          client.node.sleep(1)
+
+        # Wait for clients to connect
+        for client in clients:
+          srv.node.wait_until_succeeds(
+            f"journalctl -u armagetronad-{srv.name} -e | grep -q '{client.name}.*entered the game'"
+          )
+
+        # Wait for the match to start
+        srv.node.wait_until_succeeds(
+          f"journalctl -u armagetronad-{srv.name} -e | grep -q 'Admin: {srv.welcome}'"
+        )
+        srv.node.wait_until_succeeds(
+          f"journalctl -u armagetronad-{srv.name} -e | grep -q 'Admin: https://nixos.org'"
+        )
+        srv.node.wait_until_succeeds(
+          f"journalctl -u armagetronad-{srv.name} -e | grep -q 'Go (round 1 of 10)'"
+        )
+
+        # Wait a bit
+        srv.node.sleep(srv.coredump_delay)
+
+        # Turn the attacker player's lightcycle left
+        attacker = next(client for client in clients if client.name == srv.attacker)
+        victim = next(client for client in clients if client.name == srv.victim)
+        attacker.send('left')
+        screenshot_idx = take_screenshots(screenshot_idx)
+
+        # Wait for coredump.
+        srv.node.wait_until_succeeds(
+          f"journalctl -u armagetronad-{srv.name} -e | grep -q '{attacker.name} core dumped {victim.name}'"
+        )
+        screenshot_idx = take_screenshots(screenshot_idx)
+
+        # Disconnect both clients from the server
+        for client in clients:
+          client.send('esc')
+          client.send_on('Menu', 'up', 'up', 'ret')
+          srv.node.wait_until_succeeds(
+            f"journalctl -u armagetronad-{srv.name} -e | grep -q '{client.name}.*left the game'"
+          )
+
+        # Next server.
+        for client in clients:
+          client.send_on('Server Bookmarks', 'down')
+
+      # Stop the servers
+      for srv in servers:
+        srv.node.succeed(
+          f"systemctl stop armagetronad-{srv.name}"
+        )
+        srv.node.wait_until_fails(f"ss --numeric --udp --listening | grep -q {srv.port}")
+    '';
+
+})
diff --git a/nixos/tests/boot.nix b/nixos/tests/boot.nix
index ec2a9f6527c9..56f72dddf526 100644
--- a/nixos/tests/boot.nix
+++ b/nixos/tests/boot.nix
@@ -4,10 +4,41 @@
 }:
 
 with import ../lib/testing-python.nix { inherit system pkgs; };
-with pkgs.lib;
 
 let
-  qemu-common = import ../lib/qemu-common.nix { inherit (pkgs) lib pkgs; };
+  lib = pkgs.lib;
+  qemu-common = import ../lib/qemu-common.nix { inherit lib pkgs; };
+
+  mkStartCommand = {
+    memory ? 2048,
+    cdrom ? null,
+    usb ? null,
+    pxe ? null,
+    uboot ? false,
+    uefi ? false,
+    extraFlags ? [],
+  }: let
+    qemu = qemu-common.qemuBinary pkgs.qemu_test;
+
+    flags = [
+      "-m" (toString memory)
+      "-netdev" ("user,id=net0" + (lib.optionalString (pxe != null) ",tftp=${pxe},bootfile=netboot.ipxe"))
+      "-device" ("virtio-net-pci,netdev=net0" + (lib.optionalString (pxe != null && uefi) ",romfile=${pkgs.ipxe}/ipxe.efirom"))
+    ] ++ lib.optionals (cdrom != null) [
+      "-cdrom" cdrom
+    ] ++ lib.optionals (usb != null) [
+      "-device" "usb-ehci"
+      "-drive" "id=usbdisk,file=${usb},if=none,readonly"
+      "-device" "usb-storage,drive=usbdisk"
+    ] ++ lib.optionals (pxe != null) [
+      "-boot" "order=n"
+    ] ++ lib.optionals uefi [
+      "-drive" "if=pflash,format=raw,unit=0,readonly=on,file=${pkgs.OVMF.firmware}"
+      "-drive" "if=pflash,format=raw,unit=1,readonly=on,file=${pkgs.OVMF.variables}"
+    ] ++ extraFlags;
+
+    flagsStr = lib.concatStringsSep " " flags;
+  in "${qemu} ${flagsStr}";
 
   iso =
     (import ../lib/eval-config.nix {
@@ -28,21 +59,16 @@ let
       ];
     }).config.system.build.sdImage;
 
-  pythonDict = params: "\n    {\n        ${concatStringsSep ",\n        " (mapAttrsToList (name: param: "\"${name}\": \"${param}\"") params)},\n    }\n";
-
-  makeBootTest = name: extraConfig:
+  makeBootTest = name: config:
     let
-      machineConfig = pythonDict ({
-        qemuBinary = qemu-common.qemuBinary pkgs.qemu_test;
-        qemuFlags = "-m 768";
-      } // extraConfig);
+      startCommand = mkStartCommand config;
     in
       makeTest {
         name = "boot-" + name;
         nodes = { };
         testScript =
           ''
-            machine = create_machine(${machineConfig})
+            machine = create_machine("${startCommand}")
             machine.start()
             machine.wait_for_unit("multi-user.target")
             machine.succeed("nix store verify --no-trust -r --option experimental-features nix-command /run/current-system")
@@ -73,43 +99,35 @@ let
           config.system.build.netbootIpxeScript
         ];
       };
-      machineConfig = pythonDict ({
-        qemuBinary = qemu-common.qemuBinary pkgs.qemu_test;
-        qemuFlags = "-boot order=n -m 2000";
-        netBackendArgs = "tftp=${ipxeBootDir},bootfile=netboot.ipxe";
+      startCommand = mkStartCommand ({
+        pxe = ipxeBootDir;
       } // extraConfig);
     in
       makeTest {
         name = "boot-netboot-" + name;
         nodes = { };
         testScript = ''
-            machine = create_machine(${machineConfig})
+            machine = create_machine("${startCommand}")
             machine.start()
             machine.wait_for_unit("multi-user.target")
             machine.shutdown()
           '';
       };
-  uefiBinary = {
-    x86_64-linux = "${pkgs.OVMF.fd}/FV/OVMF.fd";
-    aarch64-linux = "${pkgs.OVMF.fd}/FV/QEMU_EFI.fd";
-  }.${pkgs.stdenv.hostPlatform.system};
 in {
     uefiCdrom = makeBootTest "uefi-cdrom" {
+      uefi = true;
       cdrom = "${iso}/iso/${iso.isoName}";
-      bios = uefiBinary;
     };
 
     uefiUsb = makeBootTest "uefi-usb" {
+      uefi = true;
       usb = "${iso}/iso/${iso.isoName}";
-      bios = uefiBinary;
     };
 
     uefiNetboot = makeNetbootTest "uefi" {
-      bios = uefiBinary;
-      # Custom ROM is needed for EFI PXE boot. I failed to understand exactly why, because QEMU should still use iPXE for EFI.
-      netFrontendArgs = "romfile=${pkgs.ipxe}/ipxe.efirom";
+      uefi = true;
     };
-} // optionalAttrs (pkgs.stdenv.hostPlatform.system == "x86_64-linux") {
+} // lib.optionalAttrs (pkgs.stdenv.hostPlatform.system == "x86_64-linux") {
     biosCdrom = makeBootTest "bios-cdrom" {
       cdrom = "${iso}/iso/${iso.isoName}";
     };
@@ -124,9 +142,12 @@ in {
       sdImage = "${sd}/sd-image/${sd.imageName}";
       mutableImage = "/tmp/linked-image.qcow2";
 
-      machineConfig = pythonDict {
-        bios = "${pkgs.ubootQemuX86}/u-boot.rom";
-        qemuFlags = "-m 768 -machine type=pc,accel=tcg -drive file=${mutableImage},if=ide,format=qcow2";
+      startCommand = mkStartCommand {
+        extraFlags = [
+          "-bios" "${pkgs.ubootQemuX86}/u-boot.rom"
+          "-machine" "type=pc,accel=tcg"
+          "-drive" "file=${mutableImage},if=virtio"
+        ];
       };
     in makeTest {
       name = "boot-uboot-extlinux";
@@ -138,11 +159,14 @@ in {
         if os.system("qemu-img create -f qcow2 -F raw -b ${sdImage} ${mutableImage}") != 0:
             raise RuntimeError("Could not create mutable linked image")
 
-        machine = create_machine(${machineConfig})
+        machine = create_machine("${startCommand}")
         machine.start()
         machine.wait_for_unit("multi-user.target")
         machine.succeed("nix store verify -r --no-trust --option experimental-features nix-command /run/current-system")
         machine.shutdown()
       '';
+
+      # kernel can't find rootfs after boot - investigate?
+      meta.broken = true;
     };
 }
diff --git a/nixos/tests/ccache.nix b/nixos/tests/ccache.nix
new file mode 100644
index 000000000000..a97ae0501767
--- /dev/null
+++ b/nixos/tests/ccache.nix
@@ -0,0 +1,24 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "ccache";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ ehmry ];
+  };
+
+  nodes.machine = { ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+    environment.systemPackages = [ pkgs.hello ];
+    programs.ccache = {
+      enable = true;
+      packageNames = [ "hello" ];
+    };
+  };
+
+  testScript =
+    ''
+      start_all()
+      machine.wait_for_unit("multi-user.target")
+      machine.succeed("nix-ccache --show-stats")
+      machine.succeed("hello")
+      machine.shutdown()
+    '';
+})
diff --git a/nixos/tests/common/ec2.nix b/nixos/tests/common/ec2.nix
index 1a64c464039b..82922102f07b 100644
--- a/nixos/tests/common/ec2.nix
+++ b/nixos/tests/common/ec2.nix
@@ -61,7 +61,7 @@ with pkgs.lib;
             + " $QEMU_OPTS"
         )
 
-        machine = create_machine({"startCommand": start_command})
+        machine = create_machine(start_command)
         try:
       '' + indentLines script + ''
         finally:
diff --git a/nixos/tests/consul.nix b/nixos/tests/consul.nix
index 6233234ff083..c819312068dc 100644
--- a/nixos/tests/consul.nix
+++ b/nixos/tests/consul.nix
@@ -42,6 +42,8 @@ let
         ];
         networking.firewall = firewallSettings;
 
+        nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ "consul" ];
+
         services.consul = {
           enable = true;
           inherit webUi;
@@ -65,6 +67,8 @@ let
         ];
         networking.firewall = firewallSettings;
 
+        nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ "consul" ];
+
         services.consul =
           assert builtins.elem thisConsensusServerHost allConsensusServerHosts;
           {
diff --git a/nixos/tests/docker-registry.nix b/nixos/tests/docker-registry.nix
index db20cb52c3e3..3969ef3f0226 100644
--- a/nixos/tests/docker-registry.nix
+++ b/nixos/tests/docker-registry.nix
@@ -13,7 +13,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
       services.dockerRegistry.port = 8080;
       services.dockerRegistry.listenAddress = "0.0.0.0";
       services.dockerRegistry.enableGarbageCollect = true;
-      networking.firewall.allowedTCPPorts = [ 8080 ];
+      services.dockerRegistry.openFirewall = true;
     };
 
     client1 = { ... }: {
diff --git a/nixos/tests/docker-tools.nix b/nixos/tests/docker-tools.nix
index f9d8b3ea64e4..7d91076600f9 100644
--- a/nixos/tests/docker-tools.nix
+++ b/nixos/tests/docker-tools.nix
@@ -46,6 +46,32 @@ let
         echo 'runAsRoot has run.'
       '';
     };
+
+  chownTestImage =
+    pkgs.dockerTools.streamLayeredImage {
+      name = "chown-test";
+      tag = "latest";
+      enableFakechroot = true;
+      fakeRootCommands = ''
+        touch /testfile
+        chown 12345:12345 /testfile
+      '';
+      config.Cmd = [ "${pkgs.coreutils}/bin/stat" "-c" "%u:%g" "/testfile" ];
+    };
+
+  nonRootTestImage =
+    pkgs.dockerTools.streamLayeredImage rec {
+      name = "non-root-test";
+      tag = "latest";
+      uid = 1000;
+      gid = 1000;
+      uname = "user";
+      gname = "user";
+      config = {
+        User = "user";
+        Cmd = [ "${pkgs.coreutils}/bin/stat" "-c" "%u:%g" "${pkgs.coreutils}/bin/stat" ];
+      };
+    };
 in {
   name = "docker-tools";
   meta = with pkgs.lib.maintainers; {
@@ -143,6 +169,15 @@ in {
         docker.succeed("docker images --format '{{.Tag}}' | grep -F '${examples.nixLayered.imageTag}'")
         docker.succeed("docker rmi ${examples.nixLayered.imageName}")
 
+    with subtest("Check that images with alternative compression schemas load"):
+        docker.succeed(
+            "docker load --input='${examples.bashZstdCompressed}'",
+            "docker rmi ${examples.bashZstdCompressed.imageName}",
+        )
+        docker.succeed(
+            "docker load --input='${examples.bashUncompressed}'",
+            "docker rmi ${examples.bashUncompressed.imageName}",
+        )
 
     with subtest(
         "Check if the nix store is correctly initialized by listing "
@@ -160,7 +195,7 @@ in {
     ):
         docker.succeed(
             "docker load --input='${examples.bashLayeredWithUser}'",
-            "docker run -u somebody --rm ${examples.bashLayeredWithUser.imageName} ${pkgs.bash}/bin/bash -c 'test 555 == $(stat --format=%a /nix) && test 555 == $(stat --format=%a /nix/store)'",
+            "docker run -u somebody --rm ${examples.bashLayeredWithUser.imageName} ${pkgs.bash}/bin/bash -c 'test 755 == $(stat --format=%a /nix) && test 755 == $(stat --format=%a /nix/store)'",
             "docker rmi ${examples.bashLayeredWithUser.imageName}",
         )
 
@@ -464,6 +499,18 @@ in {
             "docker run --rm ${examples.layeredImageWithFakeRootCommands.imageName} /hello/bin/layeredImageWithFakeRootCommands-hello"
         )
 
+    with subtest("mergeImage correctly deals with varying compression schemas in inputs"):
+        docker.succeed("docker load --input='${examples.mergeVaryingCompressor}'")
+
+        for sub_image, tag in [
+            ("${examples.redis.imageName}", "${examples.redis.imageTag}"),
+            ("${examples.bashUncompressed.imageName}", "${examples.bashUncompressed.imageTag}"),
+            ("${examples.bashZstdCompressed.imageName}", "${examples.bashZstdCompressed.imageTag}"),
+        ]:
+            docker.succeed(f"docker images --format '{{{{.Repository}}}}-{{{{.Tag}}}}' | grep -F '{sub_image}-{tag}'")
+            docker.succeed(f"docker rmi {sub_image}")
+
+
     with subtest("exportImage produces a valid tarball"):
         docker.succeed(
             "tar -tf ${examples.exportBash} | grep '\./bin/bash' > /dev/null"
@@ -565,5 +612,17 @@ in {
             "${examples.nix-shell-build-derivation} | docker load",
             "docker run --rm -it nix-shell-build-derivation"
         )
+
+    with subtest("streamLayeredImage: chown is persistent in fakeRootCommands"):
+        docker.succeed(
+            "${chownTestImage} | docker load",
+            "docker run --rm ${chownTestImage.imageName} | diff /dev/stdin <(echo 12345:12345)"
+        )
+
+    with subtest("streamLayeredImage: with non-root user"):
+        docker.succeed(
+            "${nonRootTestImage} | docker load",
+            "docker run --rm ${chownTestImage.imageName} | diff /dev/stdin <(echo 12345:12345)"
+        )
   '';
 })
diff --git a/nixos/tests/geoserver.nix b/nixos/tests/geoserver.nix
index 7e5507a296ea..4f6f2b209d07 100644
--- a/nixos/tests/geoserver.nix
+++ b/nixos/tests/geoserver.nix
@@ -1,4 +1,18 @@
-{ pkgs, lib, ... }: {
+{ pkgs, lib, ... }:
+
+let
+  geoserver = pkgs.geoserver;
+  geoserverWithImporterExtension = pkgs.geoserver.withExtensions (ps: with ps; [ importer ]);
+
+  # Blacklisted extensions:
+  # - wps-jdbc needs a running (Postrgres) db server.
+  blacklist = [ "wps-jdbc" ];
+
+  blacklistedToNull = n: v: if ! builtins.elem n blacklist then v else null;
+  getNonBlackistedExtensionsAsList = ps: builtins.filter (x: x != null) (lib.attrsets.mapAttrsToList blacklistedToNull ps);
+  geoserverWithAllExtensions = pkgs.geoserver.withExtensions (ps: getNonBlackistedExtensionsAsList ps);
+in
+{
 
   name = "geoserver";
   meta = {
@@ -9,16 +23,57 @@
     machine = { pkgs, ... }: {
       virtualisation.diskSize = 2 * 1024;
 
-      environment.systemPackages = [ pkgs.geoserver ];
+      environment.systemPackages = [
+        geoserver
+        geoserverWithImporterExtension
+        geoserverWithAllExtensions
+      ];
     };
   };
 
   testScript = ''
+    from contextlib import contextmanager
+
+    curl_cmd = "curl --fail --connect-timeout 2"
+    curl_cmd_rest = f"{curl_cmd} -u admin:geoserver -X GET"
+    base_url = "http://localhost:8080/geoserver"
+    log_file = "./log.txt"
+
+    @contextmanager
+    def running_geoserver(pkg):
+      try:
+        print(f"Launching geoserver from {pkg}...")
+        machine.execute(f"{pkg}/bin/geoserver-startup > {log_file} 2>&1 &")
+        machine.wait_until_succeeds(f"{curl_cmd} {base_url} 2>&1", timeout=60)
+        yield
+      finally:
+        # We need to wait a little bit to make sure the server is properly
+        # shutdown before launching a new instance.
+        machine.execute(f"{pkg}/bin/geoserver-shutdown; sleep 1")
+
     start_all()
 
-    machine.execute("${pkgs.geoserver}/bin/geoserver-startup > /dev/null 2>&1 &")
-    machine.wait_until_succeeds("curl --fail --connect-timeout 2 http://localhost:8080/geoserver", timeout=60)
+    with running_geoserver("${geoserver}"):
+      machine.succeed(f"{curl_cmd} {base_url}/ows?service=WMS&version=1.3.0&request=GetCapabilities")
+
+      # No extensions yet.
+      machine.fail(f"{curl_cmd_rest} {base_url}/rest/imports")
+      machine.fail(f"{curl_cmd_rest} {base_url}/rest/monitor/requests.csv")
+
+
+    with running_geoserver("${geoserverWithImporterExtension}"):
+      machine.succeed(f"{curl_cmd_rest} {base_url}/rest/imports")
+      machine.fail(f"{curl_cmd_rest} {base_url}/rest/monitor/requests.csv")
+
+    with running_geoserver("${geoserverWithAllExtensions}"):
+      machine.succeed(f"{curl_cmd_rest} {base_url}/rest/imports")
+      machine.succeed(f"{curl_cmd_rest} {base_url}/rest/monitor/requests.csv")
+      _, stdout = machine.execute(f"cat {log_file}")
+      print(stdout.replace("\\n", "\n"))
+      assert "GDAL Native Library loaded" in stdout, "gdal"
+      assert "The turbo jpeg encoder is available for usage" in stdout, "libjpeg-turbo"
+      assert "org.geotools.imageio.netcdf.utilities.NetCDFUtilities" in stdout, "netcdf"
+      assert "Unable to load library 'netcdf'" not in stdout, "netcdf"
 
-    machine.succeed("curl --fail --connect-timeout 2 http://localhost:8080/geoserver/ows?service=WMS&version=1.3.0&request=GetCapabilities")
   '';
 }
diff --git a/nixos/tests/go-camo.nix b/nixos/tests/go-camo.nix
new file mode 100644
index 000000000000..513964c31c43
--- /dev/null
+++ b/nixos/tests/go-camo.nix
@@ -0,0 +1,30 @@
+{ system ? builtins.currentSystem, config ? { }
+, pkgs ? import ../.. { inherit system config; } }:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+
+{
+  gocamo_file_key = let
+      key_val = "12345678";
+    in
+    makeTest {
+    name = "go-camo-file-key";
+    meta = {
+      maintainers = [ pkgs.lib.maintainers.viraptor ];
+    };
+
+    nodes.machine = { config, pkgs, ... }: {
+      services.go-camo = {
+        enable = true;
+        keyFile = pkgs.writeText "foo" key_val;
+      };
+    };
+
+    # go-camo responds to http requests
+    testScript = ''
+      machine.wait_for_unit("go-camo.service")
+      machine.wait_for_open_port(8080)
+      machine.succeed("curl http://localhost:8080")
+    '';
+  };
+}
diff --git a/nixos/tests/incus/container.nix b/nixos/tests/incus/container.nix
index 0e65cc1e1529..eb00429e53fe 100644
--- a/nixos/tests/incus/container.nix
+++ b/nixos/tests/incus/container.nix
@@ -1,11 +1,13 @@
-import ../make-test-python.nix ({ pkgs, lib, ... } :
+import ../make-test-python.nix ({ pkgs, lib, extra ? {}, ... } :
 
 let
   releases = import ../../release.nix {
     configuration = {
       # Building documentation makes the test unnecessarily take a longer time:
       documentation.enable = lib.mkForce false;
-    };
+
+      boot.kernel.sysctl."net.ipv4.ip_forward" = "1";
+    } // extra;
   };
 
   container-image-metadata = releases.lxdContainerMeta.${pkgs.stdenv.hostPlatform.system};
@@ -40,6 +42,12 @@ in
         with machine.nested("Waiting for instance to start and be usable"):
           retry(instance_is_up)
 
+    def check_sysctl(instance):
+        with subtest("systemd sysctl settings are applied"):
+            machine.succeed(f"incus exec {instance} -- systemctl status systemd-sysctl")
+            sysctl = machine.succeed(f"incus exec {instance} -- sysctl net.ipv4.ip_forward").strip().split(" ")[-1]
+            assert "1" == sysctl, f"systemd-sysctl configuration not correctly applied, {sysctl} != 1"
+
     machine.wait_for_unit("incus.service")
 
     # no preseed should mean no service
@@ -83,6 +91,7 @@ in
     with subtest("lxc-container generator configures plain container"):
         # reuse the existing container to save some time
         machine.succeed("incus exec container test -- -e /run/systemd/system/service.d/zzz-lxc-service.conf")
+        check_sysctl("container")
 
     with subtest("lxc-container generator configures nested container"):
         machine.execute("incus delete --force container")
@@ -94,6 +103,8 @@ in
         target = machine.succeed("incus exec container readlink -- -f /run/systemd/system/systemd-binfmt.service").strip()
         assert target == "/dev/null", "lxc generator did not correctly mask /run/systemd/system/systemd-binfmt.service"
 
+        check_sysctl("container")
+
     with subtest("lxc-container generator configures privileged container"):
         machine.execute("incus delete --force container")
         machine.succeed("incus launch nixos container --config security.privileged=true")
@@ -101,5 +112,7 @@ in
           retry(instance_is_up)
 
         machine.succeed("incus exec container test -- -e /run/systemd/system/service.d/zzz-lxc-service.conf")
+
+        check_sysctl("container")
   '';
 })
diff --git a/nixos/tests/incus/default.nix b/nixos/tests/incus/default.nix
index 26e8a4ac4c77..ff36fe9d6730 100644
--- a/nixos/tests/incus/default.nix
+++ b/nixos/tests/incus/default.nix
@@ -5,9 +5,14 @@
   handleTestOn,
 }:
 {
-  container = import ./container.nix { inherit system pkgs; };
+  container-old-init = import ./container.nix { inherit system pkgs; };
+  container-new-init = import ./container.nix { inherit system pkgs; extra = {
+    # Enable new systemd init
+    boot.initrd.systemd.enable = true;
+  }; };
   lxd-to-incus = import ./lxd-to-incus.nix { inherit system pkgs; };
   preseed = import ./preseed.nix { inherit system pkgs; };
   socket-activated = import ./socket-activated.nix { inherit system pkgs; };
+  ui = import ./ui.nix {inherit system pkgs;};
   virtual-machine = handleTestOn [ "x86_64-linux" ] ./virtual-machine.nix { inherit system pkgs; };
 }
diff --git a/nixos/tests/incus/ui.nix b/nixos/tests/incus/ui.nix
new file mode 100644
index 000000000000..24ce1217d8df
--- /dev/null
+++ b/nixos/tests/incus/ui.nix
@@ -0,0 +1,63 @@
+import ../make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "incus-ui";
+
+  meta = {
+    maintainers = lib.teams.lxc.members;
+  };
+
+  nodes.machine = { lib, ... }: {
+    virtualisation = {
+      incus.enable = true;
+      incus.ui.enable = true;
+    };
+
+    environment.systemPackages =
+      let
+        seleniumScript = pkgs.writers.writePython3Bin "selenium-script"
+          {
+            libraries = with pkgs.python3Packages; [ selenium ];
+          } ''
+          from selenium import webdriver
+          from selenium.webdriver.common.by import By
+          from selenium.webdriver.firefox.options import Options
+          from selenium.webdriver.support.ui import WebDriverWait
+
+          options = Options()
+          options.add_argument("--headless")
+          service = webdriver.FirefoxService(executable_path="${lib.getExe pkgs.geckodriver}")  # noqa: E501
+
+          driver = webdriver.Firefox(options=options, service=service)
+          driver.implicitly_wait(10)
+          driver.get("https://localhost:8443/ui")
+
+          wait = WebDriverWait(driver, 60)
+
+          assert len(driver.find_elements(By.CLASS_NAME, "l-application")) > 0
+          assert len(driver.find_elements(By.CLASS_NAME, "l-navigation__drawer")) > 0
+
+          driver.close()
+        '';
+      in
+      with pkgs; [ curl firefox-unwrapped geckodriver seleniumScript ];
+  };
+
+
+  testScript = ''
+    machine.wait_for_unit("sockets.target")
+    machine.wait_for_unit("incus.service")
+    machine.wait_for_file("/var/lib/incus/unix.socket")
+
+    # Configure incus listen address
+    machine.succeed("incus config set core.https_address :8443")
+    machine.succeed("systemctl restart incus")
+
+    # Check that the INCUS_UI environment variable is populated in the systemd unit
+    machine.succeed("cat /etc/systemd/system/incus.service | grep 'INCUS_UI'")
+
+    # Ensure the endpoint returns an HTML page with 'Incus UI' in the title
+    machine.succeed("curl -kLs https://localhost:8443/ui | grep '<title>Incus UI</title>'")
+
+    # Ensure the application is actually rendered by the Javascript
+    machine.succeed("PYTHONUNBUFFERED=1 selenium-script")
+  '';
+})
diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix
index 7576fae41f83..97bb7f8def59 100644
--- a/nixos/tests/installer.nix
+++ b/nixos/tests/installer.nix
@@ -83,46 +83,34 @@ let
                   , postInstallCommands, preBootCommands, postBootCommands, extraConfig
                   , testSpecialisationConfig, testFlakeSwitch, clevisTest, clevisFallbackTest
                   }:
-    let iface = "virtio";
-        isEfi = bootLoader == "systemd-boot" || (bootLoader == "grub" && grubUseEfi);
-        bios  = if pkgs.stdenv.isAarch64 then "QEMU_EFI.fd" else "OVMF.fd";
+    let
+      qemu-common = import ../lib/qemu-common.nix { inherit (pkgs) lib pkgs; };
+      isEfi = bootLoader == "systemd-boot" || (bootLoader == "grub" && grubUseEfi);
+      qemu = qemu-common.qemuBinary pkgs.qemu_test;
     in if !isEfi && !pkgs.stdenv.hostPlatform.isx86 then ''
       machine.succeed("true")
     '' else ''
+      import os
       import subprocess
-      tpm_folder = os.environ['NIX_BUILD_TOP']
-      def assemble_qemu_flags():
-          flags = "-cpu max"
-          ${if (system == "x86_64-linux" || system == "i686-linux")
-            then ''flags += " -m 1024"''
-            else ''flags += " -m 768 -enable-kvm -machine virt,gic-version=host"''
-          }
-          ${optionalString clevisTest ''flags += f" -chardev socket,id=chrtpm,path={tpm_folder}/swtpm-sock -tpmdev emulator,id=tpm0,chardev=chrtpm -device tpm-tis,tpmdev=tpm0"''}
-          ${optionalString clevisTest ''flags += " -device virtio-net-pci,netdev=vlan1,mac=52:54:00:12:11:02 -netdev vde,id=vlan1,sock=\"$QEMU_VDE_SOCKET_1\""''}
-          return flags
 
+      tpm_folder = os.environ['NIX_BUILD_TOP']
 
-      qemu_flags = {"qemuFlags": assemble_qemu_flags()}
+      startcommand = "${qemu} -m 2048"
 
-      import os
+      ${optionalString clevisTest ''
+        startcommand += f" -chardev socket,id=chrtpm,path={tpm_folder}/swtpm-sock -tpmdev emulator,id=tpm0,chardev=chrtpm -device tpm-tis,tpmdev=tpm0"
+        startcommand += " -device virtio-net-pci,netdev=vlan1,mac=52:54:00:12:11:02 -netdev vde,id=vlan1,sock=\"$QEMU_VDE_SOCKET_1\""
+      ''}
+      ${optionalString isEfi ''
+        startcommand +=" -drive if=pflash,format=raw,unit=0,readonly=on,file=${pkgs.OVMF.firmware} -drive if=pflash,format=raw,unit=1,readonly=on,file=${pkgs.OVMF.variables}"
+      ''}
 
       image_dir = machine.state_dir
       disk_image = os.path.join(image_dir, "machine.qcow2")
-
-      hd_flags = {
-          "hdaInterface": "${iface}",
-          "hda": disk_image,
-      }
-      ${optionalString isEfi ''
-        hd_flags.update(
-            bios="${pkgs.OVMF.fd}/FV/${bios}"
-        )''
-      }
-      default_flags = {**hd_flags, **qemu_flags}
-
+      startcommand += f" -drive file={disk_image},if=virtio,werror=report"
 
       def create_machine_named(name):
-          return create_machine({**default_flags, "name": name})
+          return create_machine(startcommand, name=name)
 
       class Tpm:
             def __init__(self):
@@ -471,7 +459,7 @@ let
           # builds stuff in the VM, needs more juice
           virtualisation.diskSize = 8 * 1024;
           virtualisation.cores = 8;
-          virtualisation.memorySize = 1536;
+          virtualisation.memorySize = 2048;
 
           boot.initrd.systemd.enable = systemdStage1;
 
@@ -526,8 +514,7 @@ let
             curl
           ]
           ++ optionals (bootLoader == "grub") (let
-            zfsSupport = lib.any (x: x == "zfs")
-              (extraInstallerConfig.boot.supportedFilesystems or []);
+            zfsSupport = extraInstallerConfig.boot.supportedFilesystems.zfs or false;
           in [
             (pkgs.grub2.override { inherit zfsSupport; })
             (pkgs.grub2_efi.override { inherit zfsSupport; })
diff --git a/nixos/tests/k3s/default.nix b/nixos/tests/k3s/default.nix
index e168f8233c76..512dc06ee77e 100644
--- a/nixos/tests/k3s/default.nix
+++ b/nixos/tests/k3s/default.nix
@@ -6,6 +6,11 @@ let
   allK3s = lib.filterAttrs (n: _: lib.strings.hasPrefix "k3s_" n) pkgs;
 in
 {
+  # Testing K3s with Etcd backend
+  etcd = lib.mapAttrs (_: k3s: import ./etcd.nix {
+    inherit system pkgs k3s;
+    inherit (pkgs) etcd;
+  }) allK3s;
   # Run a single node k3s cluster and verify a pod can run
   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
diff --git a/nixos/tests/k3s/etcd.nix b/nixos/tests/k3s/etcd.nix
new file mode 100644
index 000000000000..d6e9a294adb1
--- /dev/null
+++ b/nixos/tests/k3s/etcd.nix
@@ -0,0 +1,100 @@
+import ../make-test-python.nix ({ pkgs, lib, k3s, etcd, ... }:
+
+{
+  name = "${k3s.name}-etcd";
+
+  nodes = {
+
+    etcd = { ... }: {
+      services.etcd = {
+        enable = true;
+        openFirewall = true;
+        listenClientUrls = [ "http://192.168.1.1:2379" "http://127.0.0.1:2379" ];
+        listenPeerUrls = [ "http://192.168.1.1:2380" ];
+        initialAdvertisePeerUrls = [ "http://192.168.1.1:2380" ];
+        initialCluster = [ "etcd=http://192.168.1.1:2380" ];
+      };
+      networking = {
+        useDHCP = false;
+        defaultGateway = "192.168.1.1";
+        interfaces.eth1.ipv4.addresses = pkgs.lib.mkForce [
+          { address = "192.168.1.1"; prefixLength = 24; }
+        ];
+      };
+    };
+
+    k3s = { pkgs, ... }: {
+      environment.systemPackages = with pkgs; [ jq ];
+      # k3s uses enough resources the default vm fails.
+      virtualisation.memorySize = 1536;
+      virtualisation.diskSize = 4096;
+
+      services.k3s = {
+        enable = true;
+        role = "server";
+        extraFlags = builtins.toString [
+          "--datastore-endpoint=\"http://192.168.1.1:2379\""
+          "--disable" "coredns"
+          "--disable" "local-storage"
+          "--disable" "metrics-server"
+          "--disable" "servicelb"
+          "--disable" "traefik"
+          "--node-ip" "192.168.1.2"
+        ];
+      };
+
+      networking = {
+        firewall = {
+          allowedTCPPorts = [ 2379 2380 6443 ];
+          allowedUDPPorts = [ 8472 ];
+        };
+        useDHCP = false;
+        defaultGateway = "192.168.1.2";
+        interfaces.eth1.ipv4.addresses = pkgs.lib.mkForce [
+          { address = "192.168.1.2"; prefixLength = 24; }
+        ];
+      };
+    };
+
+  };
+
+  testScript = ''
+    with subtest("should start etcd"):
+        etcd.start()
+        etcd.wait_for_unit("etcd.service")
+
+    with subtest("should wait for etcdctl endpoint status to succeed"):
+        etcd.wait_until_succeeds("etcdctl endpoint status")
+
+    with subtest("should start k3s"):
+        k3s.start()
+        k3s.wait_for_unit("k3s")
+
+    with subtest("should test if kubectl works"):
+        k3s.wait_until_succeeds("k3s kubectl get node")
+
+    with subtest("should wait for service account to show up; takes a sec"):
+        k3s.wait_until_succeeds("k3s kubectl get serviceaccount default")
+
+    with subtest("should create a sample secret object"):
+        k3s.succeed("k3s kubectl create secret generic nixossecret --from-literal thesecret=abacadabra")
+
+    with subtest("should check if secret is correct"):
+        k3s.wait_until_succeeds("[[ $(kubectl get secrets nixossecret -o json | jq -r .data.thesecret | base64 -d) == abacadabra ]]")
+
+    with subtest("should have a secret in database"):
+        etcd.wait_until_succeeds("[[ $(etcdctl get /registry/secrets/default/nixossecret | head -c1 | wc -c) -ne 0 ]]")
+
+    with subtest("should delete the secret"):
+        k3s.succeed("k3s kubectl delete secret nixossecret")
+
+    with subtest("should not have a secret in database"):
+        etcd.wait_until_fails("[[ $(etcdctl get /registry/secrets/default/nixossecret | head -c1 | wc -c) -ne 0 ]]")
+
+    with subtest("should shutdown k3s and etcd"):
+        k3s.shutdown()
+        etcd.shutdown()
+  '';
+
+  meta.maintainers = etcd.meta.maintainers ++ k3s.meta.maintainers;
+})
diff --git a/nixos/tests/keepalived.nix b/nixos/tests/keepalived.nix
index ce291514591f..16564511d85d 100644
--- a/nixos/tests/keepalived.nix
+++ b/nixos/tests/keepalived.nix
@@ -1,6 +1,6 @@
 import ./make-test-python.nix ({ pkgs, lib, ... }: {
   name = "keepalived";
-  maintainers = [ lib.maintainers.raitobezarius ];
+  meta.maintainers = [ lib.maintainers.raitobezarius ];
 
   nodes = {
     node1 = { pkgs, ... }: {
diff --git a/nixos/tests/kernel-generic.nix b/nixos/tests/kernel-generic.nix
index 0dcab39f3fad..9714a94382ee 100644
--- a/nixos/tests/kernel-generic.nix
+++ b/nixos/tests/kernel-generic.nix
@@ -30,7 +30,6 @@ let
       linux_5_10_hardened
       linux_5_15_hardened
       linux_6_1_hardened
-      linux_6_5_hardened
       linux_6_6_hardened
       linux_6_7_hardened
       linux_rt_5_4
diff --git a/nixos/tests/lomiri-system-settings.nix b/nixos/tests/lomiri-system-settings.nix
new file mode 100644
index 000000000000..867fc14797e7
--- /dev/null
+++ b/nixos/tests/lomiri-system-settings.nix
@@ -0,0 +1,99 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "lomiri-system-settings-standalone";
+  meta.maintainers = lib.teams.lomiri.members;
+
+  nodes.machine = { config, pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+
+    environment = {
+      systemPackages = with pkgs.lomiri; [
+        suru-icon-theme
+        lomiri-system-settings
+      ];
+      variables = {
+        UITK_ICON_THEME = "suru";
+      };
+    };
+
+    i18n.supportedLocales = [ "all" ];
+
+    fonts.packages = with pkgs; [
+      # Intended font & helps with OCR
+      ubuntu_font_family
+    ];
+
+    services.upower.enable = true;
+  };
+
+  enableOCR = true;
+
+  testScript = let
+    settingsPages = [
+      # Base pages
+      { name = "wifi"; type = "internal"; element = "networks"; }
+      { name = "bluetooth"; type = "internal"; element = "discoverable|None detected"; }
+      # only text we can really look for with VPN is on a button, OCR on CI struggles with it
+      { name = "vpn"; type = "internal"; element = "Add|Manual|Configuration"; skipOCR = true; }
+      { name = "appearance"; type = "internal"; element = "Background image|blur effects"; }
+      { name = "desktop"; type = "internal"; element = "workspaces|Icon size"; }
+      { name = "sound"; type = "internal"; element = "Silent Mode|Message sound"; }
+      { name = "language"; type = "internal"; element = "Display language|External keyboard"; }
+      { name = "notification"; type = "internal"; element = "Apps that notify"; }
+      { name = "gestures"; type = "internal"; element = "Edge drag"; }
+      { name = "mouse"; type = "internal"; element = "Cursor speed|Wheel scrolling speed"; }
+      { name = "timedate"; type = "internal"; element = "Time zone|Set the time and date"; }
+
+      # External plugins
+      { name = "security-privacy"; type = "external"; element = "Locking|unlocking|permissions"; elementLocalised = "Sperren|Entsperren|Berechtigungen"; }
+    ];
+  in
+  ''
+    machine.wait_for_x()
+
+    with subtest("lomiri system settings launches"):
+        machine.execute("lomiri-system-settings >&2 &")
+        machine.wait_for_text("System Settings")
+        machine.screenshot("lss_open")
+
+    # Move focus to start of plugins list for following list of tests
+    machine.send_key("tab")
+    machine.send_key("tab")
+    machine.screenshot("lss_focus")
+
+    # tab through & open all sub-menus, to make sure none of them fail
+  '' + (lib.strings.concatMapStringsSep "\n" (page: ''
+    machine.send_key("tab")
+    machine.send_key("kp_enter")
+  ''
+  + lib.optionalString (!(page.skipOCR or false)) ''
+    with subtest("lomiri system settings ${page.name} works"):
+        machine.wait_for_text(r"(${page.element})")
+        machine.screenshot("lss_page_${page.name}")
+  '') settingsPages) + ''
+
+    machine.execute("pkill -f lomiri-system-settings")
+
+    with subtest("lomiri system settings localisation works"):
+        machine.execute("env LANG=de_DE.UTF-8 lomiri-system-settings >&2 &")
+        machine.wait_for_text("Systemeinstellungen")
+        machine.screenshot("lss_localised_open")
+
+    # Move focus to start of plugins list for following list of tests
+    machine.send_key("tab")
+    machine.send_key("tab")
+    machine.screenshot("lss_focus_localised")
+
+  '' + (lib.strings.concatMapStringsSep "\n" (page: ''
+    machine.send_key("tab")
+    machine.send_key("kp_enter")
+  '' + lib.optionalString (page.type == "external") ''
+    with subtest("lomiri system settings ${page.name} localisation works"):
+        machine.wait_for_text(r"(${page.elementLocalised})")
+        machine.screenshot("lss_localised_page_${page.name}")
+  '') settingsPages) + ''
+  '';
+})
diff --git a/nixos/tests/matomo.nix b/nixos/tests/matomo.nix
index 7dbef63136aa..130f3dd8485a 100644
--- a/nixos/tests/matomo.nix
+++ b/nixos/tests/matomo.nix
@@ -47,4 +47,8 @@ in {
     name = "matomo-beta";
     meta.maintainers = with maintainers; [ florianjacob kiwi mmilata twey boozedog ];
   };
+  matomo_5 = matomoTest pkgs.matomo_5 // {
+    name = "matomo-5";
+    meta.maintainers = with maintainers; [ florianjacob kiwi mmilata twey boozedog ] ++ lib.teams.flyingcircus.members;
+  };
 }
diff --git a/nixos/tests/matter-server.nix b/nixos/tests/matter-server.nix
new file mode 100644
index 000000000000..c646e9840d19
--- /dev/null
+++ b/nixos/tests/matter-server.nix
@@ -0,0 +1,45 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} :
+
+let
+  chipVersion = pkgs.python311Packages.home-assistant-chip-core.version;
+in
+
+{
+  name = "matter-server";
+  meta.maintainers = with lib.maintainers; [ leonm1 ];
+
+  nodes = {
+    machine = { config, ... }: {
+      services.matter-server = {
+        enable = true;
+        port = 1234;
+      };
+    };
+  };
+
+  testScript = /* python */ ''
+    start_all()
+
+    machine.wait_for_unit("matter-server.service")
+    machine.wait_for_open_port(1234)
+
+    with subtest("Check websocket server initialized"):
+        output = machine.succeed("echo \"\" | ${pkgs.websocat}/bin/websocat ws://localhost:1234/ws")
+        machine.log(output)
+
+    assert '"sdk_version": "${chipVersion}"' in output, (
+      'CHIP version \"${chipVersion}\" not present in websocket message'
+    )
+
+    assert '"fabric_id": 1' in output, (
+      "fabric_id not propagated to server"
+    )
+
+    with subtest("Check storage directory is created"):
+        machine.succeed("ls /var/lib/matter-server/chip.json")
+
+    with subtest("Check systemd hardening"):
+        _, output = machine.execute("systemd-analyze security matter-server.service | grep -v '✓'")
+        machine.log(output)
+  '';
+})
diff --git a/nixos/tests/mealie.nix b/nixos/tests/mealie.nix
new file mode 100644
index 000000000000..88f749c71294
--- /dev/null
+++ b/nixos/tests/mealie.nix
@@ -0,0 +1,24 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+
+{
+  name = "mealie";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ litchipi ];
+  };
+
+  nodes = {
+    server = {
+      services.mealie = {
+        enable = true;
+        port = 9001;
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    server.wait_for_unit("mealie.service")
+    server.wait_for_open_port(9001)
+    server.succeed("curl --fail http://localhost:9001")
+  '';
+})
diff --git a/nixos/tests/minio.nix b/nixos/tests/minio.nix
index ece4864f771c..67eb0cd88440 100644
--- a/nixos/tests/minio.nix
+++ b/nixos/tests/minio.nix
@@ -43,17 +43,17 @@ import ./make-test-python.nix ({ pkgs, ... }:
 
         # Minio requires at least 1GiB of free disk space to run.
         virtualisation.diskSize = 4 * 1024;
+
+        # Minio pre allocates 2GiB or memory, reserve some more
+        virtualisation.memorySize = 4096;
       };
     };
 
     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}")
 
       machine.wait_for_unit("minio.service")
diff --git a/nixos/tests/monado.nix b/nixos/tests/monado.nix
new file mode 100644
index 000000000000..8368950951e7
--- /dev/null
+++ b/nixos/tests/monado.nix
@@ -0,0 +1,39 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "monado";
+
+  nodes.machine =
+    { pkgs, ... }:
+
+    {
+      hardware.opengl.enable = true;
+      users.users.alice = {
+        isNormalUser = true;
+        uid = 1000;
+      };
+
+      services.monado = {
+        enable = true;
+        defaultRuntime = true;
+      };
+      # Stop Monado from probing for any hardware
+      systemd.user.services.monado.environment.SIMULATED_ENABLE = "1";
+
+      environment.systemPackages = with pkgs; [ openxr-loader ];
+    };
+
+  testScript = { nodes, ... }:
+    let
+      userId = toString nodes.machine.users.users.alice.uid;
+      runtimePath = "/run/user/${userId}";
+    in
+    ''
+      machine.succeed("loginctl enable-linger alice")
+      machine.wait_for_unit("user@${userId}.service")
+
+      machine.wait_for_unit("monado.socket", "alice")
+      machine.systemctl("start monado.service", "alice")
+      machine.wait_for_unit("monado.service", "alice")
+
+      machine.succeed("su -- alice -c env XDG_RUNTIME_DIR=${runtimePath} openxr_runtime_list")
+    '';
+})
diff --git a/nixos/tests/morph-browser.nix b/nixos/tests/morph-browser.nix
new file mode 100644
index 000000000000..859e6bb47646
--- /dev/null
+++ b/nixos/tests/morph-browser.nix
@@ -0,0 +1,53 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "morph-browser-standalone";
+  meta.maintainers = lib.teams.lomiri.members;
+
+  nodes.machine = { config, pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+
+    environment = {
+      systemPackages = with pkgs.lomiri; [
+        suru-icon-theme
+        morph-browser
+      ];
+      variables = {
+        UITK_ICON_THEME = "suru";
+      };
+    };
+
+    i18n.supportedLocales = [ "all" ];
+
+    fonts.packages = with pkgs; [
+      # Intended font & helps with OCR
+      ubuntu_font_family
+    ];
+  };
+
+  enableOCR = true;
+
+  testScript =
+    ''
+      machine.wait_for_x()
+
+      with subtest("morph browser launches"):
+          machine.execute("morph-browser >&2 &")
+          machine.wait_for_text(r"Web Browser|New|sites|Bookmarks")
+          machine.screenshot("morph_open")
+
+      with subtest("morph browser displays HTML"):
+          machine.send_chars("file://${pkgs.valgrind.doc}/share/doc/valgrind/html/index.html\n")
+          machine.wait_for_text("Valgrind Documentation")
+          machine.screenshot("morph_htmlcontent")
+
+      machine.succeed("pkill -f morph-browser")
+
+      with subtest("morph browser localisation works"):
+          machine.execute("env LANG=de_DE.UTF-8 morph-browser >&2 &")
+          machine.wait_for_text(r"Web-Browser|Neuer|Seiten|Lesezeichen")
+          machine.screenshot("morph_localised")
+    '';
+})
diff --git a/nixos/tests/nextcloud/with-postgresql-and-redis.nix b/nixos/tests/nextcloud/with-postgresql-and-redis.nix
index 3c090f0d3c3b..06afc589403d 100644
--- a/nixos/tests/nextcloud/with-postgresql-and-redis.nix
+++ b/nixos/tests/nextcloud/with-postgresql-and-redis.nix
@@ -39,7 +39,7 @@ in {
         };
         extraAppsEnable = true;
         extraApps = {
-          inherit (pkgs."nextcloud${lib.versions.major config.services.nextcloud.package.version}Packages".apps) notify_push;
+          inherit (pkgs."nextcloud${lib.versions.major config.services.nextcloud.package.version}Packages".apps) notify_push notes;
         };
         settings.trusted_proxies = [ "::1" ];
       };
@@ -84,7 +84,7 @@ 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.execute("${pkgs.lib.getExe pkgs.nextcloud-notify_push.passthru.test_client} http://nextcloud ${adminuser} ${adminpass} >&2 &")
     client.succeed(
         "${withRcloneEnv} ${diffSharedFile}"
     )
@@ -92,5 +92,7 @@ in {
 
     # redis cache should not be empty
     nextcloud.fail('test "[]" = "$(redis-cli --json KEYS "*")"')
+
+    nextcloud.fail("curl -f http://nextcloud/nix-apps/notes/lib/AppInfo/Application.php")
   '';
 })) args
diff --git a/nixos/tests/plasma6.nix b/nixos/tests/plasma6.nix
new file mode 100644
index 000000000000..ec5b3f24ef74
--- /dev/null
+++ b/nixos/tests/plasma6.nix
@@ -0,0 +1,64 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+
+{
+  name = "plasma6";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ k900 ];
+  };
+
+  nodes.machine = { ... }:
+
+  {
+    imports = [ ./common/user-account.nix ];
+    services.xserver.enable = true;
+    services.xserver.displayManager.sddm.enable = true;
+    # FIXME: this should be testing Wayland
+    services.xserver.displayManager.defaultSession = "plasmax11";
+    services.xserver.desktopManager.plasma6.enable = true;
+    environment.plasma6.excludePackages = [ pkgs.kdePackages.elisa ];
+    services.xserver.displayManager.autoLogin = {
+      enable = true;
+      user = "alice";
+    };
+  };
+
+  testScript = { nodes, ... }: let
+    user = nodes.machine.users.users.alice;
+    xdo = "${pkgs.xdotool}/bin/xdotool";
+  in ''
+    with subtest("Wait for login"):
+        start_all()
+        machine.wait_for_file("/tmp/xauth_*")
+        machine.succeed("xauth merge /tmp/xauth_*")
+
+    with subtest("Check plasmashell started"):
+        machine.wait_until_succeeds("pgrep plasmashell")
+        machine.wait_for_window("^Desktop ")
+
+    with subtest("Check that KDED is running"):
+        machine.succeed("pgrep kded6")
+
+    with subtest("Ensure Elisa is not installed"):
+        machine.fail("which elisa")
+
+    machine.succeed("su - ${user.name} -c 'xauth merge /tmp/xauth_*'")
+
+    with subtest("Run Dolphin"):
+        machine.execute("su - ${user.name} -c 'DISPLAY=:0.0 dolphin >&2 &'")
+        machine.wait_for_window(" Dolphin")
+
+    with subtest("Run Konsole"):
+        machine.execute("su - ${user.name} -c 'DISPLAY=:0.0 konsole >&2 &'")
+        machine.wait_for_window("Konsole")
+
+    with subtest("Run systemsettings"):
+        machine.execute("su - ${user.name} -c 'DISPLAY=:0.0 systemsettings >&2 &'")
+        machine.wait_for_window("Settings")
+
+    with subtest("Wait to get a screenshot"):
+        machine.execute(
+            "${xdo} key Alt+F1 sleep 10"
+        )
+        machine.screenshot("screen")
+  '';
+})
diff --git a/nixos/tests/power-profiles-daemon.nix b/nixos/tests/power-profiles-daemon.nix
index c887cde4b829..8a54d8e8bab8 100644
--- a/nixos/tests/power-profiles-daemon.nix
+++ b/nixos/tests/power-profiles-daemon.nix
@@ -8,22 +8,22 @@ import ./make-test-python.nix ({ pkgs, ... }:
   nodes.machine = { pkgs, ... }: {
     security.polkit.enable = true;
     services.power-profiles-daemon.enable = true;
-    environment.systemPackages = [ pkgs.glib ];
+    environment.systemPackages = [ pkgs.glib pkgs.power-profiles-daemon ];
   };
 
   testScript = ''
     def get_profile():
         return machine.succeed(
-            """gdbus call --system --dest net.hadess.PowerProfiles --object-path /net/hadess/PowerProfiles \
-    --method org.freedesktop.DBus.Properties.Get 'net.hadess.PowerProfiles' 'ActiveProfile'
+            """gdbus call --system --dest org.freedesktop.UPower.PowerProfiles --object-path /org/freedesktop/UPower/PowerProfiles \
+    --method org.freedesktop.DBus.Properties.Get 'org.freedesktop.UPower.PowerProfiles' 'ActiveProfile'
     """
         )
 
 
     def set_profile(profile):
         return machine.succeed(
-            """gdbus call --system --dest net.hadess.PowerProfiles --object-path /net/hadess/PowerProfiles \
-    --method org.freedesktop.DBus.Properties.Set 'net.hadess.PowerProfiles' 'ActiveProfile' "<'{profile}'>"
+            """gdbus call --system --dest org.freedesktop.UPower.PowerProfiles --object-path /org/freedesktop/UPower/PowerProfiles \
+    --method org.freedesktop.DBus.Properties.Set 'org.freedesktop.UPower.PowerProfiles' 'ActiveProfile' "<'{profile}'>"
     """.format(
                 profile=profile
             )
@@ -42,5 +42,16 @@ import ./make-test-python.nix ({ pkgs, ... }:
     profile = get_profile()
     if not "balanced" in profile:
         raise Exception("Unable to set balanced profile")
+
+    # test powerprofilectl CLI
+    machine.succeed("powerprofilesctl set power-saver")
+    profile = get_profile()
+    if not "power-saver" in profile:
+        raise Exception("Unable to set power-saver profile with powerprofilectl")
+
+    machine.succeed("powerprofilesctl set balanced")
+    profile = get_profile()
+    if not "balanced" in profile:
+        raise Exception("Unable to set balanced profile with powerprofilectl")
   '';
 })
diff --git a/nixos/tests/prometheus-exporters.nix b/nixos/tests/prometheus-exporters.nix
index 7e74f27174ec..632656ad5795 100644
--- a/nixos/tests/prometheus-exporters.nix
+++ b/nixos/tests/prometheus-exporters.nix
@@ -218,6 +218,9 @@ let
         services.dnsmasq.enable = true;
       };
       exporterTest = ''
+        wait_for_unit("dnsmasq.service")
+        wait_for_open_port(53)
+        wait_for_file("/var/lib/dnsmasq/dnsmasq.leases")
         wait_for_unit("prometheus-dnsmasq-exporter.service")
         wait_for_open_port(9153)
         succeed("curl -sSf http://localhost:9153/metrics | grep 'dnsmasq_leases 0'")
diff --git a/nixos/tests/qemu-vm-external-disk-image.nix b/nixos/tests/qemu-vm-external-disk-image.nix
index a229fc5e3963..c481159511a0 100644
--- a/nixos/tests/qemu-vm-external-disk-image.nix
+++ b/nixos/tests/qemu-vm-external-disk-image.nix
@@ -69,5 +69,8 @@ in
     os.environ['NIX_DISK_IMAGE'] = tmp_disk_image.name
 
     machine.succeed("findmnt --kernel --source ${rootFsDevice} --target /")
+
+    # Make sure systemd boot didn't clobber this
+    machine.succeed("[ ! -e /homeless-shelter ]")
   '';
 }
diff --git a/nixos/tests/scrutiny.nix b/nixos/tests/scrutiny.nix
new file mode 100644
index 000000000000..33160a6b3088
--- /dev/null
+++ b/nixos/tests/scrutiny.nix
@@ -0,0 +1,70 @@
+import ./make-test-python.nix ({ lib, ... }:
+
+{
+  name = "scrutiny";
+  meta.maintainers = with lib.maintainers; [ jnsgruk ];
+
+  nodes = {
+    machine = { self, pkgs, lib, ... }: {
+      services = {
+        scrutiny.enable = true;
+        scrutiny.collector.enable = true;
+      };
+
+      environment.systemPackages =
+        let
+          seleniumScript = pkgs.writers.writePython3Bin "selenium-script"
+            {
+              libraries = with pkgs.python3Packages; [ selenium ];
+            } ''
+            from selenium import webdriver
+            from selenium.webdriver.common.by import By
+            from selenium.webdriver.firefox.options import Options
+            from selenium.webdriver.support.ui import WebDriverWait
+            from selenium.webdriver.support import expected_conditions as EC
+
+            options = Options()
+            options.add_argument("--headless")
+            service = webdriver.FirefoxService(executable_path="${lib.getExe pkgs.geckodriver}")  # noqa: E501
+
+            driver = webdriver.Firefox(options=options, service=service)
+            driver.implicitly_wait(10)
+            driver.get("http://localhost:8080/web/dashboard")
+
+            wait = WebDriverWait(driver, 10).until(
+              EC.text_to_be_present_in_element(
+                (By.TAG_NAME, "body"), "Drive health at a glance")
+            )
+
+            body_text = driver.find_element(By.TAG_NAME, "body").text
+            assert "Temperature history for each device" in body_text
+
+            driver.close()
+          '';
+        in
+        with pkgs; [ curl firefox-unwrapped geckodriver seleniumScript ];
+    };
+  };
+  # This is the test code that will check if our service is running correctly:
+  testScript = ''
+    start_all()
+
+    # Wait for InfluxDB to be available
+    machine.wait_for_unit("influxdb2")
+    machine.wait_for_open_port(8086)
+
+    # Wait for Scrutiny to be available
+    machine.wait_for_unit("scrutiny")
+    machine.wait_for_open_port(8080)
+
+    # Ensure the API responds as we expect
+    output = machine.succeed("curl localhost:8080/api/health")
+    assert output == '{"success":true}'
+
+    # Start the collector service to send some metrics
+    collect = machine.succeed("systemctl start scrutiny-collector.service")
+
+    # Ensure the application is actually rendered by the Javascript
+    machine.succeed("PYTHONUNBUFFERED=1 selenium-script")
+  '';
+})
diff --git a/nixos/tests/searx.nix b/nixos/tests/searx.nix
index 2f808cb65266..02a88f690db7 100644
--- a/nixos/tests/searx.nix
+++ b/nixos/tests/searx.nix
@@ -36,7 +36,7 @@ import ./make-test-python.nix ({ pkgs, ...} :
   };
 
   # fancy setup: run in uWSGI and use nginx as proxy
-  nodes.fancy = { ... }: {
+  nodes.fancy = { config, ... }: {
     imports = [ ../modules/profiles/minimal.nix ];
 
     services.searx = {
@@ -65,7 +65,7 @@ import ./make-test-python.nix ({ pkgs, ...} :
           include ${pkgs.nginx}/conf/uwsgi_params;
           uwsgi_pass unix:/run/searx/uwsgi.sock;
         '';
-      locations."/searx/static/".alias = "${pkgs.searx}/share/static/";
+      locations."/searx/static/".alias = "${config.services.searx.package}/share/static/";
     };
 
     # allow nginx access to the searx socket
@@ -108,7 +108,7 @@ import ./make-test-python.nix ({ pkgs, ...} :
               "${pkgs.curl}/bin/curl --fail http://localhost/searx >&2"
           )
           fancy.succeed(
-              "${pkgs.curl}/bin/curl --fail http://localhost/searx/static/themes/oscar/js/bootstrap.min.js >&2"
+              "${pkgs.curl}/bin/curl --fail http://localhost/searx/static/themes/simple/js/leaflet.js >&2"
           )
     '';
 })
diff --git a/nixos/tests/sourcehut.nix b/nixos/tests/sourcehut.nix
deleted file mode 100644
index 0b258acc2af1..000000000000
--- a/nixos/tests/sourcehut.nix
+++ /dev/null
@@ -1,252 +0,0 @@
-import ./make-test-python.nix ({ pkgs, lib, ... }:
-let
-  domain = "sourcehut.localdomain";
-
-  # Note that wildcard certificates just under the TLD (eg. *.com)
-  # would be rejected by clients like curl.
-  tls-cert = pkgs.runCommand "selfSignedCerts" { buildInputs = [ pkgs.openssl ]; } ''
-    openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -days 36500 \
-      -subj '/CN=${domain}' -extensions v3_req \
-      -addext 'subjectAltName = DNS:*.${domain}'
-    install -D -t $out key.pem cert.pem
-  '';
-
-  images = {
-    nixos.unstable.x86_64 =
-      let
-        systemConfig = { pkgs, ... }: {
-          # passwordless ssh server
-          services.openssh = {
-            enable = true;
-            settings = {
-              PermitRootLogin = "yes";
-              PermitEmptyPasswords = true;
-            };
-          };
-
-          users = {
-            mutableUsers = false;
-            # build user
-            extraUsers."build" = {
-              isNormalUser = true;
-              uid = 1000;
-              extraGroups = [ "wheel" ];
-              password = "";
-            };
-            users.root.password = "";
-          };
-
-          security.sudo.wheelNeedsPassword = false;
-          nix.settings.trusted-users = [ "root" "build" ];
-          documentation.nixos.enable = false;
-
-          # builds.sr.ht-image-specific network settings
-          networking = {
-            hostName = "build";
-            dhcpcd.enable = false;
-            defaultGateway.address = "10.0.2.2";
-            usePredictableInterfaceNames = false;
-            interfaces."eth0".ipv4.addresses = [{
-              address = "10.0.2.15";
-              prefixLength = 25;
-            }];
-            enableIPv6 = false;
-            nameservers = [
-              # OpenNIC anycast
-              "185.121.177.177"
-              "169.239.202.202"
-              # Google
-              "8.8.8.8"
-            ];
-            firewall.allowedTCPPorts = [ 22 ];
-          };
-
-          environment.systemPackages = [
-            pkgs.gitMinimal
-            #pkgs.mercurial
-            pkgs.curl
-            pkgs.gnupg
-          ];
-        };
-        qemuConfig = { pkgs, ... }: {
-          imports = [ systemConfig ];
-          fileSystems."/".device = "/dev/disk/by-label/nixos";
-          boot.initrd.availableKernelModules = [
-            "ahci"
-            "ehci_pci"
-            "sd_mod"
-            "usb_storage"
-            "usbhid"
-            "virtio_balloon"
-            "virtio_blk"
-            "virtio_pci"
-            "virtio_ring"
-            "xhci_pci"
-          ];
-          boot.loader = {
-            grub = {
-              version = 2;
-              device = "/dev/vda";
-            };
-            timeout = 0;
-          };
-        };
-        config = (import (pkgs.path + "/nixos/lib/eval-config.nix") {
-          inherit pkgs; modules = [ qemuConfig ];
-          system = "x86_64-linux";
-        }).config;
-      in
-      import (pkgs.path + "/nixos/lib/make-disk-image.nix") {
-        inherit pkgs lib config;
-        diskSize = 16000;
-        format = "qcow2-compressed";
-        contents = [
-          { source = pkgs.writeText "gitconfig" ''
-              [user]
-                name = builds.sr.ht
-                email = build@sr.ht
-            '';
-            target = "/home/build/.gitconfig";
-            user = "build";
-            group = "users";
-            mode = "644";
-          }
-        ];
-      };
-  };
-
-in
-{
-  name = "sourcehut";
-
-  meta.maintainers = [ pkgs.lib.maintainers.tomberek ];
-
-  nodes.machine = { config, pkgs, nodes, ... }: {
-    # buildsrht needs space
-    virtualisation.diskSize = 4 * 1024;
-    virtualisation.memorySize = 2 * 1024;
-    networking.domain = domain;
-    networking.enableIPv6 = false;
-    networking.extraHosts = ''
-      ${config.networking.primaryIPAddress} builds.${domain}
-      ${config.networking.primaryIPAddress} git.${domain}
-      ${config.networking.primaryIPAddress} meta.${domain}
-    '';
-
-    services.sourcehut = {
-      enable = true;
-      nginx.enable = true;
-      nginx.virtualHost = {
-        forceSSL = true;
-        sslCertificate = "${tls-cert}/cert.pem";
-        sslCertificateKey = "${tls-cert}/key.pem";
-      };
-      postgresql.enable = true;
-      redis.enable = true;
-
-      meta.enable = true;
-      builds = {
-        enable = true;
-        # FIXME: see why it does not seem to activate fully.
-        #enableWorker = true;
-        inherit images;
-      };
-      git.enable = true;
-
-      settings."sr.ht" = {
-        global-domain = config.networking.domain;
-        service-key = pkgs.writeText "service-key" "8b327279b77e32a3620e2fc9aabce491cc46e7d821fd6713b2a2e650ce114d01";
-        network-key = pkgs.writeText "network-key" "cEEmc30BRBGkgQZcHFksiG7hjc6_dK1XR2Oo5Jb9_nQ=";
-      };
-      settings."builds.sr.ht" = {
-        oauth-client-secret = pkgs.writeText "buildsrht-oauth-client-secret" "2260e9c4d9b8dcedcef642860e0504bc";
-        oauth-client-id = "299db9f9c2013170";
-      };
-      settings."git.sr.ht" = {
-        oauth-client-secret = pkgs.writeText "gitsrht-oauth-client-secret" "3597288dc2c716e567db5384f493b09d";
-        oauth-client-id = "d07cb713d920702e";
-      };
-      settings.webhooks.private-key = pkgs.writeText "webhook-key" "Ra3IjxgFiwG9jxgp4WALQIZw/BMYt30xWiOsqD0J7EA=";
-      settings.mail = {
-        smtp-from = "root+hut@${domain}";
-        # WARNING: take care to keep pgp-privkey outside the Nix store in production,
-        # or use LoadCredentialEncrypted=
-        pgp-privkey = toString (pkgs.writeText "sourcehut.pgp-privkey" ''
-          -----BEGIN PGP PRIVATE KEY BLOCK-----
-
-          lFgEYqDRORYJKwYBBAHaRw8BAQdAehGoy36FUx2OesYm07be2rtLyvR5Pb/ltstd
-          Gk7hYQoAAP9X4oPmxxrHN8LewBpWITdBomNqlHoiP7mI0nz/BOPJHxEktDZuaXhv
-          cy90ZXN0cy9zb3VyY2VodXQgPHJvb3QraHV0QHNvdXJjZWh1dC5sb2NhbGRvbWFp
-          bj6IlwQTFgoAPxYhBPqjgjnL8RHN4JnADNicgXaYm0jJBQJioNE5AhsDBQkDwmcA
-          BgsJCAcDCgUVCgkICwUWAwIBAAIeBQIXgAAKCRDYnIF2mJtIySVCAP9e2nHsVHSi
-          2B1YGZpVG7Xf36vxljmMkbroQy+0gBPwRwEAq+jaiQqlbGhQ7R/HMFcAxBIVsq8h
-          Aw1rngsUd0o3dAicXQRioNE5EgorBgEEAZdVAQUBAQdAXZV2Sd5ZNBVTBbTGavMv
-          D6ORrUh8z7TI/3CsxCE7+yADAQgHAAD/c1RU9xH+V/uI1fE7HIn/zL0LUPpsuce2
-          cH++g4u3kBgTOYh+BBgWCgAmFiEE+qOCOcvxEc3gmcAM2JyBdpibSMkFAmKg0TkC
-          GwwFCQPCZwAACgkQ2JyBdpibSMlKagD/cTre6p1m8QuJ7kwmCFRSz5tBzIuYMMgN
-          xtT7dmS91csA/35fWsOykSiFRojQ7ccCSUTHL7ApF2EbL968tP/D2hIG
-          =Hjoc
-          -----END PGP PRIVATE KEY BLOCK-----
-        '');
-        pgp-pubkey = pkgs.writeText "sourcehut.pgp-pubkey" ''
-          -----BEGIN PGP PUBLIC KEY BLOCK-----
-
-          mDMEYqDRORYJKwYBBAHaRw8BAQdAehGoy36FUx2OesYm07be2rtLyvR5Pb/ltstd
-          Gk7hYQq0Nm5peG9zL3Rlc3RzL3NvdXJjZWh1dCA8cm9vdCtodXRAc291cmNlaHV0
-          LmxvY2FsZG9tYWluPoiXBBMWCgA/FiEE+qOCOcvxEc3gmcAM2JyBdpibSMkFAmKg
-          0TkCGwMFCQPCZwAGCwkIBwMKBRUKCQgLBRYDAgEAAh4FAheAAAoJENicgXaYm0jJ
-          JUIA/17acexUdKLYHVgZmlUbtd/fq/GWOYyRuuhDL7SAE/BHAQCr6NqJCqVsaFDt
-          H8cwVwDEEhWyryEDDWueCxR3Sjd0CLg4BGKg0TkSCisGAQQBl1UBBQEBB0BdlXZJ
-          3lk0FVMFtMZq8y8Po5GtSHzPtMj/cKzEITv7IAMBCAeIfgQYFgoAJhYhBPqjgjnL
-          8RHN4JnADNicgXaYm0jJBQJioNE5AhsMBQkDwmcAAAoJENicgXaYm0jJSmoA/3E6
-          3uqdZvELie5MJghUUs+bQcyLmDDIDcbU+3ZkvdXLAP9+X1rDspEohUaI0O3HAklE
-          xy+wKRdhGy/evLT/w9oSBg==
-          =pJD7
-          -----END PGP PUBLIC KEY BLOCK-----
-        '';
-        pgp-key-id = "0xFAA38239CBF111CDE099C00CD89C8176989B48C9";
-      };
-    };
-
-    networking.firewall.allowedTCPPorts = [ 443 ];
-    security.pki.certificateFiles = [ "${tls-cert}/cert.pem" ];
-    services.nginx = {
-      enable = true;
-      recommendedGzipSettings = true;
-      recommendedOptimisation = true;
-      recommendedTlsSettings = true;
-      recommendedProxySettings = true;
-    };
-
-    services.postgresql = {
-      enable = true;
-      enableTCPIP = false;
-      settings.unix_socket_permissions = "0770";
-    };
-  };
-
-  testScript = ''
-    start_all()
-    machine.wait_for_unit("multi-user.target")
-
-    # Testing metasrht
-    machine.wait_for_unit("metasrht-api.service")
-    machine.wait_for_unit("metasrht.service")
-    machine.wait_for_unit("metasrht-webhooks.service")
-    machine.wait_for_open_port(5000)
-    machine.succeed("curl -sL http://localhost:5000 | grep meta.${domain}")
-    machine.succeed("curl -sL http://meta.${domain} | grep meta.${domain}")
-
-    # Testing buildsrht
-    machine.wait_for_unit("buildsrht.service")
-    machine.wait_for_open_port(5002)
-    machine.succeed("curl -sL http://localhost:5002 | grep builds.${domain}")
-    #machine.wait_for_unit("buildsrht-worker.service")
-
-    # Testing gitsrht
-    machine.wait_for_unit("gitsrht-api.service")
-    machine.wait_for_unit("gitsrht.service")
-    machine.wait_for_unit("gitsrht-webhooks.service")
-    machine.succeed("curl -sL http://git.${domain} | grep git.${domain}")
-  '';
-})
diff --git a/nixos/tests/sourcehut/builds.nix b/nixos/tests/sourcehut/builds.nix
new file mode 100644
index 000000000000..f1f928ecc3d0
--- /dev/null
+++ b/nixos/tests/sourcehut/builds.nix
@@ -0,0 +1,54 @@
+import ../make-test-python.nix ({ pkgs, lib, ... }:
+let
+  domain = "sourcehut.localdomain";
+in
+{
+  name = "sourcehut";
+
+  meta.maintainers = with pkgs.lib.maintainers; [ tomberek nessdoor ];
+
+  nodes.machine = { config, pkgs, nodes, ... }: {
+    imports = [
+      ./nodes/common.nix
+    ];
+
+    networking.domain = domain;
+    networking.extraHosts = ''
+      ${config.networking.primaryIPAddress} builds.${domain}
+      ${config.networking.primaryIPAddress} meta.${domain}
+    '';
+
+    services.sourcehut = {
+      builds = {
+        enable = true;
+        # FIXME: see why it does not seem to activate fully.
+        #enableWorker = true;
+        images = { };
+      };
+
+      settings."builds.sr.ht" = {
+        oauth-client-secret = pkgs.writeText "buildsrht-oauth-client-secret" "2260e9c4d9b8dcedcef642860e0504bc";
+        oauth-client-id = "299db9f9c2013170";
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("multi-user.target")
+
+    with subtest("Check whether meta comes up"):
+         machine.wait_for_unit("metasrht-api.service")
+         machine.wait_for_unit("metasrht.service")
+         machine.wait_for_unit("metasrht-webhooks.service")
+         machine.wait_for_open_port(5000)
+         machine.succeed("curl -sL http://localhost:5000 | grep meta.${domain}")
+         machine.succeed("curl -sL http://meta.${domain} | grep meta.${domain}")
+
+    with subtest("Check whether builds comes up"):
+         machine.wait_for_unit("buildsrht.service")
+         machine.wait_for_open_port(5002)
+         machine.succeed("curl -sL http://localhost:5002 | grep builds.${domain}")
+         #machine.wait_for_unit("buildsrht-worker.service")
+  '';
+})
diff --git a/nixos/tests/sourcehut/default.nix b/nixos/tests/sourcehut/default.nix
new file mode 100644
index 000000000000..04f1551d70d9
--- /dev/null
+++ b/nixos/tests/sourcehut/default.nix
@@ -0,0 +1,6 @@
+{ system, pkgs, ... }:
+
+{
+  git = import ./git.nix { inherit system pkgs; };
+  builds = import ./builds.nix { inherit system pkgs; };
+}
diff --git a/nixos/tests/sourcehut/git.nix b/nixos/tests/sourcehut/git.nix
new file mode 100644
index 000000000000..ed184d5d5518
--- /dev/null
+++ b/nixos/tests/sourcehut/git.nix
@@ -0,0 +1,96 @@
+import ../make-test-python.nix ({ pkgs, lib, ... }:
+let
+  domain = "sourcehut.localdomain";
+in
+{
+  name = "sourcehut";
+
+  meta.maintainers = with pkgs.lib.maintainers; [ tomberek nessdoor ];
+
+  nodes.machine = { config, pkgs, nodes, ... }: {
+    imports = [
+      ./nodes/common.nix
+    ];
+
+    networking.domain = domain;
+    networking.extraHosts = ''
+      ${config.networking.primaryIPAddress} git.${domain}
+      ${config.networking.primaryIPAddress} meta.${domain}
+    '';
+
+    services.sourcehut = {
+      git.enable = true;
+      settings."git.sr.ht" = {
+        oauth-client-secret = pkgs.writeText "gitsrht-oauth-client-secret" "3597288dc2c716e567db5384f493b09d";
+        oauth-client-id = "d07cb713d920702e";
+      };
+    };
+
+    environment.systemPackages = with pkgs; [
+      git
+    ];
+  };
+
+  testScript =
+    let
+      userName = "nixos-test";
+      userPass = "AutoNixosTestPwd";
+      hutConfig = pkgs.writeText "hut-config" ''
+        instance "${domain}" {
+          # Will be replaced at runtime with the generated token
+          access-token "OAUTH-TOKEN"
+        }
+      '';
+      sshConfig = pkgs.writeText "ssh-config" ''
+        Host git.${domain}
+             IdentityFile = ~/.ssh/id_rsa
+      '';
+    in
+    ''
+      start_all()
+      machine.wait_for_unit("multi-user.target")
+      machine.wait_for_unit("sshd.service")
+
+      with subtest("Check whether meta comes up"):
+           machine.wait_for_unit("metasrht-api.service")
+           machine.wait_for_unit("metasrht.service")
+           machine.wait_for_unit("metasrht-webhooks.service")
+           machine.wait_for_open_port(5000)
+           machine.succeed("curl -sL http://localhost:5000 | grep meta.${domain}")
+           machine.succeed("curl -sL http://meta.${domain} | grep meta.${domain}")
+
+      with subtest("Create a new user account and OAuth access key"):
+           machine.succeed("echo ${userPass} | metasrht-manageuser -ps -e ${userName}@${domain}\
+                            -t active_paying ${userName}");
+           (_, token) = machine.execute("srht-gen-oauth-tok -i ${domain} -q ${userName} ${userPass}")
+           token = token.strip().replace("/", r"\\/") # Escape slashes in token before passing it to sed
+           machine.execute("mkdir -p ~/.config/hut/")
+           machine.execute("sed s/OAUTH-TOKEN/" + token + "/ ${hutConfig} > ~/.config/hut/config")
+
+      with subtest("Check whether git comes up"):
+           machine.wait_for_unit("gitsrht-api.service")
+           machine.wait_for_unit("gitsrht.service")
+           machine.wait_for_unit("gitsrht-webhooks.service")
+           machine.succeed("curl -sL http://git.${domain} | grep git.${domain}")
+
+      with subtest("Add an SSH key for Git access"):
+           machine.execute("ssh-keygen -q -N \"\" -t rsa -f ~/.ssh/id_rsa")
+           machine.execute("cat ${sshConfig} > ~/.ssh/config")
+           machine.succeed("hut meta ssh-key create ~/.ssh/id_rsa.pub")
+
+      with subtest("Create a new repo and push contents to it"):
+           machine.execute("git init test")
+           machine.execute("echo \"Hello world!\" > test/hello.txt")
+           machine.execute("cd test && git add .")
+           machine.execute("cd test && git commit -m \"Initial commit\"")
+           machine.execute("cd test && git tag v0.1")
+           machine.succeed("cd test && git remote add origin gitsrht@git.${domain}:~${userName}/test")
+           machine.execute("( echo -n 'git.${domain} '; cat /etc/ssh/ssh_host_ed25519_key.pub ) > ~/.ssh/known_hosts")
+           machine.succeed("hut git create test")
+           machine.succeed("cd test && git push --tags --set-upstream origin master")
+
+      with subtest("Verify that the repo is downloadable and its contents match the original"):
+           machine.succeed("curl https://git.${domain}/~${userName}/test/archive/v0.1.tar.gz | tar -xz")
+           machine.succeed("diff test-v0.1/hello.txt test/hello.txt")
+    '';
+})
diff --git a/nixos/tests/sourcehut/nodes/common.nix b/nixos/tests/sourcehut/nodes/common.nix
new file mode 100644
index 000000000000..f0a81358f972
--- /dev/null
+++ b/nixos/tests/sourcehut/nodes/common.nix
@@ -0,0 +1,107 @@
+{ config, pkgs, nodes, ... }:
+let
+  domain = config.networking.domain;
+
+  # Note that wildcard certificates just under the TLD (eg. *.com)
+  # would be rejected by clients like curl.
+  tls-cert = pkgs.runCommand "selfSignedCerts" { buildInputs = [ pkgs.openssl ]; } ''
+    openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -days 36500 \
+      -subj '/CN=${domain}' -extensions v3_req \
+      -addext 'subjectAltName = DNS:*.${domain}'
+    install -D -t $out key.pem cert.pem
+  '';
+in
+{
+  # buildsrht needs space
+  virtualisation.diskSize = 4 * 1024;
+  virtualisation.memorySize = 2 * 1024;
+  networking.enableIPv6 = false;
+
+  services.sourcehut = {
+    enable = true;
+    nginx.enable = true;
+    nginx.virtualHost = {
+      forceSSL = true;
+      sslCertificate = "${tls-cert}/cert.pem";
+      sslCertificateKey = "${tls-cert}/key.pem";
+    };
+    postgresql.enable = true;
+    redis.enable = true;
+
+    meta.enable = true;
+
+    settings."sr.ht" = {
+      environment = "production";
+      global-domain = config.networking.domain;
+      service-key = pkgs.writeText "service-key" "8b327279b77e32a3620e2fc9aabce491cc46e7d821fd6713b2a2e650ce114d01";
+      network-key = pkgs.writeText "network-key" "cEEmc30BRBGkgQZcHFksiG7hjc6_dK1XR2Oo5Jb9_nQ=";
+    };
+    settings.webhooks.private-key = pkgs.writeText "webhook-key" "Ra3IjxgFiwG9jxgp4WALQIZw/BMYt30xWiOsqD0J7EA=";
+    settings.mail = {
+      smtp-from = "root+hut@${domain}";
+      # WARNING: take care to keep pgp-privkey outside the Nix store in production,
+      # or use LoadCredentialEncrypted=
+      pgp-privkey = toString (pkgs.writeText "sourcehut.pgp-privkey" ''
+        -----BEGIN PGP PRIVATE KEY BLOCK-----
+
+        lFgEYqDRORYJKwYBBAHaRw8BAQdAehGoy36FUx2OesYm07be2rtLyvR5Pb/ltstd
+        Gk7hYQoAAP9X4oPmxxrHN8LewBpWITdBomNqlHoiP7mI0nz/BOPJHxEktDZuaXhv
+        cy90ZXN0cy9zb3VyY2VodXQgPHJvb3QraHV0QHNvdXJjZWh1dC5sb2NhbGRvbWFp
+        bj6IlwQTFgoAPxYhBPqjgjnL8RHN4JnADNicgXaYm0jJBQJioNE5AhsDBQkDwmcA
+        BgsJCAcDCgUVCgkICwUWAwIBAAIeBQIXgAAKCRDYnIF2mJtIySVCAP9e2nHsVHSi
+        2B1YGZpVG7Xf36vxljmMkbroQy+0gBPwRwEAq+jaiQqlbGhQ7R/HMFcAxBIVsq8h
+        Aw1rngsUd0o3dAicXQRioNE5EgorBgEEAZdVAQUBAQdAXZV2Sd5ZNBVTBbTGavMv
+        D6ORrUh8z7TI/3CsxCE7+yADAQgHAAD/c1RU9xH+V/uI1fE7HIn/zL0LUPpsuce2
+        cH++g4u3kBgTOYh+BBgWCgAmFiEE+qOCOcvxEc3gmcAM2JyBdpibSMkFAmKg0TkC
+        GwwFCQPCZwAACgkQ2JyBdpibSMlKagD/cTre6p1m8QuJ7kwmCFRSz5tBzIuYMMgN
+        xtT7dmS91csA/35fWsOykSiFRojQ7ccCSUTHL7ApF2EbL968tP/D2hIG
+        =Hjoc
+        -----END PGP PRIVATE KEY BLOCK-----
+      '');
+      pgp-pubkey = pkgs.writeText "sourcehut.pgp-pubkey" ''
+        -----BEGIN PGP PUBLIC KEY BLOCK-----
+
+        mDMEYqDRORYJKwYBBAHaRw8BAQdAehGoy36FUx2OesYm07be2rtLyvR5Pb/ltstd
+        Gk7hYQq0Nm5peG9zL3Rlc3RzL3NvdXJjZWh1dCA8cm9vdCtodXRAc291cmNlaHV0
+        LmxvY2FsZG9tYWluPoiXBBMWCgA/FiEE+qOCOcvxEc3gmcAM2JyBdpibSMkFAmKg
+        0TkCGwMFCQPCZwAGCwkIBwMKBRUKCQgLBRYDAgEAAh4FAheAAAoJENicgXaYm0jJ
+        JUIA/17acexUdKLYHVgZmlUbtd/fq/GWOYyRuuhDL7SAE/BHAQCr6NqJCqVsaFDt
+        H8cwVwDEEhWyryEDDWueCxR3Sjd0CLg4BGKg0TkSCisGAQQBl1UBBQEBB0BdlXZJ
+        3lk0FVMFtMZq8y8Po5GtSHzPtMj/cKzEITv7IAMBCAeIfgQYFgoAJhYhBPqjgjnL
+        8RHN4JnADNicgXaYm0jJBQJioNE5AhsMBQkDwmcAAAoJENicgXaYm0jJSmoA/3E6
+        3uqdZvELie5MJghUUs+bQcyLmDDIDcbU+3ZkvdXLAP9+X1rDspEohUaI0O3HAklE
+        xy+wKRdhGy/evLT/w9oSBg==
+        =pJD7
+        -----END PGP PUBLIC KEY BLOCK-----
+      '';
+      pgp-key-id = "0xFAA38239CBF111CDE099C00CD89C8176989B48C9";
+    };
+  };
+
+  networking.firewall.allowedTCPPorts = [ 80 443 ];
+  security.pki.certificateFiles = [ "${tls-cert}/cert.pem" ];
+  services.nginx = {
+    enable = true;
+    recommendedGzipSettings = true;
+    recommendedOptimisation = true;
+    recommendedTlsSettings = true;
+    recommendedProxySettings = true;
+  };
+
+  services.postgresql = {
+    enable = true;
+    enableTCPIP = false;
+    settings.unix_socket_permissions = "0770";
+  };
+
+  services.openssh = {
+    enable = true;
+    settings.PasswordAuthentication = false;
+    settings.PermitRootLogin = "no";
+  };
+
+  environment.systemPackages = with pkgs; [
+    hut # For interacting with the Sourcehut APIs via CLI
+    srht-gen-oauth-tok # To automatically generate user OAuth tokens
+  ];
+}
diff --git a/nixos/tests/systemd-boot.nix b/nixos/tests/systemd-boot.nix
index c0b37a230df0..54c380602bd4 100644
--- a/nixos/tests/systemd-boot.nix
+++ b/nixos/tests/systemd-boot.nix
@@ -14,6 +14,72 @@ let
     boot.loader.efi.canTouchEfiVariables = true;
     environment.systemPackages = [ pkgs.efibootmgr ];
   };
+
+  commonXbootldr = { config, lib, pkgs, ... }:
+    let
+      diskImage = import ../lib/make-disk-image.nix {
+        inherit config lib pkgs;
+        label = "nixos";
+        format = "qcow2";
+        partitionTableType = "efixbootldr";
+        touchEFIVars = true;
+        installBootLoader = true;
+      };
+    in
+    {
+      imports = [ common ];
+      virtualisation.useBootLoader = lib.mkForce false; # Only way to tell qemu-vm not to create the default system image
+      virtualisation.directBoot.enable = false; # But don't direct boot either because we're testing systemd-boot
+
+      system.build.diskImage = diskImage; # Use custom disk image with an XBOOTLDR partition
+      virtualisation.efi.variables = "${diskImage}/efi-vars.fd";
+
+      virtualisation.useDefaultFilesystems = false; # Needs custom setup for `diskImage`
+      virtualisation.bootPartition = null;
+      virtualisation.fileSystems = {
+        "/" = {
+          device = "/dev/vda3";
+          fsType = "ext4";
+        };
+        "/boot" = {
+          device = "/dev/vda2";
+          fsType = "vfat";
+          noCheck = true;
+        };
+        "/efi" = {
+          device = "/dev/vda1";
+          fsType = "vfat";
+          noCheck = true;
+        };
+      };
+
+      boot.loader.systemd-boot.enable = true;
+      boot.loader.efi.efiSysMountPoint = "/efi";
+      boot.loader.systemd-boot.xbootldrMountPoint = "/boot";
+    };
+
+  customDiskImage = nodes: ''
+    import os
+    import subprocess
+    import tempfile
+
+    tmp_disk_image = tempfile.NamedTemporaryFile()
+
+    subprocess.run([
+      "${nodes.machine.virtualisation.qemu.package}/bin/qemu-img",
+      "create",
+      "-f",
+      "qcow2",
+      "-b",
+      "${nodes.machine.system.build.diskImage}/nixos.qcow2",
+      "-F",
+      "qcow2",
+      tmp_disk_image.name,
+    ])
+
+    # Set NIX_DISK_IMAGE so that the qemu script finds the right disk image.
+    os.environ['NIX_DISK_IMAGE'] = tmp_disk_image.name
+  '';
 in
 {
   basic = makeTest {
@@ -27,6 +93,61 @@ in
       machine.wait_for_unit("multi-user.target")
 
       machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf")
+      machine.succeed("grep 'sort-key nixos' /boot/loader/entries/nixos-generation-1.conf")
+
+      # Ensure we actually booted using systemd-boot
+      # Magic number is the vendor UUID used by systemd-boot.
+      machine.succeed(
+          "test -e /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"
+      )
+
+      # "bootctl install" should have created an EFI entry
+      machine.succeed('efibootmgr | grep "Linux Boot Manager"')
+    '';
+  };
+
+  # Test that systemd-boot works with secure boot
+  secureBoot = makeTest {
+    name = "systemd-boot-secure-boot";
+
+    nodes.machine = {
+      imports = [ common ];
+      environment.systemPackages = [ pkgs.sbctl ];
+      virtualisation.useSecureBoot = true;
+    };
+
+    testScript = let
+      efiArch = pkgs.stdenv.hostPlatform.efiArch;
+    in { nodes, ... }: ''
+      machine.start(allow_reboot=True)
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed("sbctl create-keys")
+      machine.succeed("sbctl enroll-keys --yes-this-might-brick-my-machine")
+      machine.succeed('sbctl sign /boot/EFI/systemd/systemd-boot${efiArch}.efi')
+      machine.succeed('sbctl sign /boot/EFI/BOOT/BOOT${toUpper efiArch}.EFI')
+      machine.succeed('sbctl sign /boot/EFI/nixos/*${nodes.machine.system.boot.loader.kernelFile}.efi')
+
+      machine.reboot()
+
+      assert "Secure Boot: enabled (user)" in machine.succeed("bootctl status")
+    '';
+  };
+
+  basicXbootldr = makeTest {
+    name = "systemd-boot-xbootldr";
+    meta.maintainers = with pkgs.lib.maintainers; [ sdht0 ];
+
+    nodes.machine = commonXbootldr;
+
+    testScript = { nodes, ... }: ''
+      ${customDiskImage nodes}
+
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed("test -e /efi/EFI/systemd/systemd-bootx64.efi")
+      machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf")
 
       # Ensure we actually booted using systemd-boot
       # Magic number is the vendor UUID used by systemd-boot.
@@ -46,7 +167,9 @@ in
 
     nodes.machine = { pkgs, lib, ... }: {
       imports = [ common ];
-      specialisation.something.configuration = {};
+      specialisation.something.configuration = {
+        boot.loader.systemd-boot.sortKey = "something";
+      };
     };
 
     testScript = ''
@@ -59,6 +182,9 @@ in
       machine.succeed(
           "grep -q 'title NixOS (something)' /boot/loader/entries/nixos-generation-1-specialisation-something.conf"
       )
+      machine.succeed(
+          "grep 'sort-key something' /boot/loader/entries/nixos-generation-1-specialisation-something.conf"
+      )
     '';
   };
 
@@ -136,25 +262,46 @@ in
     };
 
     testScript = ''
-      machine.succeed("test -e /boot/loader/entries/o_netbootxyz.conf")
+      machine.succeed("test -e /boot/loader/entries/netbootxyz.conf")
       machine.succeed("test -e /boot/efi/netbootxyz/netboot.xyz.efi")
     '';
   };
 
-  entryFilename = makeTest {
-    name = "systemd-boot-entry-filename";
+  memtestSortKey = makeTest {
+    name = "systemd-boot-memtest-sortkey";
     meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
 
     nodes.machine = { pkgs, lib, ... }: {
       imports = [ common ];
       boot.loader.systemd-boot.memtest86.enable = true;
-      boot.loader.systemd-boot.memtest86.entryFilename = "apple.conf";
+      boot.loader.systemd-boot.memtest86.sortKey = "apple";
     };
 
     testScript = ''
-      machine.fail("test -e /boot/loader/entries/memtest86.conf")
-      machine.succeed("test -e /boot/loader/entries/apple.conf")
+      machine.succeed("test -e /boot/loader/entries/memtest86.conf")
       machine.succeed("test -e /boot/efi/memtest86/memtest.efi")
+      machine.succeed("grep 'sort-key apple' /boot/loader/entries/memtest86.conf")
+    '';
+  };
+
+  entryFilenameXbootldr = makeTest {
+    name = "systemd-boot-entry-filename-xbootldr";
+    meta.maintainers = with pkgs.lib.maintainers; [ sdht0 ];
+
+    nodes.machine = { pkgs, lib, ... }: {
+      imports = [ commonXbootldr ];
+      boot.loader.systemd-boot.memtest86.enable = true;
+    };
+
+    testScript = { nodes, ... }: ''
+      ${customDiskImage nodes}
+
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed("test -e /efi/EFI/systemd/systemd-bootx64.efi")
+      machine.succeed("test -e /boot/loader/entries/memtest86.conf")
+      machine.succeed("test -e /boot/EFI/memtest86/memtest.efi")
     '';
   };
 
@@ -245,9 +392,9 @@ in
           machine.succeed("${finalSystem}/bin/switch-to-configuration boot")
           machine.fail("test -e /boot/efi/fruits/tomato.efi")
           machine.fail("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi")
-          machine.succeed("test -e /boot/loader/entries/o_netbootxyz.conf")
+          machine.succeed("test -e /boot/loader/entries/netbootxyz.conf")
           machine.succeed("test -e /boot/efi/netbootxyz/netboot.xyz.efi")
-          machine.succeed("test -e /boot/efi/nixos/.extra-files/loader/entries/o_netbootxyz.conf")
+          machine.succeed("test -e /boot/efi/nixos/.extra-files/loader/entries/netbootxyz.conf")
           machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/netbootxyz/netboot.xyz.efi")
     '';
   };
diff --git a/nixos/tests/transfer-sh.nix b/nixos/tests/transfer-sh.nix
new file mode 100644
index 000000000000..f4ab7d28858e
--- /dev/null
+++ b/nixos/tests/transfer-sh.nix
@@ -0,0 +1,20 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "transfer-sh";
+
+  meta = {
+    maintainers = with lib.maintainers; [ ocfox ];
+  };
+
+  nodes.machine = { pkgs, ... }: {
+    services.transfer-sh = {
+      enable = true;
+      settings.LISTENER = ":1234";
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("transfer-sh.service")
+    machine.wait_for_open_port(1234)
+    machine.succeed("curl --fail http://localhost:1234/")
+  '';
+})
diff --git a/nixos/tests/vikunja.nix b/nixos/tests/vikunja.nix
index 60fd5ce13854..4e2bf166a7b6 100644
--- a/nixos/tests/vikunja.nix
+++ b/nixos/tests/vikunja.nix
@@ -13,15 +13,20 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
         frontendScheme = "http";
         frontendHostname = "localhost";
       };
-      services.nginx.enable = true;
+      services.nginx = {
+        enable = true;
+        virtualHosts."http://localhost" = {
+          locations."/".proxyPass = "http://localhost:3456";
+        };
+      };
     };
     vikunjaPostgresql = { pkgs, ... }: {
       services.vikunja = {
         enable = true;
         database = {
           type = "postgres";
-          user = "vikunja-api";
-          database = "vikunja-api";
+          user = "vikunja";
+          database = "vikunja";
           host = "/run/postgresql";
         };
         frontendScheme = "http";
@@ -30,20 +35,25 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
       };
       services.postgresql = {
         enable = true;
-        ensureDatabases = [ "vikunja-api" ];
+        ensureDatabases = [ "vikunja" ];
         ensureUsers = [
-          { name = "vikunja-api";
+          { name = "vikunja";
             ensureDBOwnership = true;
           }
         ];
       };
-      services.nginx.enable = true;
+      services.nginx = {
+        enable = true;
+        virtualHosts."http://localhost" = {
+          locations."/".proxyPass = "http://localhost:9090";
+        };
+      };
     };
   };
 
   testScript =
     ''
-      vikunjaSqlite.wait_for_unit("vikunja-api.service")
+      vikunjaSqlite.wait_for_unit("vikunja.service")
       vikunjaSqlite.wait_for_open_port(3456)
       vikunjaSqlite.succeed("curl --fail http://localhost:3456/api/v1/info")
 
@@ -52,7 +62,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
       vikunjaSqlite.succeed("curl --fail http://localhost/api/v1/info")
       vikunjaSqlite.succeed("curl --fail http://localhost")
 
-      vikunjaPostgresql.wait_for_unit("vikunja-api.service")
+      vikunjaPostgresql.wait_for_unit("vikunja.service")
       vikunjaPostgresql.wait_for_open_port(9090)
       vikunjaPostgresql.succeed("curl --fail http://localhost:9090/api/v1/info")
 
diff --git a/nixos/tests/web-apps/mastodon/default.nix b/nixos/tests/web-apps/mastodon/default.nix
index 411ebfcd731b..178590d13b63 100644
--- a/nixos/tests/web-apps/mastodon/default.nix
+++ b/nixos/tests/web-apps/mastodon/default.nix
@@ -5,5 +5,5 @@ let
 in
 {
   standard = handleTestOn supportedSystems ./standard.nix { inherit system; };
-  remote-postgresql = handleTestOn supportedSystems ./remote-postgresql.nix { inherit system; };
+  remote-databases = handleTestOn supportedSystems ./remote-databases.nix { inherit system; };
 }
diff --git a/nixos/tests/web-apps/mastodon/remote-postgresql.nix b/nixos/tests/web-apps/mastodon/remote-databases.nix
index 6548883db452..fa6430a99353 100644
--- a/nixos/tests/web-apps/mastodon/remote-postgresql.nix
+++ b/nixos/tests/web-apps/mastodon/remote-databases.nix
@@ -16,7 +16,14 @@ in
   meta.maintainers = with pkgs.lib.maintainers; [ erictapen izorkin ];
 
   nodes = {
-    database = { config, ... }: {
+    databases = { config, ... }: {
+      environment = {
+        etc = {
+          "redis/password-redis-db".text = ''
+            ogjhJL8ynrP7MazjYOF6
+          '';
+        };
+      };
       networking = {
         interfaces.eth1 = {
           ipv4.addresses = [
@@ -24,7 +31,17 @@ in
           ];
         };
         extraHosts = hosts;
-        firewall.allowedTCPPorts = [ config.services.postgresql.port ];
+        firewall.allowedTCPPorts = [
+          config.services.redis.servers.mastodon.port
+          config.services.postgresql.port
+        ];
+      };
+
+      services.redis.servers.mastodon = {
+        enable = true;
+        bind = "0.0.0.0";
+        port = 31637;
+        requirePassFile = "/etc/redis/password-redis-db";
       };
 
       services.postgresql = {
@@ -83,6 +100,9 @@ in
 
       environment = {
         etc = {
+          "mastodon/password-redis-db".text = ''
+            ogjhJL8ynrP7MazjYOF6
+          '';
           "mastodon/password-posgressql-db".text = ''
             SoDTZcISc3f1M1LJsRLT
           '';
@@ -108,6 +128,12 @@ in
         localDomain = "mastodon.local";
         enableUnixSocket = false;
         streamingProcesses = 2;
+        redis = {
+          createLocally = false;
+          host = "192.168.2.102";
+          port = 31637;
+          passwordFile = "/etc/mastodon/password-redis-db";
+        };
         database = {
           createLocally = false;
           host = "192.168.2.102";
@@ -151,12 +177,14 @@ in
     extraInit = ''
       nginx.wait_for_unit("nginx.service")
       nginx.wait_for_open_port(443)
-      database.wait_for_unit("postgresql.service")
-      database.wait_for_open_port(5432)
+      databases.wait_for_unit("redis-mastodon.service")
+      databases.wait_for_unit("postgresql.service")
+      databases.wait_for_open_port(31637)
+      databases.wait_for_open_port(5432)
     '';
     extraShutdown = ''
       nginx.shutdown()
-      database.shutdown()
+      databases.shutdown()
     '';
   };
 })
diff --git a/nixos/tests/web-apps/mastodon/script.nix b/nixos/tests/web-apps/mastodon/script.nix
index afb7c0e0a0eb..9184c63c8941 100644
--- a/nixos/tests/web-apps/mastodon/script.nix
+++ b/nixos/tests/web-apps/mastodon/script.nix
@@ -8,7 +8,6 @@
 
   ${extraInit}
 
-  server.wait_for_unit("redis-mastodon.service")
   server.wait_for_unit("mastodon-sidekiq-all.service")
   server.wait_for_unit("mastodon-streaming.target")
   server.wait_for_unit("mastodon-web.service")
diff --git a/nixos/tests/web-apps/mastodon/standard.nix b/nixos/tests/web-apps/mastodon/standard.nix
index e14cf592332e..ddc764e2168c 100644
--- a/nixos/tests/web-apps/mastodon/standard.nix
+++ b/nixos/tests/web-apps/mastodon/standard.nix
@@ -83,6 +83,7 @@ in
     extraInit = ''
       server.wait_for_unit("nginx.service")
       server.wait_for_open_port(443)
+      server.wait_for_unit("redis-mastodon.service")
       server.wait_for_unit("postgresql.service")
       server.wait_for_open_port(5432)
     '';
diff --git a/nixos/tests/zfs.nix b/nixos/tests/zfs.nix
index 0b411b0b9d8a..851fced2c5e1 100644
--- a/nixos/tests/zfs.nix
+++ b/nixos/tests/zfs.nix
@@ -7,14 +7,14 @@ with import ../lib/testing-python.nix { inherit system pkgs; };
 
 let
 
-  makeZfsTest = name:
+  makeZfsTest =
     { kernelPackages
     , enableSystemdStage1 ? false
     , zfsPackage
     , extraTest ? ""
     }:
     makeTest {
-      name = "zfs-" + name;
+      name = zfsPackage.kernelModuleAttribute;
       meta = with pkgs.lib.maintainers; {
         maintainers = [ elvishjerricco ];
       };
@@ -192,23 +192,23 @@ let
 in {
 
   # maintainer: @raitobezarius
-  series_2_1 = makeZfsTest "2.1-series" {
+  series_2_1 = makeZfsTest {
     zfsPackage = pkgs.zfs_2_1;
     kernelPackages = pkgs.linuxPackages;
   };
 
-  stable = makeZfsTest "stable" {
-    zfsPackage = pkgs.zfsStable;
+  series_2_2 = makeZfsTest {
+    zfsPackage = pkgs.zfs_2_2;
     kernelPackages = pkgs.linuxPackages;
   };
 
-  unstable = makeZfsTest "unstable" rec {
-    zfsPackage = pkgs.zfsUnstable;
+  unstable = makeZfsTest rec {
+    zfsPackage = pkgs.zfs_unstable;
     kernelPackages = zfsPackage.latestCompatibleLinuxPackages;
   };
 
-  unstableWithSystemdStage1 = makeZfsTest "unstable" rec {
-    zfsPackage = pkgs.zfsUnstable;
+  unstableWithSystemdStage1 = makeZfsTest rec {
+    zfsPackage = pkgs.zfs_unstable;
     kernelPackages = zfsPackage.latestCompatibleLinuxPackages;
     enableSystemdStage1 = true;
   };