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/configuration/x-windows.chapter.md1
-rw-r--r--nixos/doc/manual/development/replace-modules.section.md3
-rw-r--r--nixos/doc/manual/development/running-nixos-tests.section.md10
-rw-r--r--nixos/doc/manual/development/settings-options.section.md28
-rw-r--r--nixos/doc/manual/development/writing-modules.chapter.md7
-rw-r--r--nixos/doc/manual/installation/installing.chapter.md3
-rw-r--r--nixos/doc/manual/release-notes/rl-2405.section.md60
-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.nix10
-rw-r--r--nixos/lib/test-driver/test_driver/driver.py60
-rw-r--r--nixos/lib/test-driver/test_driver/logger.py12
-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/no-x-libs.nix2
-rw-r--r--nixos/modules/config/users-groups.nix5
-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/image/repart-image.nix38
-rw-r--r--nixos/modules/image/repart.nix32
-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.nix9
-rw-r--r--nixos/modules/programs/chromium.nix19
-rw-r--r--nixos/modules/programs/coolercontrol.nix37
-rw-r--r--nixos/modules/programs/gnupg.nix43
-rw-r--r--nixos/modules/programs/kdeconnect.nix5
-rw-r--r--nixos/modules/programs/steam.nix22
-rw-r--r--nixos/modules/programs/wayland/sway.nix9
-rw-r--r--nixos/modules/security/pam.nix43
-rw-r--r--nixos/modules/services/backup/syncoid.nix2
-rw-r--r--nixos/modules/services/databases/memcached.nix2
-rw-r--r--nixos/modules/services/databases/postgresql.nix43
-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/nixseparatedebuginfod.nix4
-rw-r--r--nixos/modules/services/games/armagetronad.nix268
-rw-r--r--nixos/modules/services/hardware/fwupd.nix4
-rw-r--r--nixos/modules/services/hardware/monado.nix102
-rw-r--r--nixos/modules/services/hardware/nvidia-container-toolkit-cdi-generator/cdi-generate.nix57
-rw-r--r--nixos/modules/services/hardware/nvidia-container-toolkit-cdi-generator/default.nix8
-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/mail/listmonk.nix6
-rw-r--r--nixos/modules/services/matrix/matrix-sliding-sync.nix3
-rw-r--r--nixos/modules/services/misc/atuin.nix10
-rw-r--r--nixos/modules/services/misc/docker-registry.nix10
-rw-r--r--nixos/modules/services/misc/etebase-server.nix28
-rw-r--r--nixos/modules/services/misc/homepage-dashboard.nix224
-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/tabby.nix203
-rw-r--r--nixos/modules/services/misc/transfer-sh.nix102
-rw-r--r--nixos/modules/services/monitoring/mackerel-agent.nix2
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/nut.nix15
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/restic.nix13
-rw-r--r--nixos/modules/services/monitoring/scrutiny.nix149
-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/mosquitto.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/security/vaultwarden/default.nix10
-rw-r--r--nixos/modules/services/security/yubikey-agent.nix12
-rw-r--r--nixos/modules/services/web-apps/mealie.nix79
-rw-r--r--nixos/modules/services/web-apps/miniflux.nix28
-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/x11/desktop-managers/deepin.nix1
-rw-r--r--nixos/modules/services/x11/desktop-managers/default.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/lxqt.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/plasma5.nix3
-rw-r--r--nixos/modules/services/x11/desktop-managers/plasma6.nix292
-rw-r--r--nixos/modules/services/x11/desktop-managers/xfce.nix1
-rw-r--r--nixos/modules/services/x11/display-managers/sddm.nix26
-rw-r--r--nixos/modules/services/x11/xserver.nix2
-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.nix6
-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/repart.nix16
-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/cri-o.nix2
-rw-r--r--nixos/modules/virtualisation/hyperv-image.nix1
-rw-r--r--nixos/modules/virtualisation/incus.nix7
-rw-r--r--nixos/modules/virtualisation/linode-config.nix1
-rw-r--r--nixos/modules/virtualisation/oci-containers.nix7
-rw-r--r--nixos/modules/virtualisation/podman/default.nix4
-rw-r--r--nixos/modules/virtualisation/qemu-vm.nix4
-rw-r--r--nixos/modules/virtualisation/virtualbox-host.nix51
-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/akkoma.nix12
-rw-r--r--nixos/tests/all-tests.nix10
-rw-r--r--nixos/tests/armagetronad.nix272
-rw-r--r--nixos/tests/boot.nix82
-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.nix22
-rw-r--r--nixos/tests/homepage-dashboard.nix30
-rw-r--r--nixos/tests/incus/container.nix14
-rw-r--r--nixos/tests/incus/lxd-to-incus.nix1
-rw-r--r--nixos/tests/incus/preseed.nix1
-rw-r--r--nixos/tests/incus/socket-activated.nix1
-rw-r--r--nixos/tests/incus/ui.nix1
-rw-r--r--nixos/tests/incus/virtual-machine.nix1
-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/kernel-generic.nix1
-rw-r--r--nixos/tests/knot.nix21
-rw-r--r--nixos/tests/lomiri-system-settings.nix99
-rw-r--r--nixos/tests/matter-server.nix45
-rw-r--r--nixos/tests/mealie.nix24
-rw-r--r--nixos/tests/miniflux.nix78
-rw-r--r--nixos/tests/minio.nix6
-rw-r--r--nixos/tests/monado.nix39
-rw-r--r--nixos/tests/nixops/default.nix3
-rw-r--r--nixos/tests/pass-secret-service.nix1
-rw-r--r--nixos/tests/plasma6.nix64
-rw-r--r--nixos/tests/qemu-vm-external-disk-image.nix3
-rw-r--r--nixos/tests/sanoid.nix3
-rw-r--r--nixos/tests/searx.nix6
-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/virtualbox.nix47
-rw-r--r--nixos/tests/zfs.nix18
169 files changed, 4119 insertions, 921 deletions
diff --git a/nixos/doc/manual/configuration/x-windows.chapter.md b/nixos/doc/manual/configuration/x-windows.chapter.md
index 0451e4d25265..bf1872ae01ac 100644
--- a/nixos/doc/manual/configuration/x-windows.chapter.md
+++ b/nixos/doc/manual/configuration/x-windows.chapter.md
@@ -150,6 +150,7 @@ Or if you have an older card, you may have to use one of the legacy
 drivers:
 
 ```nix
+services.xserver.videoDrivers = [ "nvidiaLegacy470" ];
 services.xserver.videoDrivers = [ "nvidiaLegacy390" ];
 services.xserver.videoDrivers = [ "nvidiaLegacy340" ];
 services.xserver.videoDrivers = [ "nvidiaLegacy304" ];
diff --git a/nixos/doc/manual/development/replace-modules.section.md b/nixos/doc/manual/development/replace-modules.section.md
index ac9f5adbaf98..45e2adbc2608 100644
--- a/nixos/doc/manual/development/replace-modules.section.md
+++ b/nixos/doc/manual/development/replace-modules.section.md
@@ -47,9 +47,8 @@ without having to know its implementation details.
 ```nix
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
+  inherit (lib) mkIf mkOption types;
   cfg = config.programs.man;
 in
 
diff --git a/nixos/doc/manual/development/running-nixos-tests.section.md b/nixos/doc/manual/development/running-nixos-tests.section.md
index 33076f5dc2a7..b8191ebd313c 100644
--- a/nixos/doc/manual/development/running-nixos-tests.section.md
+++ b/nixos/doc/manual/development/running-nixos-tests.section.md
@@ -18,3 +18,13 @@ you can view a log of the test:
 ```ShellSession
 $ nix-store --read-log result
 ```
+
+## System Requirements {#sec-running-nixos-tests-requirements}
+
+NixOS tests require virtualization support.
+This means that the machine must have `kvm` in its [system features](https://nixos.org/manual/nix/stable/command-ref/conf-file.html?highlight=system-features#conf-system-features) list, or `apple-virt` in case of macOS.
+These features are autodetected locally, but `apple-virt` is only autodetected since Nix 2.19.0.
+
+Features of **remote builders** must additionally be configured manually on the client, e.g. on NixOS with [`nix.buildMachines.*.supportedFeatures`](https://search.nixos.org/options?show=nix.buildMachines.*.supportedFeatures&sort=alpha_asc&query=nix.buildMachines) or through general [Nix configuration](https://nixos.org/manual/nix/stable/advanced-topics/distributed-builds).
+
+If you run the tests on a **macOS** machine, you also need a "remote" builder for Linux; possibly a VM. [nix-darwin](https://daiderd.com/nix-darwin/) users may enable [`nix.linux-builder.enable`](https://daiderd.com/nix-darwin/manual/index.html#opt-nix.linux-builder.enable) to launch such a VM.
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/development/writing-modules.chapter.md b/nixos/doc/manual/development/writing-modules.chapter.md
index e07b899e6df7..20157a21e890 100644
--- a/nixos/doc/manual/development/writing-modules.chapter.md
+++ b/nixos/doc/manual/development/writing-modules.chapter.md
@@ -104,9 +104,8 @@ functions system environment substitution should *not* be disabled explicitly.
 ```nix
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
+  inherit (lib) concatStringsSep mkIf mkOption optionalString types;
   cfg = config.services.locate;
 in {
   options.services.locate = {
@@ -163,9 +162,7 @@ in {
 ::: {#exec-escaping-example .example}
 ### Escaping in Exec directives
 ```nix
-{ config, lib, pkgs, utils, ... }:
-
-with lib;
+{ config, pkgs, utils, ... }:
 
 let
   cfg = config.services.echo;
diff --git a/nixos/doc/manual/installation/installing.chapter.md b/nixos/doc/manual/installation/installing.chapter.md
index 815bcc071cd9..c7deb07352f1 100644
--- a/nixos/doc/manual/installation/installing.chapter.md
+++ b/nixos/doc/manual/installation/installing.chapter.md
@@ -272,6 +272,9 @@ update /etc/fstab.
     # parted /dev/sda -- mkpart ESP fat32 1MB 512MB
     # parted /dev/sda -- set 3 esp on
     ```
+    ::: {.note}
+    In case you decided to not create a swap partition, replace `3` by `2`. To be sure of the id number of ESP, run `parted --list`.
+    :::
 
 Once complete, you can follow with
 [](#sec-installation-manual-partitioning-formatting).
diff --git a/nixos/doc/manual/release-notes/rl-2405.section.md b/nixos/doc/manual/release-notes/rl-2405.section.md
index ac3d2b69a4a2..72389518b1ea 100644
--- a/nixos/doc/manual/release-notes/rl-2405.section.md
+++ b/nixos/doc/manual/release-notes/rl-2405.section.md
@@ -16,10 +16,20 @@ 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
@@ -42,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. -->
@@ -66,9 +78,15 @@ 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).
@@ -77,8 +95,12 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
 
 - [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)
@@ -89,20 +111,31 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
 
 - [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.
 
+- `unrar` was updated to v7. See [changelog](https://www.rarlab.com/unrar7notes.htm) for more information.
+
+- `k3s` was updated to [v1.29](https://github.com/k3s-io/k3s/releases/tag/v1.29.1%2Bk3s2). See [changelog and upgrade notes](https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.29.md#urgent-upgrade-notes) for more information.
+
 - `k9s` was updated to v0.31. There have been various breaking changes in the config file format,
   check out the changelog of [v0.29](https://github.com/derailed/k9s/releases/tag/v0.29.0),
   [v0.30](https://github.com/derailed/k9s/releases/tag/v0.30.0) and
   [v0.31](https://github.com/derailed/k9s/releases/tag/v0.31.0) for details. It is recommended
   to back up your current configuration and let k9s recreate the new base configuration.
 
+- The option `services.postgresql.ensureUsers._.ensurePermissions` has been removed as it's
+  not declarative and is broken with newer postgresql versions. Consider using
+  [](#opt-services.postgresql.ensureUsers._.ensureDBOwnership)
+  instead or a tool that's more suited for managing the data inside a postgresql database.
+
 - `idris2` was updated to v0.7.0. This version introduces breaking changes. Check out the [changelog](https://github.com/idris-lang/Idris2/blob/v0.7.0/CHANGELOG.md#v070) for details.
 
 - `neo4j` has been updated to 5, you may want to read the [release notes for Neo4j 5](https://neo4j.com/release-notes/database/neo4j-5/)
@@ -111,6 +144,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
@@ -124,6 +159,10 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
 
 - `paperless`' `services.paperless.extraConfig` setting has been removed and converted to the freeform type and option named `services.paperless.settings`.
 
+- `services.homepage-dashboard` now takes it's configuration using native Nix expressions, rather than dumping templated configurations into `/var/lib/homepage-dashboard` where they were previously managed manually. There are now new options which allow the configuration of bookmarks, services, widgets and custom CSS/JS natively in Nix.
+
+- `hare` may now be cross-compiled. For that to work, however, `haredoc` needed to stop being built together with it. Thus, the latter is now its own package with the name of `haredoc`.
+
 - The legacy and long deprecated systemd target `network-interfaces.target` has been removed. Use `network.target` instead.
 
 - `services.frp.settings` now generates the frp configuration file in TOML format as [recommended by upstream](https://github.com/fatedier/frp#configuration-files), instead of the legacy INI format. This has also introduced other changes in the configuration file structure and options.
@@ -136,6 +175,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.
@@ -173,6 +216,10 @@ 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`.
@@ -263,6 +310,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 ];``;
 
@@ -310,6 +361,8 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
 
 - A new hardening flag, `zerocallusedregs` was made available, corresponding to the gcc/clang option `-fzero-call-used-regs=used-gpr`.
 
+- A new hardening flag, `trivialautovarinit` was made available, corresponding to the gcc/clang option `-ftrivial-auto-var-init=pattern`.
+
 - New options were added to the dnsdist module to enable and configure a DNSCrypt endpoint (see `services.dnsdist.dnscrypt.enable`, etc.).
   The module can generate the DNSCrypt provider key pair, certificates and also performs their rotation automatically with no downtime.
 
@@ -364,6 +417,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 bc7880da9fe0..e4953ba72dd9 100644
--- a/nixos/lib/systemd-unit-options.nix
+++ b/nixos/lib/systemd-unit-options.nix
@@ -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/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/logger.py b/nixos/lib/test-driver/test_driver/logger.py
index 116244b5e4ae..0b0623bddfa1 100644
--- a/nixos/lib/test-driver/test_driver/logger.py
+++ b/nixos/lib/test-driver/test_driver/logger.py
@@ -1,6 +1,3 @@
-# mypy: disable-error-code="no-untyped-call"
-# drop the above line when mypy is upgraded to include
-# https://github.com/python/typeshed/commit/49b717ca52bf0781a538b04c0d76a5513f7119b8
 import codecs
 import os
 import sys
@@ -10,6 +7,7 @@ from contextlib import contextmanager
 from queue import Empty, Queue
 from typing import Any, Dict, Iterator
 from xml.sax.saxutils import XMLGenerator
+from xml.sax.xmlreader import AttributesImpl
 
 from colorama import Fore, Style
 
@@ -22,7 +20,7 @@ class Logger:
         self.queue: "Queue[Dict[str, str]]" = Queue()
 
         self.xml.startDocument()
-        self.xml.startElement("logfile", attrs={})
+        self.xml.startElement("logfile", attrs=AttributesImpl({}))
 
         self._print_serial_logs = True
 
@@ -44,7 +42,7 @@ class Logger:
         return message
 
     def log_line(self, message: str, attributes: Dict[str, str]) -> None:
-        self.xml.startElement("line", attributes)
+        self.xml.startElement("line", attrs=AttributesImpl(attributes))
         self.xml.characters(message)
         self.xml.endElement("line")
 
@@ -89,8 +87,8 @@ class Logger:
             )
         )
 
-        self.xml.startElement("nest", attrs={})
-        self.xml.startElement("head", attributes)
+        self.xml.startElement("nest", attrs=AttributesImpl({}))
+        self.xml.startElement("head", attrs=AttributesImpl(attributes))
         self.xml.characters(message)
         self.xml.endElement("head")
 
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/no-x-libs.nix b/nixos/modules/config/no-x-libs.nix
index 870b3fe77cca..fea6e0c4110b 100644
--- a/nixos/modules/config/no-x-libs.nix
+++ b/nixos/modules/config/no-x-libs.nix
@@ -66,7 +66,7 @@ with lib;
       networkmanager-sstp = super.networkmanager-vpnc.override { withGnome = false; };
       networkmanager-vpnc = super.networkmanager-vpnc.override { withGnome = false; };
       pango = super.pango.override { x11Support = false; };
-      pinentry = super.pinentry.override { enabledFlavors = [ "curses" "tty" "emacs" ]; withLibsecret = false; };
+      pinentry-curses = super.pinentry-curses.override { withLibsecret = false; };
       pipewire = super.pipewire.override { vulkanSupport = false; x11Support = false; };
       pythonPackagesExtensions = super.pythonPackagesExtensions ++ [
         (python-final: python-prev: {
diff --git a/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix
index dd34771c0b42..02cd1a17f538 100644
--- a/nixos/modules/config/users-groups.nix
+++ b/nixos/modules/config/users-groups.nix
@@ -704,6 +704,11 @@ in {
     in stringAfter [ "users" ] ''
       if [ -e ${lingerDir} ] ; then
         cd ${lingerDir}
+        for user in ${lingerDir}/*; do
+          if ! id "$user" >/dev/null 2>&1; then
+            rm --force -- "$user"
+          fi
+        done
         ls ${lingerDir} | sort | comm -3 -1 ${lingeringUsersFile} - | xargs -r ${pkgs.systemd}/bin/loginctl disable-linger
         ls ${lingerDir} | sort | comm -3 -2 ${lingeringUsersFile} - | xargs -r ${pkgs.systemd}/bin/loginctl  enable-linger
       fi
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/image/repart-image.nix b/nixos/modules/image/repart-image.nix
index 7ac47ee32ff4..5ae523c43f58 100644
--- a/nixos/modules/image/repart-image.nix
+++ b/nixos/modules/image/repart-image.nix
@@ -3,6 +3,7 @@
 
 { lib
 , runCommand
+, runCommandLocal
 , python3
 , black
 , ruff
@@ -33,6 +34,7 @@
 , seed
 , definitionsDirectory
 , sectorSize
+, mkfsEnv ? {}
 }:
 
 let
@@ -50,6 +52,11 @@ let
     mypy --strict $out
   '';
 
+  amendedRepartDefinitions = runCommandLocal "amended-repart.d" {} ''
+    definitions=$(${amendRepartDefinitions} ${partitions} ${definitionsDirectory})
+    cp -r $definitions $out
+  '';
+
   fileSystemToolMapping = {
     "vfat" = [ dosfstools mtools ];
     "ext4" = [ e2fsprogs.bin ];
@@ -74,28 +81,39 @@ in
 
 runCommand imageFileBasename
 {
+  __structuredAttrs = true;
+
   nativeBuildInputs = [
     systemd
     fakeroot
     util-linux
     compressionPkg
   ] ++ fileSystemTools;
-} ''
-  amendedRepartDefinitions=$(${amendRepartDefinitions} ${partitions} ${definitionsDirectory})
 
+  env = mkfsEnv;
+
+  systemdRepartFlags = [
+    "--dry-run=no"
+    "--empty=create"
+    "--size=auto"
+    "--seed=${seed}"
+    "--definitions=${amendedRepartDefinitions}"
+    "--split=${lib.boolToString split}"
+    "--json=pretty"
+  ] ++ lib.optionals (sectorSize != null) [
+    "--sector-size=${toString sectorSize}"
+  ];
+
+  passthru = {
+    inherit amendRepartDefinitions amendedRepartDefinitions;
+  };
+} ''
   mkdir -p $out
   cd $out
 
   echo "Building image with systemd-repart..."
   unshare --map-root-user fakeroot systemd-repart \
-    --dry-run=no \
-    --empty=create \
-    --size=auto \
-    --seed="${seed}" \
-    --definitions="$amendedRepartDefinitions" \
-    --split="${lib.boolToString split}" \
-    --json=pretty \
-    ${lib.optionalString (sectorSize != null) "--sector-size=${toString sectorSize}"} \
+    ''${systemdRepartFlags[@]} \
     ${imageFileBasename}.raw \
     | tee repart-output.json
 
diff --git a/nixos/modules/image/repart.nix b/nixos/modules/image/repart.nix
index 6a933f0d83cc..90c9c7e51dfa 100644
--- a/nixos/modules/image/repart.nix
+++ b/nixos/modules/image/repart.nix
@@ -60,6 +60,11 @@ let
       };
     };
   };
+
+  mkfsOptionsToEnv = opts: lib.mapAttrs' (fsType: options: {
+    name = "SYSTEMD_REPART_MKFS_OPTIONS_${lib.toUpper fsType}";
+    value = builtins.concatStringsSep " " options;
+  }) opts;
 in
 {
   options.image.repart = {
@@ -183,6 +188,29 @@ in
       '';
     };
 
+    mkfsOptions = lib.mkOption {
+      type = with lib.types; attrsOf (listOf str);
+      default = {};
+      example = lib.literalExpression ''
+        {
+          vfat = [ "-S 512" "-c" ];
+        }
+      '';
+      description = lib.mdDoc ''
+        Specify extra options for created file systems. The specified options
+        are converted to individual environment variables of the format
+        `SYSTEMD_REPART_MKFS_OPTIONS_<FSTYPE>`.
+
+        See [upstream systemd documentation](https://github.com/systemd/systemd/blob/v255/docs/ENVIRONMENT.md?plain=1#L575-L577)
+        for information about the usage of these environment variables.
+
+        The example would produce the following environment variable:
+        ```
+        SYSTEMD_REPART_MKFS_OPTIONS_VFAT="-S 512 -c"
+        ```
+      '';
+    };
+
   };
 
   config = {
@@ -239,11 +267,13 @@ in
           (lib.mapAttrs (_n: v: { Partition = v.repartConfig; }) finalPartitions);
 
         partitions = pkgs.writeText "partitions.json" (builtins.toJSON finalPartitions);
+
+        mkfsEnv = mkfsOptionsToEnv cfg.mkfsOptions;
       in
       pkgs.callPackage ./repart-image.nix {
         systemd = cfg.package;
         inherit (cfg) imageFileBasename compression split seed sectorSize;
-        inherit fileSystems definitionsDirectory partitions;
+        inherit fileSystems definitionsDirectory partitions mkfsEnv;
       };
 
     meta.maintainers = with lib.maintainers; [ nikstur ];
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 d9e95b8207f2..41e369ac1c65 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
@@ -162,6 +163,7 @@
   ./programs/clash-verge.nix
   ./programs/cnping.nix
   ./programs/command-not-found/command-not-found.nix
+  ./programs/coolercontrol.nix
   ./programs/criu.nix
   ./programs/darling.nix
   ./programs/dconf.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
@@ -547,6 +550,7 @@
   ./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
@@ -582,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
@@ -715,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
@@ -777,12 +783,14 @@
   ./services/misc/svnserve.nix
   ./services/misc/synergy.nix
   ./services/misc/sysprof.nix
+  ./services/misc/tabby.nix
   ./services/misc/tandoor-recipes.nix
   ./services/misc/taskserver
   ./services/misc/tautulli.nix
   ./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
@@ -1322,6 +1330,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
diff --git a/nixos/modules/programs/chromium.nix b/nixos/modules/programs/chromium.nix
index 45a9e9e2a689..5e8983730048 100644
--- a/nixos/modules/programs/chromium.nix
+++ b/nixos/modules/programs/chromium.nix
@@ -98,6 +98,24 @@ in
           }
         '';
       };
+
+      initialPrefs = mkOption {
+        type = types.attrs;
+        description = lib.mdDoc ''
+          Initial preferences are used to configure the browser for the first run.
+          Unlike {option}`programs.chromium.extraOpts`, initialPrefs can be changed by users in the browser settings.
+          More information can be found in the Chromium documentation:
+          <https://www.chromium.org/administrators/configuring-other-preferences/>
+        '';
+        default = {};
+        example = literalExpression ''
+          {
+            "first_run_tabs" = [
+              "https://nixos.org/"
+            ];
+          }
+        '';
+      };
     };
   };
 
@@ -110,6 +128,7 @@ in
         { source = "${cfg.plasmaBrowserIntegrationPackage}/etc/chromium/native-messaging-hosts/org.kde.plasma.browser_integration.json"; };
       "chromium/policies/managed/default.json" = lib.mkIf (defaultProfile != {}) { text = builtins.toJSON defaultProfile; };
       "chromium/policies/managed/extra.json" = lib.mkIf (cfg.extraOpts != {}) { text = builtins.toJSON cfg.extraOpts; };
+      "chromium/initial_preferences" = lib.mkIf (cfg.initialPrefs != {}) { text = builtins.toJSON cfg.initialPrefs; };
       # for google-chrome https://www.chromium.org/administrators/linux-quick-start
       "opt/chrome/native-messaging-hosts/org.kde.plasma.browser_integration.json" = lib.mkIf cfg.enablePlasmaBrowserIntegration
         { source = "${cfg.plasmaBrowserIntegrationPackage}/etc/opt/chrome/native-messaging-hosts/org.kde.plasma.browser_integration.json"; };
diff --git a/nixos/modules/programs/coolercontrol.nix b/nixos/modules/programs/coolercontrol.nix
new file mode 100644
index 000000000000..6e7299ad16b7
--- /dev/null
+++ b/nixos/modules/programs/coolercontrol.nix
@@ -0,0 +1,37 @@
+{ config
+, lib
+, pkgs
+, ...
+}:
+
+let
+  cfg = config.programs.coolercontrol;
+in
+{
+  ##### interface
+  options = {
+    programs.coolercontrol.enable = lib.mkEnableOption (lib.mdDoc "CoolerControl GUI & its background services");
+  };
+
+  ##### implementation
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = with pkgs.coolercontrol; [
+      coolercontrol-gui
+    ];
+
+    systemd = {
+      packages = with pkgs.coolercontrol; [
+        coolercontrol-liqctld
+        coolercontrold
+      ];
+
+      # https://github.com/NixOS/nixpkgs/issues/81138
+      services = {
+        coolercontrol-liqctld.wantedBy = [ "multi-user.target" ];
+        coolercontrold.wantedBy = [ "multi-user.target" ];
+      };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ OPNA2608 codifryed ];
+}
diff --git a/nixos/modules/programs/gnupg.nix b/nixos/modules/programs/gnupg.nix
index 8f82de033666..66be1f247fbd 100644
--- a/nixos/modules/programs/gnupg.nix
+++ b/nixos/modules/programs/gnupg.nix
@@ -1,8 +1,7 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
+  inherit (lib) mkRemovedOptionModule mkOption mkPackageOption types mkIf optionalString;
 
   cfg = config.programs.gnupg;
 
@@ -15,6 +14,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
@@ -25,8 +25,10 @@ let
       "curses";
 
 in
-
 {
+  imports = [
+    (mkRemovedOptionModule [ "programs" "gnupg" "agent" "pinentryFlavor" ] "Use programs.gnupg.agent.pinentryPackage instead")
+  ];
 
   options.programs.gnupg = {
     package = mkPackageOption pkgs "gnupg" { };
@@ -65,17 +67,17 @@ in
       '';
     };
 
-    agent.pinentryFlavor = mkOption {
-      type = types.nullOr (types.enum pkgs.pinentry.flavors);
-      example = "gnome3";
-      default = defaultPinentryFlavor;
-      defaultText = literalMD ''matching the configured desktop environment'';
+    agent.pinentryPackage = mkOption {
+      type = types.nullOr types.package;
+      example = lib.literalMD "pkgs.pinentry-gnome3";
+      default = pkgs.pinentry-curses;
+      defaultText = lib.literalMD "matching the configured desktop environment or `pkgs.pinentry-curses`";
       description = lib.mdDoc ''
-        Which pinentry interface to use. If not null, the path to the
-        pinentry binary will be set in /etc/gnupg/gpg-agent.conf.
-        If not set at all, it'll pick an appropriate flavor depending on the
-        system configuration (qt flavor for lxqt and plasma5, gtk2 for xfce
-        4.12, gnome3 on all other systems with X enabled, ncurses otherwise).
+        Which pinentry package to use. The path to the mainProgram as defined in
+        the package's meta attriutes will be set in /etc/gnupg/gpg-agent.conf.
+        If not set by the user, it'll pick an appropriate flavor depending on the
+        system configuration (qt flavor for lxqt and plasma5, gtk2 for xfce,
+        gnome3 on all other systems with X enabled, curses otherwise).
       '';
     };
 
@@ -101,9 +103,8 @@ in
   };
 
   config = mkIf cfg.agent.enable {
-    programs.gnupg.agent.settings = {
-      pinentry-program = lib.mkIf (cfg.agent.pinentryFlavor != null)
-        "${pkgs.pinentry.${cfg.agent.pinentryFlavor}}/bin/pinentry";
+    programs.gnupg.agent.settings = mkIf (cfg.agent.pinentryPackage != null) {
+      pinentry-program = lib.getExe cfg.agent.pinentryPackage;
     };
 
     environment.etc."gnupg/gpg-agent.conf".source =
@@ -206,9 +207,9 @@ in
       wantedBy = [ "sockets.target" ];
     };
 
-    services.dbus.packages = mkIf (cfg.agent.pinentryFlavor == "gnome3") [ pkgs.gcr ];
+    services.dbus.packages = mkIf (lib.elem "gnome3" (cfg.agent.pinentryPackage.flavors or [])) [ pkgs.gcr ];
 
-    environment.systemPackages = with pkgs; [ cfg.package ];
+    environment.systemPackages = [ cfg.package ];
 
     environment.interactiveShellInit = ''
       # Bind gpg-agent to this TTY if gpg commands are used.
@@ -229,12 +230,10 @@ in
     '';
 
     assertions = [
-      { assertion = cfg.agent.enableSSHSupport -> !config.programs.ssh.startAgent;
+      {
+        assertion = cfg.agent.enableSSHSupport -> !config.programs.ssh.startAgent;
         message = "You can't use ssh-agent and GnuPG agent with SSH support enabled at the same time!";
       }
     ];
   };
-
-  # uses attributes of the linked package
-  meta.buildDocsInSandbox = false;
 }
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/steam.nix b/nixos/modules/programs/steam.nix
index c7f1e622f7ba..31803f061dce 100644
--- a/nixos/modules/programs/steam.nix
+++ b/nixos/modules/programs/steam.nix
@@ -43,6 +43,9 @@ in {
         }
       '';
       apply = steam: steam.override (prev: {
+        extraEnv = (lib.optionalAttrs (cfg.extraCompatPackages != [ ]) {
+            STEAM_EXTRA_COMPAT_TOOLS_PATHS = makeBinPath cfg.extraCompatPackages;
+          }) // (prev.extraEnv or {});
         extraLibraries = pkgs: let
           prevLibs = if prev ? extraLibraries then prev.extraLibraries pkgs else [ ];
           additionalLibs = with config.hardware.opengl;
@@ -56,6 +59,8 @@ in {
           # use the setuid wrapped bubblewrap
           bubblewrap = "${config.security.wrapperDir}/..";
         };
+      } // optionalAttrs cfg.extest.enable {
+        extraEnv.LD_PRELOAD = "${pkgs.pkgsi686Linux.extest}/lib/libextest.so";
       });
       description = lib.mdDoc ''
         The Steam package to use. Additional libraries are added from the system
@@ -66,6 +71,16 @@ in {
       '';
     };
 
+    extraCompatPackages = mkOption {
+      type = types.listOf types.package;
+      default = [ ];
+      description = lib.mdDoc ''
+        Extra packages to be used as compatibility tools for Steam on Linux. Packages will be included
+        in the `STEAM_EXTRA_COMPAT_TOOLS_PATHS` environmental variable. For more information see
+        <https://github.com/ValveSoftware/steam-for-linux/issues/6310">.
+      '';
+    };
+
     remotePlay.openFirewall = mkOption {
       type = types.bool;
       default = false;
@@ -114,6 +129,11 @@ in {
         };
       };
     };
+
+    extest.enable = mkEnableOption (lib.mdDoc ''
+      Load the extest library into Steam, to translate X11 input events to
+      uinput events (e.g. for using Steam Input on Wayland)
+    '');
   };
 
   config = mkIf cfg.enable {
@@ -167,5 +187,5 @@ in {
     ];
   };
 
-  meta.maintainers = with maintainers; [ mkg20001 ];
+  meta.maintainers = teams.steam;
 }
diff --git a/nixos/modules/programs/wayland/sway.nix b/nixos/modules/programs/wayland/sway.nix
index 57ee629b2881..2bd297af5254 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; [
@@ -152,6 +152,7 @@ in {
             '';
           }
         ];
+
         environment = {
           systemPackages = optional (cfg.package != null) cfg.package ++ cfg.extraPackages;
           # Needed for the default wallpaper:
@@ -166,8 +167,12 @@ in {
             "sway/config".source = mkOptionDefault "${cfg.package}/etc/sway/config";
           };
         };
+
+        programs.gnupg.agent.pinentryPackage = lib.mkDefault pkgs.pinentry-gnome3;
+
         # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1050913
         xdg.portal.config.sway.default = mkDefault [ "wlr" "gtk" ];
+
         # To make a Sway session available if a display manager like SDDM is enabled:
         services.xserver.displayManager.sessionPackages = optionals (cfg.package != null) [ cfg.package ]; }
       (import ./wayland-session.nix { inherit lib pkgs; })
diff --git a/nixos/modules/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/backup/syncoid.nix b/nixos/modules/services/backup/syncoid.nix
index 7b8d3b431309..4a04f0aa1622 100644
--- a/nixos/modules/services/backup/syncoid.nix
+++ b/nixos/modules/services/backup/syncoid.nix
@@ -134,7 +134,7 @@ in
     localSourceAllow = mkOption {
       type = types.listOf types.str;
       # Permissions snapshot and destroy are in case --no-sync-snap is not used
-      default = [ "bookmark" "hold" "send" "snapshot" "destroy" ];
+      default = [ "bookmark" "hold" "send" "snapshot" "destroy" "mount" ];
       description = lib.mdDoc ''
         Permissions granted for the {option}`services.syncoid.user` user
         for local source datasets. See
diff --git a/nixos/modules/services/databases/memcached.nix b/nixos/modules/services/databases/memcached.nix
index 542c80ab2e67..fd943c20091a 100644
--- a/nixos/modules/services/databases/memcached.nix
+++ b/nixos/modules/services/databases/memcached.nix
@@ -37,7 +37,7 @@ in
         description = lib.mdDoc "The port to bind to.";
       };
 
-      enableUnixSocket = mkEnableOption (lib.mdDoc "unix socket at /run/memcached/memcached.sock");
+      enableUnixSocket = mkEnableOption (lib.mdDoc "Unix Domain Socket at /run/memcached/memcached.sock instead of listening on an IP address and port. The `listen` and `port` options are ignored.");
 
       maxMemory = mkOption {
         type = types.ints.unsigned;
diff --git a/nixos/modules/services/databases/postgresql.nix b/nixos/modules/services/databases/postgresql.nix
index ed5915735730..c4e76c82ba5c 100644
--- a/nixos/modules/services/databases/postgresql.nix
+++ b/nixos/modules/services/databases/postgresql.nix
@@ -161,33 +161,6 @@ in
               '';
             };
 
-            ensurePermissions = mkOption {
-              type = types.attrsOf types.str;
-              default = {};
-              visible = false; # This option has been deprecated.
-              description = lib.mdDoc ''
-                This option is DEPRECATED and should not be used in nixpkgs anymore,
-                use `ensureDBOwnership` instead. It can also break with newer
-                versions of PostgreSQL (≥ 15).
-
-                Permissions to ensure for the user, specified as an attribute set.
-                The attribute names specify the database and tables to grant the permissions for.
-                The attribute values specify the permissions to grant. You may specify one or
-                multiple comma-separated SQL privileges here.
-
-                For more information on how to specify the target
-                and on which privileges exist, see the
-                [GRANT syntax](https://www.postgresql.org/docs/current/sql-grant.html).
-                The attributes are used as `GRANT ''${attrValue} ON ''${attrName}`.
-              '';
-              example = literalExpression ''
-                {
-                  "DATABASE \"nextcloud\"" = "ALL PRIVILEGES";
-                  "ALL TABLES IN SCHEMA public" = "ALL PRIVILEGES";
-                }
-              '';
-            };
-
             ensureDBOwnership = mkOption {
               type = types.bool;
               default = false;
@@ -460,16 +433,6 @@ in
         Offender: ${name} has not been found among databases.
       '';
     }) cfg.ensureUsers;
-    # `ensurePermissions` is now deprecated, let's avoid it.
-    warnings = lib.optional (any ({ ensurePermissions, ... }: ensurePermissions != {}) cfg.ensureUsers) "
-      `services.postgresql.ensureUsers.*.ensurePermissions` is used in your expressions,
-      this option is known to be broken with newer PostgreSQL versions,
-      consider migrating to `services.postgresql.ensureUsers.*.ensureDBOwnership` or
-      consult the release notes or manual for more migration guidelines.
-
-      This option will be removed in NixOS 24.05 unless it sees significant
-      maintenance improvements.
-    ";
 
     services.postgresql.settings =
       {
@@ -583,11 +546,6 @@ in
               concatMapStrings
               (user:
               let
-                  userPermissions = concatStringsSep "\n"
-                    (mapAttrsToList
-                      (database: permission: ''$PSQL -tAc 'GRANT ${permission} ON ${database} TO "${user.name}"' '')
-                      user.ensurePermissions
-                    );
                   dbOwnershipStmt = optionalString
                     user.ensureDBOwnership
                     ''$PSQL -tAc 'ALTER DATABASE "${user.name}" OWNER TO "${user.name}";' '';
@@ -599,7 +557,6 @@ in
                   userClauses = ''$PSQL -tAc 'ALTER ROLE "${user.name}" ${concatStringsSep " " clauseSqlStatements}' '';
                 in ''
                   $PSQL -tAc "SELECT 1 FROM pg_roles WHERE rolname='${user.name}'" | grep -q 1 || $PSQL -tAc 'CREATE USER "${user.name}"'
-                  ${userPermissions}
                   ${userClauses}
 
                   ${dbOwnershipStmt}
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/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/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/hardware/fwupd.nix b/nixos/modules/services/hardware/fwupd.nix
index 8a9e38d0547b..c4837ff80ec7 100644
--- a/nixos/modules/services/hardware/fwupd.nix
+++ b/nixos/modules/services/hardware/fwupd.nix
@@ -14,11 +14,11 @@ let
 
   customEtc = {
     "fwupd/fwupd.conf" = {
-      source = format.generate "fwupd.conf" {
+      source = format.generate "fwupd.conf" ({
         fwupd = cfg.daemonSettings;
       } // lib.optionalAttrs (lib.length (lib.attrNames cfg.uefiCapsuleSettings) != 0) {
         uefi_capsule = cfg.uefiCapsuleSettings;
-      };
+      });
       # fwupd tries to chmod the file if it doesn't have the right permissions
       mode = "0640";
     };
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
index a90d234f65c0..1aaa2d07b9bd 100644
--- 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
@@ -1,37 +1,58 @@
-{ config, lib, pkgs }: let
+{
+  addDriverRunpath,
+  glibc,
+  jq,
+  lib,
+  nvidia-container-toolkit,
+  nvidia-driver,
+  runtimeShell,
+  writeScriptBin,
+}:
+let
   mountOptions = { options = ["ro" "nosuid" "nodev" "bind"]; };
   mounts = [
-    { hostPath = "${lib.getBin config.hardware.nvidia.package}/bin/nvidia-cuda-mps-control";
+    # FIXME: Making /usr mounts optional
+    { hostPath = lib.getExe' nvidia-driver "nvidia-cuda-mps-control";
       containerPath = "/usr/bin/nvidia-cuda-mps-control"; }
-    { hostPath = "${lib.getBin config.hardware.nvidia.package}/bin/nvidia-cuda-mps-server";
+    { hostPath = lib.getExe' nvidia-driver "nvidia-cuda-mps-server";
       containerPath = "/usr/bin/nvidia-cuda-mps-server"; }
-    { hostPath = "${lib.getBin config.hardware.nvidia.package}/bin/nvidia-debugdump";
+    { hostPath = lib.getExe' nvidia-driver "nvidia-debugdump";
       containerPath = "/usr/bin/nvidia-debugdump"; }
-    { hostPath = "${lib.getBin config.hardware.nvidia.package}/bin/nvidia-powerd";
+    { hostPath = lib.getExe' nvidia-driver "nvidia-powerd";
       containerPath = "/usr/bin/nvidia-powerd"; }
-    { hostPath = "${lib.getBin config.hardware.nvidia.package}/bin/nvidia-smi";
+    { hostPath = lib.getExe' nvidia-driver "nvidia-smi";
       containerPath = "/usr/bin/nvidia-smi"; }
-    { hostPath = "${pkgs.nvidia-container-toolkit}/bin/nvidia-ctk";
+    { hostPath = lib.getExe' nvidia-container-toolkit "nvidia-ctk";
       containerPath = "/usr/bin/nvidia-ctk"; }
-    { hostPath = "${pkgs.glibc}/lib";
-      containerPath = "${pkgs.glibc}/lib"; }
-    { hostPath = "${pkgs.glibc}/lib64";
-      containerPath = "${pkgs.glibc}/lib64"; }
+    { hostPath = "${lib.getLib glibc}/lib";
+      containerPath = "${lib.getLib glibc}/lib"; }
+
+    # FIXME: use closureinfo
+    {
+      hostPath = addDriverRunpath.driverLink;
+      containerPath = addDriverRunpath.driverLink;
+    }
+    { hostPath = "${lib.getLib glibc}/lib";
+      containerPath = "${lib.getLib glibc}/lib"; }
+    { hostPath = "${lib.getLib glibc}/lib64";
+      containerPath = "${lib.getLib glibc}/lib64"; }
   ];
   jqAddMountExpression = ".containerEdits.mounts[.containerEdits.mounts | length] |= . +";
   mountsToJq = lib.concatMap
     (mount:
-      ["${pkgs.jq}/bin/jq '${jqAddMountExpression} ${builtins.toJSON (mount // mountOptions)}'"])
+      ["${lib.getExe jq} '${jqAddMountExpression} ${builtins.toJSON (mount // mountOptions)}'"])
     mounts;
-in ''
-#! ${pkgs.runtimeShell}
+in
+writeScriptBin "nvidia-cdi-generator"
+''
+#! ${runtimeShell}
 
 function cdiGenerate {
-  ${pkgs.nvidia-container-toolkit}/bin/nvidia-ctk cdi generate \
+  ${lib.getExe' nvidia-container-toolkit "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
+    --ldconfig-path ${lib.getExe' glibc "ldconfig"} \
+    --library-search-path ${lib.getLib nvidia-driver}/lib \
+    --nvidia-ctk-path ${lib.getExe' nvidia-container-toolkit "nvidia-ctk"}
 }
 
 cdiGenerate | \
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
index 3c96e9c41be5..b95bdf191fad 100644
--- a/nixos/modules/services/hardware/nvidia-container-toolkit-cdi-generator/default.nix
+++ b/nixos/modules/services/hardware/nvidia-container-toolkit-cdi-generator/default.nix
@@ -26,9 +26,11 @@
       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);
+        ExecStart =
+          let
+            script = pkgs.callPackage ./cdi-generate.nix { nvidia-driver = config.hardware.nvidia.package; };
+          in
+          lib.getExe script;
         Type = "oneshot";
       };
     };
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/mail/listmonk.nix b/nixos/modules/services/mail/listmonk.nix
index 945eb436c1f2..d6399304cc10 100644
--- a/nixos/modules/services/mail/listmonk.nix
+++ b/nixos/modules/services/mail/listmonk.nix
@@ -187,7 +187,11 @@ in {
           # Indeed, it will try to create all the folders and realize one of them already exist.
           # Therefore, we have to create it ourselves.
           ''${pkgs.coreutils}/bin/mkdir -p "''${STATE_DIRECTORY}/listmonk/uploads"''
-          "${cfg.package}/bin/listmonk --config ${cfgFile} --idempotent --install --upgrade --yes"
+          # setup database if not already done
+          "${cfg.package}/bin/listmonk --config ${cfgFile} --idempotent --install --yes"
+          # apply db migrations (setup and migrations can not be done in one step
+          # with "--install --upgrade" listmonk ignores the upgrade)
+          "${cfg.package}/bin/listmonk --config ${cfgFile} --upgrade --yes"
           "${updateDatabaseConfigScript}/bin/update-database-config.sh"
         ];
         ExecStart = "${cfg.package}/bin/listmonk --config ${cfgFile}";
diff --git a/nixos/modules/services/matrix/matrix-sliding-sync.nix b/nixos/modules/services/matrix/matrix-sliding-sync.nix
index 8b22cd7dba80..d62e41bebd64 100644
--- a/nixos/modules/services/matrix/matrix-sliding-sync.nix
+++ b/nixos/modules/services/matrix/matrix-sliding-sync.nix
@@ -37,7 +37,7 @@ in
             type = lib.types.str;
             default = "127.0.0.1:8009";
             example = "[::]:8008";
-            description = lib.mdDoc "The interface and port to listen on.";
+            description = lib.mdDoc "The interface and port or path (for unix socket) to listen on.";
           };
 
           SYNCV3_LOG_LEVEL = lib.mkOption {
@@ -98,6 +98,7 @@ in
         ExecStart = lib.getExe cfg.package;
         StateDirectory = "matrix-sliding-sync";
         WorkingDirectory = "%S/matrix-sliding-sync";
+        RuntimeDirectory = "matrix-sliding-sync";
         Restart = "on-failure";
         RestartSec = "1s";
       };
diff --git a/nixos/modules/services/misc/atuin.nix b/nixos/modules/services/misc/atuin.nix
index 7637e06141ca..7e89929884d6 100644
--- a/nixos/modules/services/misc/atuin.nix
+++ b/nixos/modules/services/misc/atuin.nix
@@ -54,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
+          '';
         };
       };
     };
@@ -134,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/etebase-server.nix b/nixos/modules/services/misc/etebase-server.nix
index 045048a1a2e3..f5a5e8a780d4 100644
--- a/nixos/modules/services/misc/etebase-server.nix
+++ b/nixos/modules/services/misc/etebase-server.nix
@@ -5,9 +5,6 @@ with lib;
 let
   cfg = config.services.etebase-server;
 
-  pythonEnv = pkgs.python3.withPackages (ps: with ps;
-    [ etebase-server daphne ]);
-
   iniFmt = pkgs.formats.ini {};
 
   configIni = iniFmt.generate "etebase-server.ini" cfg.settings;
@@ -46,6 +43,13 @@ in
         '';
       };
 
+      package = mkOption {
+        type = types.package;
+        default = pkgs.python3.pkgs.etebase-server;
+        defaultText = literalExpression "pkgs.python3.pkgs.etebase-server";
+        description = lib.mdDoc "etebase-server package to use.";
+      };
+
       dataDir = mkOption {
         type = types.str;
         default = "/var/lib/etebase-server";
@@ -164,7 +168,7 @@ in
       (runCommand "etebase-server" {
         nativeBuildInputs = [ makeWrapper ];
       } ''
-        makeWrapper ${pythonEnv}/bin/etebase-server \
+        makeWrapper ${cfg.package}/bin/etebase-server \
           $out/bin/etebase-server \
           --chdir ${escapeShellArg cfg.dataDir} \
           --prefix ETEBASE_EASY_CONFIG_PATH : "${configIni}"
@@ -178,8 +182,8 @@ in
     systemd.services.etebase-server = {
       description = "An Etebase (EteSync 2.0) server";
       after = [ "network.target" "systemd-tmpfiles-setup.service" ];
+      path = [ cfg.package ];
       wantedBy = [ "multi-user.target" ];
-      path = [ pythonEnv ];
       serviceConfig = {
         User = cfg.user;
         Restart = "always";
@@ -187,24 +191,26 @@ in
       };
       environment = {
         ETEBASE_EASY_CONFIG_PATH = configIni;
+        PYTHONPATH = cfg.package.pythonPath;
       };
       preStart = ''
         # Auto-migrate on first run or if the package has changed
         versionFile="${cfg.dataDir}/src-version"
-        if [[ $(cat "$versionFile" 2>/dev/null) != ${pkgs.etebase-server} ]]; then
+        if [[ $(cat "$versionFile" 2>/dev/null) != ${cfg.package} ]]; then
           etebase-server migrate --no-input
           etebase-server collectstatic --no-input --clear
-          echo ${pkgs.etebase-server} > "$versionFile"
+          echo ${cfg.package} > "$versionFile"
         fi
       '';
       script =
         let
+          python = cfg.package.python;
           networking = if cfg.unixSocket != null
-          then "-u ${cfg.unixSocket}"
-          else "-b 0.0.0.0 -p ${toString cfg.port}";
+          then "--uds ${cfg.unixSocket}"
+          else "--host 0.0.0.0 --port ${toString cfg.port}";
         in ''
-          cd "${pythonEnv}/lib/etebase-server";
-          daphne ${networking} \
+          ${python.pkgs.uvicorn}/bin/uvicorn ${networking} \
+            --app-dir ${cfg.package}/${cfg.package.python.sitePackages} \
             etebase_server.asgi:application
         '';
     };
diff --git a/nixos/modules/services/misc/homepage-dashboard.nix b/nixos/modules/services/misc/homepage-dashboard.nix
index 07a09e2b6bbf..02f1378cb0d5 100644
--- a/nixos/modules/services/misc/homepage-dashboard.nix
+++ b/nixos/modules/services/misc/homepage-dashboard.nix
@@ -6,6 +6,8 @@
 
 let
   cfg = config.services.homepage-dashboard;
+  # Define the settings format used for this program
+  settingsFormat = pkgs.formats.yaml { };
 in
 {
   options = {
@@ -25,31 +27,217 @@ in
         default = 8082;
         description = lib.mdDoc "Port for Homepage to bind to.";
       };
+
+      environmentFile = lib.mkOption {
+        type = lib.types.str;
+        description = ''
+          The path to an environment file that contains environment variables to pass
+          to the homepage-dashboard service, for the purpose of passing secrets to
+          the service.
+
+          See the upstream documentation:
+
+          https://gethomepage.dev/latest/installation/docker/#using-environment-secrets
+        '';
+        default = "";
+      };
+
+      customCSS = lib.mkOption {
+        type = lib.types.lines;
+        description = lib.mdDoc ''
+          Custom CSS for styling Homepage.
+
+          See https://gethomepage.dev/latest/configs/custom-css-js/.
+        '';
+        default = "";
+      };
+
+      customJS = lib.mkOption {
+        type = lib.types.lines;
+        description = lib.mdDoc ''
+          Custom Javascript for Homepage.
+
+          See https://gethomepage.dev/latest/configs/custom-css-js/.
+        '';
+        default = "";
+      };
+
+      bookmarks = lib.mkOption {
+        inherit (settingsFormat) type;
+        description = lib.mdDoc ''
+          Homepage bookmarks configuration.
+
+          See https://gethomepage.dev/latest/configs/bookmarks/.
+        '';
+        # Defaults: https://github.com/gethomepage/homepage/blob/main/src/skeleton/bookmarks.yaml
+        example = [
+          {
+            Developer = [
+              { Github = [{ abbr = "GH"; href = "https://github.com/"; }]; }
+            ];
+          }
+          {
+            Entertainment = [
+              { YouTube = [{ abbr = "YT"; href = "https://youtube.com/"; }]; }
+            ];
+          }
+        ];
+        default = [ ];
+      };
+
+      services = lib.mkOption {
+        inherit (settingsFormat) type;
+        description = lib.mdDoc ''
+          Homepage services configuration.
+
+          See https://gethomepage.dev/latest/configs/services/.
+        '';
+        # Defaults: https://github.com/gethomepage/homepage/blob/main/src/skeleton/services.yaml
+        example = [
+          {
+            "My First Group" = [
+              {
+                "My First Service" = {
+                  href = "http://localhost/";
+                  description = "Homepage is awesome";
+                };
+              }
+            ];
+          }
+          {
+            "My Second Group" = [
+              {
+                "My Second Service" = {
+                  href = "http://localhost/";
+                  description = "Homepage is the best";
+                };
+              }
+            ];
+          }
+        ];
+        default = [ ];
+      };
+
+      widgets = lib.mkOption {
+        inherit (settingsFormat) type;
+        description = lib.mdDoc ''
+          Homepage widgets configuration.
+
+          See https://gethomepage.dev/latest/configs/service-widgets/.
+        '';
+        # Defaults: https://github.com/gethomepage/homepage/blob/main/src/skeleton/widgets.yaml
+        example = [
+          {
+            resources = {
+              cpu = true;
+              memory = true;
+              disk = "/";
+            };
+          }
+          {
+            search = {
+              provider = "duckduckgo";
+              target = "_blank";
+            };
+          }
+        ];
+        default = [ ];
+      };
+
+      kubernetes = lib.mkOption {
+        inherit (settingsFormat) type;
+        description = lib.mdDoc ''
+          Homepage kubernetes configuration.
+
+          See https://gethomepage.dev/latest/configs/kubernetes/.
+        '';
+        default = { };
+      };
+
+      docker = lib.mkOption {
+        inherit (settingsFormat) type;
+        description = lib.mdDoc ''
+          Homepage docker configuration.
+
+          See https://gethomepage.dev/latest/configs/docker/.
+        '';
+        default = { };
+      };
+
+      settings = lib.mkOption {
+        inherit (settingsFormat) type;
+        description = lib.mdDoc ''
+          Homepage settings.
+
+          See https://gethomepage.dev/latest/configs/settings/.
+        '';
+        # Defaults: https://github.com/gethomepage/homepage/blob/main/src/skeleton/settings.yaml
+        default = { };
+      };
     };
   };
 
-  config = lib.mkIf cfg.enable {
-    systemd.services.homepage-dashboard = {
-      description = "Homepage Dashboard";
-      after = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
+  config =
+    let
+      # If homepage-dashboard is enabled, but none of the configuration values have been updated,
+      # then default to "unmanaged" configuration which is manually updated in
+      # var/lib/homepage-dashboard. This is to maintain backwards compatibility, and should be
+      # deprecated in a future release.
+      managedConfig = !(
+        cfg.bookmarks == [ ] &&
+        cfg.customCSS == "" &&
+        cfg.customJS == "" &&
+        cfg.docker == { } &&
+        cfg.kubernetes == { } &&
+        cfg.services == [ ] &&
+        cfg.settings == { } &&
+        cfg.widgets == [ ]
+      );
+
+      configDir = if managedConfig then "/etc/homepage-dashboard" else "/var/lib/homepage-dashboard";
+
+      msg = "using unmanaged configuration for homepage-dashboard is deprecated and will be removed"
+        + " in 24.05. please see the NixOS documentation for `services.homepage-dashboard' and add"
+        + " your bookmarks, services, widgets, and other configuration using the options provided.";
+    in
+    lib.mkIf cfg.enable {
+      warnings = lib.optional (!managedConfig) msg;
 
-      environment = {
-        HOMEPAGE_CONFIG_DIR = "/var/lib/homepage-dashboard";
-        PORT = "${toString cfg.listenPort}";
+      environment.etc = lib.mkIf managedConfig {
+        "homepage-dashboard/custom.css".text = cfg.customCSS;
+        "homepage-dashboard/custom.js".text = cfg.customJS;
+
+        "homepage-dashboard/bookmarks.yaml".source = settingsFormat.generate "bookmarks.yaml" cfg.bookmarks;
+        "homepage-dashboard/docker.yaml".source = settingsFormat.generate "docker.yaml" cfg.docker;
+        "homepage-dashboard/kubernetes.yaml".source = settingsFormat.generate "kubernetes.yaml" cfg.kubernetes;
+        "homepage-dashboard/services.yaml".source = settingsFormat.generate "services.yaml" cfg.services;
+        "homepage-dashboard/settings.yaml".source = settingsFormat.generate "settings.yaml" cfg.settings;
+        "homepage-dashboard/widgets.yaml".source = settingsFormat.generate "widgets.yaml" cfg.widgets;
       };
 
-      serviceConfig = {
-        Type = "simple";
-        DynamicUser = true;
-        StateDirectory = "homepage-dashboard";
-        ExecStart = "${lib.getExe cfg.package}";
-        Restart = "on-failure";
+      systemd.services.homepage-dashboard = {
+        description = "Homepage Dashboard";
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+
+        environment = {
+          HOMEPAGE_CONFIG_DIR = configDir;
+          PORT = toString cfg.listenPort;
+          LOG_TARGETS = lib.mkIf managedConfig "stdout";
+        };
+
+        serviceConfig = {
+          Type = "simple";
+          DynamicUser = true;
+          EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
+          StateDirectory = lib.mkIf (!managedConfig) "homepage-dashboard";
+          ExecStart = lib.getExe cfg.package;
+          Restart = "on-failure";
+        };
       };
-    };
 
-    networking.firewall = lib.mkIf cfg.openFirewall {
-      allowedTCPPorts = [ cfg.listenPort ];
+      networking.firewall = lib.mkIf cfg.openFirewall {
+        allowedTCPPorts = [ cfg.listenPort ];
+      };
     };
-  };
 }
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/tabby.nix b/nixos/modules/services/misc/tabby.nix
new file mode 100644
index 000000000000..a3072e5df75e
--- /dev/null
+++ b/nixos/modules/services/misc/tabby.nix
@@ -0,0 +1,203 @@
+{ config, lib, pkgs, ... }:
+let
+  inherit (lib) types;
+
+  cfg = config.services.tabby;
+  format = pkgs.formats.toml { };
+  tabbyPackage = cfg.package.override {
+    inherit (cfg) acceleration;
+  };
+in
+{
+  options = {
+    services.tabby = {
+      enable = lib.mkEnableOption (
+        lib.mdDoc "Self-hosted AI coding assistant using large language models"
+      );
+
+      package = lib.mkPackageOption pkgs "tabby" { };
+
+      port = lib.mkOption {
+        type = types.port;
+        default = 11029;
+        description = lib.mdDoc ''
+          Specifies the bind port on which the tabby server HTTP interface listens.
+        '';
+      };
+
+      model = lib.mkOption {
+        type = types.str;
+        default = "TabbyML/StarCoder-1B";
+        description = lib.mdDoc ''
+          Specify the model that tabby will use to generate completions.
+
+          This model will be downloaded automatically if it is not already present.
+
+          If you want to utilize an existing model that you've already
+          downloaded you'll need to move it into tabby's state directory which
+          lives in `/var/lib/tabby`. Because the tabby.service is configured to
+          use a DyanmicUser the service will need to have been started at least
+          once before you can move the locally existing model into
+          `/var/lib/tabby`. You can set the model to 'none' and tabby will
+          startup and fail to download a model, but will have created the
+          `/var/lib/tabby` directory. You can then copy over the model manually
+          into `/var/lib/tabby`, update the model option to the name you just
+          downloaded and copied over then `nixos-rebuild switch` to start using
+          it.
+
+          $ tabby download --model TabbyML/DeepseekCoder-6.7B
+          $ find ~/.tabby/ | tail -n1
+          /home/ghthor/.tabby/models/TabbyML/DeepseekCoder-6.7B/ggml/q8_0.v2.gguf
+          $ sudo rsync -r ~/.tabby/models/ /var/lib/tabby/models/
+          $ sudo chown -R tabby:tabby /var/lib/tabby/models/
+
+          See for Model Options:
+          > https://github.com/TabbyML/registry-tabby
+        '';
+      };
+
+      acceleration = lib.mkOption {
+        type = types.nullOr (types.enum [ "cpu" "rocm" "cuda" "metal" ]);
+        default = null;
+        example = "rocm";
+        description = lib.mdDoc ''
+          Specifies the device to use for hardware acceleration.
+
+          -   `cpu`: no acceleration just use the CPU
+          -  `rocm`: supported by modern AMD GPUs
+          -  `cuda`: supported by modern NVIDIA GPUs
+          - `metal`: supported on darwin aarch64 machines
+
+          Tabby will try and determine what type of acceleration that is
+          already enabled in your configuration when `acceleration = null`.
+
+          - nixpkgs.config.cudaSupport
+          - nixpkgs.config.rocmSupport
+          - if stdenv.isDarwin && stdenv.isAarch64
+
+          IFF multiple acceleration methods are found to be enabled or if you
+          haven't set either `cudaSupport or rocmSupport` you will have to
+          specify the device type manually here otherwise it will default to
+          the first from the list above or to cpu.
+        '';
+      };
+
+      settings = lib.mkOption {
+        inherit (format) type;
+        default = { };
+        description = lib.mdDoc ''
+          Tabby scheduler configuration
+
+          See for more details:
+          > https://tabby.tabbyml.com/docs/configuration/#repository-context-for-code-completion
+        '';
+        example = lib.literalExpression ''
+          settings = {
+            repositories = [
+              { name = "tabby"; git_url = "https://github.com/TabbyML/tabby.git"; }
+              { name = "CTranslate2"; git_url = "git@github.com:OpenNMT/CTranslate2.git"; }
+
+              # local directory is also supported, but limited by systemd DynamicUser=1
+              # adding local repositories will need to be done manually
+              { name = "repository_a"; git_url = "file:///var/lib/tabby/repository_a"; }
+            ];
+          };
+        '';
+      };
+
+      usageCollection = lib.mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Enable sending anonymous usage data.
+
+          See for more details:
+          > https://tabby.tabbyml.com/docs/configuration#usage-collection
+        '';
+      };
+
+      indexInterval = lib.mkOption {
+        type = types.str;
+        default = "5hours";
+        example = "5hours";
+        description = lib.mdDoc ''
+          Run tabby scheduler to generate the index database at this interval.
+          Updates by default every 5 hours. This value applies to
+          `OnUnitInactiveSec`
+
+          The format is described in
+          {manpage}`systemd.time(7)`.
+
+          To disable running `tabby scheduler --now` updates, set to `"never"`
+        '';
+      };
+    };
+  };
+
+  # TODO(ghthor): firewall config
+
+  config = lib.mkIf cfg.enable {
+    environment = {
+      etc."tabby/config.toml".source = format.generate "config.toml" cfg.settings;
+      systemPackages = [ tabbyPackage ];
+    };
+
+
+    systemd = let
+      serviceUser = {
+        WorkingDirectory = "/var/lib/tabby";
+        StateDirectory = [ "tabby" ];
+        ConfigurationDirectory = [ "tabby" ];
+        DynamicUser = true;
+        User = "tabby";
+        Group = "tabby";
+      };
+
+      serviceEnv = lib.mkMerge [
+        {
+          TABBY_ROOT = "%S/tabby";
+        }
+        (lib.mkIf (!cfg.usageCollection) {
+          TABBY_DISABLE_USAGE_COLLECTION = "1";
+        })
+      ];
+    in {
+      services.tabby = {
+        wantedBy = [ "multi-user.target" ];
+        description = "Self-hosted AI coding assistant using large language models";
+        after = [ "network.target" ];
+        environment = serviceEnv;
+        serviceConfig = lib.mkMerge [
+          serviceUser
+          {
+            ExecStart =
+              "${lib.getExe tabbyPackage} serve --model ${cfg.model} --port ${toString cfg.port} --device ${tabbyPackage.featureDevice}";
+          }
+        ];
+      };
+
+      services.tabby-scheduler = lib.mkIf (cfg.indexInterval != "never") {
+        wantedBy = [ "multi-user.target" ];
+        description = "Tabby repository indexing service";
+        after = [ "network.target" ];
+        environment = serviceEnv;
+        preStart = "cp -f /etc/tabby/config.toml \${TABBY_ROOT}/config.toml";
+        serviceConfig = lib.mkMerge [
+          serviceUser
+          {
+            # Type = "oneshot";
+            ExecStart = "${lib.getExe tabbyPackage} scheduler --now";
+          }
+        ];
+      };
+      timers.tabby-scheduler = lib.mkIf (cfg.indexInterval != "never") {
+        description = "Update timer for tabby-scheduler";
+        partOf = [ "tabby-scheduler.service" ];
+        wantedBy = [ "timers.target" ];
+        timerConfig.OnUnitInactiveSec = cfg.indexInterval;
+      };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ ghthor ];
+}
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/mackerel-agent.nix b/nixos/modules/services/monitoring/mackerel-agent.nix
index 5915634ed26f..d1e84c0359dc 100644
--- a/nixos/modules/services/monitoring/mackerel-agent.nix
+++ b/nixos/modules/services/monitoring/mackerel-agent.nix
@@ -81,7 +81,7 @@ in {
       include = mkDefault "/etc/mackerel-agent/conf.d/*.conf";
     };
 
-    # upstream service file in https://git.io/JUt4Q
+    # upstream service file in https://github.com/mackerelio/mackerel-agent/blob/master/packaging/rpm/src/mackerel-agent.service
     systemd.services.mackerel-agent = {
       description = "mackerel.io agent";
       wants = [ "network-online.target" ];
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/prometheus/exporters/restic.nix b/nixos/modules/services/monitoring/prometheus/exporters/restic.nix
index 5b32c93a666d..977bd42e9812 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/restic.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/restic.nix
@@ -93,12 +93,14 @@ in
   };
 
   serviceOpts = {
+    script = ''
+      export RESTIC_PASSWORD_FILE=$CREDENTIALS_DIRECTORY/RESTIC_PASSWORD_FILE
+      ${pkgs.prometheus-restic-exporter}/bin/restic-exporter.py \
+        ${concatStringsSep " \\\n  " cfg.extraFlags}
+    '';
     serviceConfig = {
-      ExecStart = ''
-        ${pkgs.prometheus-restic-exporter}/bin/restic-exporter.py \
-          ${concatStringsSep " \\\n  " cfg.extraFlags}
-      '';
       EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile;
+      LoadCredential = [ "RESTIC_PASSWORD_FILE:${cfg.passwordFile}" ];
     };
     environment =
       let
@@ -108,8 +110,7 @@ in
         toRcloneVal = v: if lib.isBool v then lib.boolToString v else v;
       in
       {
-        RESTIC_REPO_URL = cfg.repository;
-        RESTIC_REPO_PASSWORD_FILE = cfg.passwordFile;
+        RESTIC_REPOSITORY = cfg.repository;
         LISTEN_ADDRESS = cfg.listenAddress;
         LISTEN_PORT = toString cfg.port;
         REFRESH_INTERVAL = toString cfg.refreshInterval;
diff --git a/nixos/modules/services/monitoring/scrutiny.nix b/nixos/modules/services/monitoring/scrutiny.nix
index 454668a9a128..aef924ef840c 100644
--- a/nixos/modules/services/monitoring/scrutiny.nix
+++ b/nixos/modules/services/monitoring/scrutiny.nix
@@ -1,5 +1,11 @@
 { config, lib, pkgs, ... }:
 let
+  inherit (lib) maintainers;
+  inherit (lib.meta) getExe;
+  inherit (lib.modules) mkIf;
+  inherit (lib.options) literalExpression mkEnableOption mkOption mkPackageOption;
+  inherit (lib.types) bool enum nullOr port str submodule;
+
   cfg = config.services.scrutiny;
   # Define the settings format used for this program
   settingsFormat = pkgs.formats.yaml { };
@@ -7,20 +13,16 @@ in
 {
   options = {
     services.scrutiny = {
-      enable = lib.mkEnableOption "Enables the scrutiny web application.";
+      enable = mkEnableOption "Scrutiny, a web application for drive monitoring";
 
-      package = lib.mkPackageOptionMD pkgs "scrutiny" { };
+      package = mkPackageOption pkgs "scrutiny" { };
 
-      openFirewall = lib.mkOption {
-        type = lib.types.bool;
-        default = false;
-        description = "Open the default ports in the firewall for Scrutiny.";
-      };
+      openFirewall = mkEnableOption "opening the default ports in the firewall for Scrutiny";
 
-      influxdb.enable = lib.mkOption {
-        type = lib.types.bool;
+      influxdb.enable = mkOption {
+        type = bool;
         default = true;
-        description = lib.mdDoc ''
+        description = ''
           Enables InfluxDB on the host system using the `services.influxdb2` NixOS module
           with default options.
 
@@ -29,127 +31,124 @@ in
         '';
       };
 
-      settings = lib.mkOption {
-        description = lib.mdDoc ''
+      settings = mkOption {
+        description = ''
           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 {
+        type = submodule {
           freeformType = settingsFormat.type;
 
-          options.web.listen.port = lib.mkOption {
-            type = lib.types.port;
+          options.web.listen.port = mkOption {
+            type = port;
             default = 8080;
-            description = lib.mdDoc "Port for web application to listen on.";
+            description = "Port for web application to listen on.";
           };
 
-          options.web.listen.host = lib.mkOption {
-            type = lib.types.str;
+          options.web.listen.host = mkOption {
+            type = str;
             default = "0.0.0.0";
-            description = lib.mdDoc "Interface address for web application to bind to.";
+            description = "Interface address for web application to bind to.";
           };
 
-          options.web.listen.basepath = lib.mkOption {
-            type = lib.types.str;
+          options.web.listen.basepath = mkOption {
+            type = str;
             default = "";
             example = "/scrutiny";
-            description = lib.mdDoc ''
+            description = ''
               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" ];
+          options.log.level = mkOption {
+            type = enum [ "INFO" "DEBUG" ];
             default = "INFO";
-            description = lib.mdDoc "Log level for Scrutiny.";
+            description = "Log level for Scrutiny.";
           };
 
-          options.web.influxdb.scheme = lib.mkOption {
-            type = lib.types.str;
+          options.web.influxdb.scheme = mkOption {
+            type = str;
             default = "http";
-            description = lib.mdDoc "URL scheme to use when connecting to InfluxDB.";
+            description = "URL scheme to use when connecting to InfluxDB.";
           };
 
-          options.web.influxdb.host = lib.mkOption {
-            type = lib.types.str;
+          options.web.influxdb.host = mkOption {
+            type = str;
             default = "0.0.0.0";
-            description = lib.mdDoc "IP or hostname of the InfluxDB instance.";
+            description = "IP or hostname of the InfluxDB instance.";
           };
 
-          options.web.influxdb.port = lib.mkOption {
-            type = lib.types.port;
+          options.web.influxdb.port = mkOption {
+            type = port;
             default = 8086;
-            description = lib.mdDoc "The port of the InfluxDB instance.";
+            description = "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.tls.insecure_skip_verify = mkEnableOption "skipping TLS verification when connecting to InfluxDB";
 
-          options.web.influxdb.token = lib.mkOption {
-            type = lib.types.nullOr lib.types.str;
+          options.web.influxdb.token = mkOption {
+            type = nullOr str;
             default = null;
-            description = lib.mdDoc "Authentication token for connecting to InfluxDB.";
+            description = "Authentication token for connecting to InfluxDB.";
           };
 
-          options.web.influxdb.org = lib.mkOption {
-            type = lib.types.nullOr lib.types.str;
+          options.web.influxdb.org = mkOption {
+            type = nullOr str;
             default = null;
-            description = lib.mdDoc "InfluxDB organisation under which to store data.";
+            description = "InfluxDB organisation under which to store data.";
           };
 
-          options.web.influxdb.bucket = lib.mkOption {
-            type = lib.types.nullOr lib.types.str;
+          options.web.influxdb.bucket = mkOption {
+            type = nullOr str;
             default = null;
-            description = lib.mdDoc "InfluxDB bucket in which to store data.";
+            description = "InfluxDB bucket in which to store data.";
           };
         };
       };
 
       collector = {
-        enable = lib.mkEnableOption "Enables the scrutiny metrics collector.";
+        enable = mkEnableOption "the Scrutiny metrics collector";
 
-        package = lib.mkPackageOptionMD pkgs "scrutiny-collector" { };
+        package = mkPackageOption pkgs "scrutiny-collector" { };
 
-        schedule = lib.mkOption {
-          type = lib.types.str;
+        schedule = mkOption {
+          type = str;
           default = "*:0/15";
-          description = lib.mdDoc ''
+          description = ''
             How often to run the collector in systemd calendar format.
           '';
         };
 
-        settings = lib.mkOption {
-          description = lib.mdDoc ''
+        settings = mkOption {
+          description = ''
             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 {
+          type = submodule {
             freeformType = settingsFormat.type;
 
-            options.host.id = lib.mkOption {
-              type = lib.types.nullOr lib.types.str;
+            options.host.id = mkOption {
+              type = nullOr str;
               default = null;
-              description = lib.mdDoc "Host ID for identifying/labelling groups of disks";
+              description = "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.api.endpoint = mkOption {
+              type = str;
+              default = "http://localhost:${toString cfg.settings.web.listen.port}";
+              defaultText = literalExpression ''"http://localhost:''${config.services.scrutiny.settings.web.listen.port}"'';
+              description = "Scrutiny app API endpoint for sending metrics to.";
             };
 
-            options.log.level = lib.mkOption {
-              type = lib.types.enum [ "INFO" "DEBUG" ];
+            options.log.level = mkOption {
+              type = enum [ "INFO" "DEBUG" ];
               default = "INFO";
-              description = lib.mdDoc "Log level for Scrutiny collector.";
+              description = "Log level for Scrutiny collector.";
             };
           };
         };
@@ -157,14 +156,14 @@ in
     };
   };
 
-  config = lib.mkIf (cfg.enable || cfg.collector.enable) {
+  config = mkIf (cfg.enable || cfg.collector.enable) {
     services.influxdb2.enable = cfg.influxdb.enable;
 
-    networking.firewall = lib.mkIf cfg.openFirewall {
+    networking.firewall = mkIf cfg.openFirewall {
       allowedTCPPorts = [ cfg.settings.web.listen.port ];
     };
 
-    services.smartd = lib.mkIf cfg.collector.enable {
+    services.smartd = mkIf cfg.collector.enable {
       enable = true;
       extraOptions = [
         "-A /var/log/smartd/"
@@ -174,7 +173,7 @@ in
 
     systemd = {
       services = {
-        scrutiny = lib.mkIf cfg.enable {
+        scrutiny = 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" ];
@@ -185,14 +184,14 @@ in
           };
           serviceConfig = {
             DynamicUser = true;
-            ExecStart = "${lib.getExe cfg.package} start --config ${settingsFormat.generate "scrutiny.yaml" cfg.settings}";
+            ExecStart = "${getExe cfg.package} start --config ${settingsFormat.generate "scrutiny.yaml" cfg.settings}";
             Restart = "always";
             StateDirectory = "scrutiny";
             StateDirectoryMode = "0750";
           };
         };
 
-        scrutiny-collector = lib.mkIf cfg.collector.enable {
+        scrutiny-collector = mkIf cfg.collector.enable {
           description = "Scrutiny Collector Service";
           environment = {
             COLLECTOR_VERSION = "1";
@@ -200,12 +199,12 @@ in
           };
           serviceConfig = {
             Type = "oneshot";
-            ExecStart = "${lib.getExe cfg.collector.package} run --config ${settingsFormat.generate "scrutiny-collector.yaml" cfg.collector.settings}";
+            ExecStart = "${getExe cfg.collector.package} run --config ${settingsFormat.generate "scrutiny-collector.yaml" cfg.collector.settings}";
           };
         };
       };
 
-      timers = lib.mkIf cfg.collector.enable {
+      timers = mkIf cfg.collector.enable {
         scrutiny-collector = {
           timerConfig = {
             OnCalendar = cfg.collector.schedule;
@@ -217,5 +216,5 @@ in
     };
   };
 
-  meta.maintainers = [ lib.maintainers.jnsgruk ];
+  meta.maintainers = [ maintainers.jnsgruk ];
 }
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/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/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/security/vaultwarden/default.nix b/nixos/modules/services/security/vaultwarden/default.nix
index 470db735bf64..60d8015d0cee 100644
--- a/nixos/modules/services/security/vaultwarden/default.nix
+++ b/nixos/modules/services/security/vaultwarden/default.nix
@@ -180,7 +180,6 @@ in {
     users.groups.vaultwarden = { };
 
     systemd.services.vaultwarden = {
-      aliases = [ "bitwarden_rs.service" ];
       after = [ "network.target" ];
       path = with pkgs; [ openssl ];
       serviceConfig = {
@@ -202,7 +201,6 @@ in {
     };
 
     systemd.services.backup-vaultwarden = mkIf (cfg.backupDir != null) {
-      aliases = [ "backup-bitwarden_rs.service" ];
       description = "Backup vaultwarden";
       environment = {
         DATA_FOLDER = "/var/lib/bitwarden_rs";
@@ -222,7 +220,6 @@ in {
     };
 
     systemd.timers.backup-vaultwarden = mkIf (cfg.backupDir != null) {
-      aliases = [ "backup-bitwarden_rs.timer" ];
       description = "Backup vaultwarden on time";
       timerConfig = {
         OnCalendar = mkDefault "23:00";
@@ -240,6 +237,9 @@ in {
     };
   };
 
-  # uses attributes of the linked package
-  meta.buildDocsInSandbox = false;
+  meta = {
+    # uses attributes of the linked package
+    buildDocsInSandbox = false;
+    maintainers = with lib.maintainers; [ dotlambda SuperSandro2000 ];
+  };
 }
diff --git a/nixos/modules/services/security/yubikey-agent.nix b/nixos/modules/services/security/yubikey-agent.nix
index a9f15e4405f2..bf8432a00238 100644
--- a/nixos/modules/services/security/yubikey-agent.nix
+++ b/nixos/modules/services/security/yubikey-agent.nix
@@ -6,9 +6,6 @@ with lib;
 
 let
   cfg = config.services.yubikey-agent;
-
-  # reuse the pinentryFlavor option from the gnupg module
-  pinentryFlavor = config.programs.gnupg.agent.pinentryFlavor;
 in
 {
   ###### interface
@@ -41,13 +38,8 @@ in
     # This overrides the systemd user unit shipped with the
     # yubikey-agent package
     systemd.user.services.yubikey-agent = mkIf (pinentryFlavor != null) {
-      path = [ pkgs.pinentry.${pinentryFlavor} ];
-      wantedBy = [
-        (if pinentryFlavor == "tty" || pinentryFlavor == "curses" then
-          "default.target"
-        else
-          "graphical-session.target")
-      ];
+      path = [ config.programs.gnupg.agent.pinentryPackage ];
+      wantedBy = [ "default.target" ];
     };
 
     # Yubikey-agent expects pcsd to be running in order to function.
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/miniflux.nix b/nixos/modules/services/web-apps/miniflux.nix
index 1a5b7d0c24e9..16b6fb0d655d 100644
--- a/nixos/modules/services/web-apps/miniflux.nix
+++ b/nixos/modules/services/web-apps/miniflux.nix
@@ -16,10 +16,20 @@ in
 {
   options = {
     services.miniflux = {
-      enable = mkEnableOption (lib.mdDoc "miniflux and creates a local postgres database for it");
+      enable = mkEnableOption (lib.mdDoc "miniflux");
 
       package = mkPackageOption pkgs "miniflux" { };
 
+      createDatabaseLocally = lib.mkOption {
+        type = lib.types.bool;
+        default = true;
+        description = ''
+          Whether a PostgreSQL database should be automatically created and
+          configured on the local host. If set to `false`, you need provision a
+          database yourself and make sure to create the hstore extension in it.
+        '';
+      };
+
       config = mkOption {
         type = with types; attrsOf (oneOf [ str int ]);
         example = literalExpression ''
@@ -38,7 +48,7 @@ in
         '';
       };
 
-      adminCredentialsFile = mkOption  {
+      adminCredentialsFile = mkOption {
         type = types.path;
         description = lib.mdDoc ''
           File containing the ADMIN_USERNAME and
@@ -51,14 +61,14 @@ in
   };
 
   config = mkIf cfg.enable {
-    services.miniflux.config =  {
+    services.miniflux.config = {
       LISTEN_ADDR = mkDefault defaultAddress;
-      DATABASE_URL = "user=miniflux host=/run/postgresql dbname=miniflux";
+      DATABASE_URL = lib.mkIf cfg.createDatabaseLocally "user=miniflux host=/run/postgresql dbname=miniflux";
       RUN_MIGRATIONS = 1;
       CREATE_ADMIN = 1;
     };
 
-    services.postgresql = {
+    services.postgresql = lib.mkIf cfg.createDatabaseLocally {
       enable = true;
       ensureUsers = [ {
         name = "miniflux";
@@ -67,7 +77,7 @@ in
       ensureDatabases = [ "miniflux" ];
     };
 
-    systemd.services.miniflux-dbsetup = {
+    systemd.services.miniflux-dbsetup = lib.mkIf cfg.createDatabaseLocally {
       description = "Miniflux database setup";
       requires = [ "postgresql.service" ];
       after = [ "network.target" "postgresql.service" ];
@@ -81,8 +91,9 @@ in
     systemd.services.miniflux = {
       description = "Miniflux service";
       wantedBy = [ "multi-user.target" ];
-      requires = [ "miniflux-dbsetup.service" ];
-      after = [ "network.target" "postgresql.service" "miniflux-dbsetup.service" ];
+      requires = lib.optional cfg.createDatabaseLocally "miniflux-dbsetup.service";
+      after = [ "network.target" ]
+        ++ lib.optionals cfg.createDatabaseLocally [ "postgresql.service" "miniflux-dbsetup.service" ];
 
       serviceConfig = {
         ExecStart = "${cfg.package}/bin/miniflux";
@@ -129,6 +140,7 @@ in
         include "${pkgs.apparmorRulesFromClosure { name = "miniflux"; } cfg.package}"
         r ${cfg.package}/bin/miniflux,
         r @{sys}/kernel/mm/transparent_hugepage/hpage_pmd_size,
+        rw /run/miniflux/**,
       }
     '';
   };
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/x11/desktop-managers/deepin.nix b/nixos/modules/services/x11/desktop-managers/deepin.nix
index 0824d6e30a8a..e6f221201013 100644
--- a/nixos/modules/services/x11/desktop-managers/deepin.nix
+++ b/nixos/modules/services/x11/desktop-managers/deepin.nix
@@ -66,6 +66,7 @@ in
       services.upower.enable = mkDefault config.powerManagement.enable;
       networking.networkmanager.enable = mkDefault true;
       programs.dconf.enable = mkDefault true;
+      programs.gnupg.agent.pinentryPackage = pkgs.pinentry-qt;
 
       fonts.packages = with pkgs; [ noto-fonts ];
       xdg.mime.enable = true;
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/lxqt.nix b/nixos/modules/services/x11/desktop-managers/lxqt.nix
index 50ad72dc7388..d3bdc4326a90 100644
--- a/nixos/modules/services/x11/desktop-managers/lxqt.nix
+++ b/nixos/modules/services/x11/desktop-managers/lxqt.nix
@@ -62,6 +62,8 @@ in
     # Link some extra directories in /run/current-system/software/share
     environment.pathsToLink = [ "/share" ];
 
+    programs.gnupg.agent.pinentryPackage = pkgs.pinentry-qt;
+
     # virtual file systems support for PCManFM-QT
     services.gvfs.enable = true;
 
diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix
index 0eb492ce4684..c884b4487e24 100644
--- a/nixos/modules/services/x11/desktop-managers/plasma5.nix
+++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix
@@ -336,6 +336,7 @@ in
         serif = [ "Noto Serif" ];
       };
 
+      programs.gnupg.agent.pinentryPackage = pkgs.pinentry-qt;
       programs.ssh.askPassword = mkDefault "${pkgs.plasma5Packages.ksshaskpass.out}/bin/ksshaskpass";
 
       # Enable helpful DBus services.
@@ -362,7 +363,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..a471a48c9002
--- /dev/null
+++ b/nixos/modules/services/x11/desktop-managers/plasma6.nix
@@ -0,0 +1,292 @@
+{
+  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.optional config.services.samba.enable kdenetwork-filesharing
+      ++ 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.gnupg.agent.pinentryPackage = pkgs.pinentry-qt;
+    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/desktop-managers/xfce.nix b/nixos/modules/services/x11/desktop-managers/xfce.nix
index e28486bcc12d..6bc964f4c6ed 100644
--- a/nixos/modules/services/x11/desktop-managers/xfce.nix
+++ b/nixos/modules/services/x11/desktop-managers/xfce.nix
@@ -131,6 +131,7 @@ in
         xfdesktop
       ] ++ optional cfg.enableScreensaver xfce4-screensaver) excludePackages;
 
+    programs.gnupg.agent.pinentryPackage = pkgs.pinentry-gtk2;
     programs.xfconf.enable = true;
     programs.thunar.enable = true;
 
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/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix
index 38fb1074fcdf..3d7474e18263 100644
--- a/nixos/modules/services/x11/xserver.nix
+++ b/nixos/modules/services/x11/xserver.nix
@@ -749,6 +749,8 @@ in
     boot.kernel.sysctl."fs.inotify.max_user_instances" = mkDefault 524288;
     boot.kernel.sysctl."fs.inotify.max_user_watches" = mkDefault 524288;
 
+    programs.gnupg.agent.pinentryPackage = lib.mkDefault pkgs.pinentry-gnome3;
+
     systemd.defaultUnit = mkIf cfg.autorun "graphical.target";
 
     systemd.services.display-manager =
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..b6b0f64b94c8 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)
@@ -797,6 +797,7 @@ let
           "UseHostname"
           "Hostname"
           "UseDomains"
+          "UseGateway"
           "UseRoutes"
           "UseTimezone"
           "ClientIdentifier"
@@ -829,6 +830,7 @@ let
         (assertValueOneOf "SendHostname" boolValues)
         (assertValueOneOf "UseHostname" boolValues)
         (assertValueOneOf "UseDomains" (boolValues ++ ["route"]))
+        (assertValueOneOf "UseGateway" boolValues)
         (assertValueOneOf "UseRoutes" boolValues)
         (assertValueOneOf "UseTimezone" boolValues)
         (assertValueOneOf "ClientIdentifier" ["mac" "duid" "duid-only"])
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..a8885aee78f2 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"
+      ] ++ (optional cfg.package.withBootloader "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/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/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/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/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 3bbe0ba45851..a561c5682ae5 100644
--- a/nixos/modules/virtualisation/incus.nix
+++ b/nixos/modules/virtualisation/incus.nix
@@ -107,6 +107,13 @@ in
   };
 
   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;
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/oci-containers.nix b/nixos/modules/virtualisation/oci-containers.nix
index b6a7b1154c4a..5bffb3f04716 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}
@@ -309,7 +312,7 @@ let
 
     preStop = if cfg.backend == "podman"
       then "podman stop --ignore --cidfile=/run/podman-${escapedName}.ctr-id"
-      else "${cfg.backend} stop ${name}";
+      else "${cfg.backend} stop ${name} || true";
 
     postStop = if cfg.backend == "podman"
       then "podman rm -f --ignore --cidfile=/run/podman-${escapedName}.ctr-id"
diff --git a/nixos/modules/virtualisation/podman/default.nix b/nixos/modules/virtualisation/podman/default.nix
index 7411ebc2a311..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
@@ -216,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/virtualbox-host.nix b/nixos/modules/virtualisation/virtualbox-host.nix
index 50a8f8189590..0ecf7f490cf6 100644
--- a/nixos/modules/virtualisation/virtualbox-host.nix
+++ b/nixos/modules/virtualisation/virtualbox-host.nix
@@ -6,7 +6,7 @@ let
   cfg = config.virtualisation.virtualbox.host;
 
   virtualbox = cfg.package.override {
-    inherit (cfg) enableHardening headless enableWebService;
+    inherit (cfg) enableHardening headless enableWebService enableKvm;
     extensionPack = if cfg.enableExtensionPack then pkgs.virtualboxExtpack else null;
   };
 
@@ -81,13 +81,24 @@ in
         Build VirtualBox web service tool (vboxwebsrv) to allow managing VMs via other webpage frontend tools. Useful for headless servers.
       '';
     };
+
+    enableKvm = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Enable KVM support for VirtualBox. This increases compatibility with Linux kernel versions, because the VirtualBox kernel modules
+        are not required.
+
+        This option is incompatible with `enableHardening` and `addNetworkInterface`.
+
+        Note: This is experimental. Please check https://github.com/cyberus-technology/virtualbox-kvm/issues.
+      '';
+    };
   };
 
   config = mkIf cfg.enable (mkMerge [{
     warnings = mkIf (pkgs.config.virtualbox.enableExtensionPack or false)
       ["'nixpkgs.virtualbox.enableExtensionPack' has no effect, please use 'virtualisation.virtualbox.host.enableExtensionPack'"];
-    boot.kernelModules = [ "vboxdrv" "vboxnetadp" "vboxnetflt" ];
-    boot.extraModulePackages = [ kernelModules ];
     environment.systemPackages = [ virtualbox ];
 
     security.wrappers = let
@@ -114,17 +125,43 @@ in
 
     services.udev.extraRules =
       ''
-        KERNEL=="vboxdrv",    OWNER="root", GROUP="vboxusers", MODE="0660", TAG+="systemd"
-        KERNEL=="vboxdrvu",   OWNER="root", GROUP="root",      MODE="0666", TAG+="systemd"
-        KERNEL=="vboxnetctl", OWNER="root", GROUP="vboxusers", MODE="0660", TAG+="systemd"
         SUBSYSTEM=="usb_device", ACTION=="add", RUN+="${virtualbox}/libexec/virtualbox/VBoxCreateUSBNode.sh $major $minor $attr{bDeviceClass}"
         SUBSYSTEM=="usb", ACTION=="add", ENV{DEVTYPE}=="usb_device", RUN+="${virtualbox}/libexec/virtualbox/VBoxCreateUSBNode.sh $major $minor $attr{bDeviceClass}"
         SUBSYSTEM=="usb_device", ACTION=="remove", RUN+="${virtualbox}/libexec/virtualbox/VBoxCreateUSBNode.sh --remove $major $minor"
         SUBSYSTEM=="usb", ACTION=="remove", ENV{DEVTYPE}=="usb_device", RUN+="${virtualbox}/libexec/virtualbox/VBoxCreateUSBNode.sh --remove $major $minor"
       '';
+  } (mkIf cfg.enableKvm {
+    assertions = [
+      {
+        assertion = !cfg.addNetworkInterface;
+        message = "VirtualBox KVM only supports standard NAT networking for VMs. Please turn off virtualisation.virtualbox.host.addNetworkInferface.";
+      }
+
+      {
+        assertion = !cfg.enableHardening;
+        message = "VirtualBox KVM is not compatible with hardening: Please turn off virtualisation.virtualbox.host.enableHardening.";
+      }
+    ];
+
+    warnings = [
+      ''
+        KVM support in VirtualBox is experimental. Not all security features are available yet.
+        See: https://github.com/cyberus-technology/virtualbox-kvm/issues/12
+      ''
+    ];
+  }) (mkIf (!cfg.enableKvm) {
+    boot.kernelModules = [ "vboxdrv" "vboxnetadp" "vboxnetflt" ];
+    boot.extraModulePackages = [ kernelModules ];
+
+    services.udev.extraRules =
+      ''
+        KERNEL=="vboxdrv",    OWNER="root", GROUP="vboxusers", MODE="0660", TAG+="systemd"
+        KERNEL=="vboxdrvu",   OWNER="root", GROUP="root",      MODE="0666", TAG+="systemd"
+        KERNEL=="vboxnetctl", OWNER="root", GROUP="vboxusers", MODE="0660", TAG+="systemd"
+     '';
 
     # Since we lack the right setuid/setcap binaries, set up a host-only network by default.
-  } (mkIf cfg.addNetworkInterface {
+  }) (mkIf cfg.addNetworkInterface {
     systemd.services.vboxnet0 =
       { description = "VirtualBox vboxnet0 Interface";
         requires = [ "dev-vboxnetctl.device" ];
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/akkoma.nix b/nixos/tests/akkoma.nix
index 287e2d485999..2907017ee3d5 100644
--- a/nixos/tests/akkoma.nix
+++ b/nixos/tests/akkoma.nix
@@ -31,16 +31,12 @@ let
 
     export REQUESTS_CA_BUNDLE="/etc/ssl/certs/ca-certificates.crt"
 
-    echo '${userPassword}' | ${pkgs.toot}/bin/toot login_cli -i "akkoma.nixos.test" -e "jamy@nixos.test"
-    echo "y" | ${pkgs.toot}/bin/toot post "hello world Jamy here"
-
-    # Retrieving timeline with toot currently broken due to incompatible timestamp format
-    # cf. <https://akkoma.dev/AkkomaGang/akkoma/issues/637> and <https://github.com/ihabunek/toot/issues/399>
-    #echo "y" | ${pkgs.toot}/bin/toot timeline | grep -F -q "hello world Jamy here"
+    ${pkgs.toot}/bin/toot login_cli -i "akkoma.nixos.test" -e "jamy@nixos.test" -p '${userPassword}'
+    ${pkgs.toot}/bin/toot post "hello world Jamy here"
+    ${pkgs.toot}/bin/toot timeline -1 | grep -F -q "hello world Jamy here"
 
     # Test file upload
-    echo "y" | ${pkgs.toot}/bin/toot upload <(dd if=/dev/zero bs=1024 count=1024 status=none) \
-      | grep -F -q "https://akkoma.nixos.test/media"
+    ${pkgs.toot}/bin/toot upload <(dd if=/dev/zero bs=1024 count=1024 status=none)
   '';
 
   checkFe = pkgs.writers.writeBashBin "checkFe" ''
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 1144a5611dcf..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,6 +540,7 @@ 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 {};
@@ -692,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 {};
@@ -911,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/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 f252eb9ff61e..7d91076600f9 100644
--- a/nixos/tests/docker-tools.nix
+++ b/nixos/tests/docker-tools.nix
@@ -58,6 +58,20 @@ let
       '';
       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; {
@@ -181,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}",
         )
 
@@ -604,5 +618,11 @@ in {
             "${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/homepage-dashboard.nix b/nixos/tests/homepage-dashboard.nix
index 56e077f5ff6d..dd36473e8ac0 100644
--- a/nixos/tests/homepage-dashboard.nix
+++ b/nixos/tests/homepage-dashboard.nix
@@ -2,13 +2,35 @@ import ./make-test-python.nix ({ lib, ... }: {
   name = "homepage-dashboard";
   meta.maintainers = with lib.maintainers; [ jnsgruk ];
 
-  nodes.machine = { pkgs, ... }: {
+  nodes.unmanaged_conf = { pkgs, ... }: {
     services.homepage-dashboard.enable = true;
   };
 
+  nodes.managed_conf = { pkgs, ... }: {
+    services.homepage-dashboard = {
+      enable = true;
+      settings.title = "custom";
+    };
+  };
+
   testScript = ''
-    machine.wait_for_unit("homepage-dashboard.service")
-    machine.wait_for_open_port(8082)
-    machine.succeed("curl --fail http://localhost:8082/")
+    # Ensure the services are started on unmanaged machine
+    unmanaged_conf.wait_for_unit("homepage-dashboard.service")
+    unmanaged_conf.wait_for_open_port(8082)
+    unmanaged_conf.succeed("curl --fail http://localhost:8082/")
+
+    # Ensure that /etc/homepage-dashboard doesn't exist, and boilerplate
+    # configs are copied into place.
+    unmanaged_conf.fail("test -d /etc/homepage-dashboard")
+    unmanaged_conf.succeed("test -f /var/lib/private/homepage-dashboard/settings.yaml")
+
+    # Ensure the services are started on managed machine
+    managed_conf.wait_for_unit("homepage-dashboard.service")
+    managed_conf.wait_for_open_port(8082)
+    managed_conf.succeed("curl --fail http://localhost:8082/")
+
+    # Ensure /etc/homepage-dashboard is created and unmanaged conf location isn't.
+    managed_conf.succeed("test -d /etc/homepage-dashboard")
+    managed_conf.fail("test -f /var/lib/private/homepage-dashboard/settings.yaml")
   '';
 })
diff --git a/nixos/tests/incus/container.nix b/nixos/tests/incus/container.nix
index 0f42d16f133d..9260f70da98c 100644
--- a/nixos/tests/incus/container.nix
+++ b/nixos/tests/incus/container.nix
@@ -5,6 +5,8 @@ let
     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;
   };
 
@@ -27,6 +29,7 @@ in
 
       incus.enable = true;
     };
+    networking.nftables.enable = true;
   };
 
   testScript = ''
@@ -40,6 +43,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 +92,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 +104,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 +113,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/lxd-to-incus.nix b/nixos/tests/incus/lxd-to-incus.nix
index c0fc98c224df..262f63c0f26f 100644
--- a/nixos/tests/incus/lxd-to-incus.nix
+++ b/nixos/tests/incus/lxd-to-incus.nix
@@ -67,6 +67,7 @@ import ../make-test-python.nix (
 
           incus.enable = true;
         };
+        networking.nftables.enable = true;
       };
 
     testScript = ''
diff --git a/nixos/tests/incus/preseed.nix b/nixos/tests/incus/preseed.nix
index a488d71f3c92..f2d928115f3e 100644
--- a/nixos/tests/incus/preseed.nix
+++ b/nixos/tests/incus/preseed.nix
@@ -48,6 +48,7 @@ import ../make-test-python.nix ({ pkgs, lib, ... } :
         ];
       };
     };
+    networking.nftables.enable = true;
   };
 
   testScript = ''
diff --git a/nixos/tests/incus/socket-activated.nix b/nixos/tests/incus/socket-activated.nix
index fca536b7054f..59caf1090fbd 100644
--- a/nixos/tests/incus/socket-activated.nix
+++ b/nixos/tests/incus/socket-activated.nix
@@ -12,6 +12,7 @@ import ../make-test-python.nix ({ pkgs, lib, ... } :
       incus.enable = true;
       incus.socketActivation = true;
     };
+    networking.nftables.enable = true;
   };
 
   testScript = ''
diff --git a/nixos/tests/incus/ui.nix b/nixos/tests/incus/ui.nix
index 24ce1217d8df..837eb14844ce 100644
--- a/nixos/tests/incus/ui.nix
+++ b/nixos/tests/incus/ui.nix
@@ -10,6 +10,7 @@ import ../make-test-python.nix ({ pkgs, lib, ... }: {
       incus.enable = true;
       incus.ui.enable = true;
     };
+    networking.nftables.enable = true;
 
     environment.systemPackages =
       let
diff --git a/nixos/tests/incus/virtual-machine.nix b/nixos/tests/incus/virtual-machine.nix
index c76e4f448f2f..ab378c7b9490 100644
--- a/nixos/tests/incus/virtual-machine.nix
+++ b/nixos/tests/incus/virtual-machine.nix
@@ -32,6 +32,7 @@ in
 
       incus.enable = true;
     };
+    networking.nftables.enable = true;
   };
 
   testScript = ''
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/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/knot.nix b/nixos/tests/knot.nix
index c5af8bf1edcc..eec94a22f2fa 100644
--- a/nixos/tests/knot.nix
+++ b/nixos/tests/knot.nix
@@ -66,6 +66,10 @@ in {
             "0.0.0.0@53"
             "::@53"
            ];
+          listen-quic = [
+            "0.0.0.0@853"
+            "::@853"
+           ];
           automatic-acl = true;
         };
 
@@ -129,8 +133,13 @@ in {
           key = "xfr_key";
         };
 
+        remote.primary-quic = {
+          address = "192.168.0.1@853";
+          key = "xfr_key";
+          quic = true;
+        };
+
         template.default = {
-          master = "primary";
           # zonefileless setup
           # https://www.knot-dns.cz/docs/2.8/html/operation.html#example-2
           zonefile-sync = "-1";
@@ -139,8 +148,14 @@ in {
         };
 
         zone = {
-          "example.com".file = "example.com.zone";
-          "sub.example.com".file = "sub.example.com.zone";
+          "example.com" = {
+            master = "primary";
+            file = "example.com.zone";
+          };
+          "sub.example.com" = {
+            master = "primary-quic";
+            file = "sub.example.com.zone";
+          };
         };
 
         log.syslog.any = "debug";
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/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/miniflux.nix b/nixos/tests/miniflux.nix
index a3af53db0e7a..6d38224448ed 100644
--- a/nixos/tests/miniflux.nix
+++ b/nixos/tests/miniflux.nix
@@ -15,6 +15,10 @@ let
             ADMIN_USERNAME=${username}
             ADMIN_PASSWORD=${password}
           '';
+  postgresPassword = "correcthorsebatterystaple";
+  postgresPasswordFile = pkgs.writeText "pgpass" ''
+    *:*:*:*:${postgresPassword}
+  '';
 
 in
 {
@@ -56,32 +60,62 @@ in
           adminCredentialsFile = customAdminCredentialsFile;
         };
       };
+
+    postgresTcp = { config, pkgs, lib, ... }: {
+      services.postgresql = {
+        enable = true;
+        initialScript = pkgs.writeText "init-postgres" ''
+          CREATE USER miniflux WITH PASSWORD '${postgresPassword}';
+          CREATE DATABASE miniflux WITH OWNER miniflux;
+        '';
+        enableTCPIP = true;
+        authentication = ''
+          host sameuser miniflux samenet scram-sha-256
+        '';
+      };
+      systemd.services.postgresql.postStart = lib.mkAfter ''
+        $PSQL -tAd miniflux -c 'CREATE EXTENSION hstore;'
+      '';
+      networking.firewall.allowedTCPPorts = [ config.services.postgresql.port ];
+    };
+    externalDb = { ... }: {
+      security.apparmor.enable = true;
+      services.miniflux = {
+        enable = true;
+        createDatabaseLocally = false;
+        inherit adminCredentialsFile;
+        config = {
+          DATABASE_URL = "user=miniflux host=postgresTcp dbname=miniflux sslmode=disable";
+          PGPASSFILE = "/run/miniflux/pgpass";
+        };
+      };
+      systemd.services.miniflux.preStart = ''
+        cp ${postgresPasswordFile} /run/miniflux/pgpass
+        chmod 600 /run/miniflux/pgpass
+      '';
+    };
   };
   testScript = ''
-    start_all()
+    def runTest(machine, port, user):
+      machine.wait_for_unit("miniflux.service")
+      machine.wait_for_open_port(port)
+      machine.succeed(f"curl --fail 'http://localhost:{port}/healthcheck' | grep OK")
+      machine.succeed(
+          f"curl 'http://localhost:{port}/v1/me' -u '{user}' -H Content-Type:application/json | grep '\"is_admin\":true'"
+      )
+      machine.fail('journalctl -b --no-pager --grep "^audit: .*apparmor=\\"DENIED\\""')
 
-    default.wait_for_unit("miniflux.service")
-    default.wait_for_open_port(${toString defaultPort})
-    default.succeed("curl --fail 'http://localhost:${toString defaultPort}/healthcheck' | grep OK")
-    default.succeed(
-        "curl 'http://localhost:${toString defaultPort}/v1/me' -u '${defaultUsername}:${defaultPassword}' -H Content-Type:application/json | grep '\"is_admin\":true'"
-    )
-    default.fail('journalctl -b --no-pager --grep "^audit: .*apparmor=\\"DENIED\\""')
+    default.start()
+    withoutSudo.start()
+    customized.start()
+    postgresTcp.start()
 
-    withoutSudo.wait_for_unit("miniflux.service")
-    withoutSudo.wait_for_open_port(${toString defaultPort})
-    withoutSudo.succeed("curl --fail 'http://localhost:${toString defaultPort}/healthcheck' | grep OK")
-    withoutSudo.succeed(
-        "curl 'http://localhost:${toString defaultPort}/v1/me' -u '${defaultUsername}:${defaultPassword}' -H Content-Type:application/json | grep '\"is_admin\":true'"
-    )
-    withoutSudo.fail('journalctl -b --no-pager --grep "^audit: .*apparmor=\\"DENIED\\""')
+    runTest(default, ${toString defaultPort}, "${defaultUsername}:${defaultPassword}")
+    runTest(withoutSudo, ${toString defaultPort}, "${defaultUsername}:${defaultPassword}")
+    runTest(customized, ${toString port}, "${username}:${password}")
 
-    customized.wait_for_unit("miniflux.service")
-    customized.wait_for_open_port(${toString port})
-    customized.succeed("curl --fail 'http://localhost:${toString port}/healthcheck' | grep OK")
-    customized.succeed(
-        "curl 'http://localhost:${toString port}/v1/me' -u '${username}:${password}' -H Content-Type:application/json | grep '\"is_admin\":true'"
-    )
-    customized.fail('journalctl -b --no-pager --grep "^audit: .*apparmor=\\"DENIED\\""')
+    postgresTcp.wait_for_unit("postgresql.service")
+    externalDb.start()
+    runTest(externalDb, ${toString defaultPort}, "${defaultUsername}:${defaultPassword}")
   '';
 })
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/nixops/default.nix b/nixos/tests/nixops/default.nix
index 6501d13a2ed3..8477e5059fca 100644
--- a/nixos/tests/nixops/default.nix
+++ b/nixos/tests/nixops/default.nix
@@ -9,7 +9,7 @@ let
     #  - Alternatively, blocked on a NixOps 2 release
     #    https://github.com/NixOS/nixops/issues/1242
     # stable = testsLegacyNetwork { nixopsPkg = pkgs.nixops; };
-    unstable = testsForPackage { nixopsPkg = pkgs.nixops_unstable; };
+    unstable = testsForPackage { nixopsPkg = pkgs.nixops_unstable_minimal; };
 
     # inherit testsForPackage;
   };
@@ -32,6 +32,7 @@ let
           pkgs.hello
           pkgs.figlet
         ];
+        virtualisation.memorySize = 2048;
 
         # TODO: make this efficient, https://github.com/NixOS/nixpkgs/issues/180529
         system.includeBuildDependencies = true;
diff --git a/nixos/tests/pass-secret-service.nix b/nixos/tests/pass-secret-service.nix
index e0dddf0ad29e..cdbdaa52dbc0 100644
--- a/nixos/tests/pass-secret-service.nix
+++ b/nixos/tests/pass-secret-service.nix
@@ -26,7 +26,6 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
 
       programs.gnupg = {
         agent.enable = true;
-        agent.pinentryFlavor = "tty";
         dirmngr.enable = true;
       };
     };
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/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/sanoid.nix b/nixos/tests/sanoid.nix
index 411ebcead9f6..1575634e6284 100644
--- a/nixos/tests/sanoid.nix
+++ b/nixos/tests/sanoid.nix
@@ -115,8 +115,11 @@ in {
     source.systemctl("start --wait syncoid-pool-sanoid.service")
     target.succeed("cat /mnt/pool/sanoid/test.txt")
     source.systemctl("start --wait syncoid-pool-syncoid.service")
+    source.systemctl("start --wait syncoid-pool-syncoid.service")
     target.succeed("cat /mnt/pool/syncoid/test.txt")
 
+    assert(len(source.succeed("zfs list -H -t snapshot pool/syncoid").splitlines()) == 1), "Syncoid should only retain one sync snapshot"
+
     source.systemctl("start --wait syncoid-pool.service")
     target.succeed("[[ -d /mnt/pool/full-pool/syncoid ]]")
 
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/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/virtualbox.nix b/nixos/tests/virtualbox.nix
index e522d0679e15..3c2a391233db 100644
--- a/nixos/tests/virtualbox.nix
+++ b/nixos/tests/virtualbox.nix
@@ -3,6 +3,7 @@
   pkgs ? import ../.. { inherit system config; },
   debug ? false,
   enableUnfree ? false,
+  enableKvm ? false,
   use64bitGuest ? true
 }:
 
@@ -340,7 +341,7 @@ let
     testExtensionPack.vmFlags = enableExtensionPackVMFlags;
   };
 
-  mkVBoxTest = useExtensionPack: vms: name: testScript: makeTest {
+  mkVBoxTest = vboxHostConfig: vms: name: testScript: makeTest {
     name = "virtualbox-${name}";
 
     nodes.machine = { lib, config, ... }: {
@@ -349,14 +350,23 @@ let
         vmConfigs = mapAttrsToList mkVMConf vms;
       in [ ./common/user-account.nix ./common/x11.nix ] ++ vmConfigs;
       virtualisation.memorySize = 2048;
-      virtualisation.qemu.options = ["-cpu" "kvm64,svm=on,vmx=on"];
-      virtualisation.virtualbox.host.enable = true;
+
+      virtualisation.qemu.options = let
+        # IvyBridge is reasonably ancient to be compatible with recent
+        # Intel/AMD hosts and sufficient for the KVM flavor.
+        guestCpu = if config.virtualisation.virtualbox.host.enableKvm then "IvyBridge" else "kvm64";
+      in ["-cpu" "${guestCpu},svm=on,vmx=on"];
+
       test-support.displayManager.auto.user = "alice";
       users.users.alice.extraGroups = let
         inherit (config.virtualisation.virtualbox.host) enableHardening;
-      in lib.mkIf enableHardening (lib.singleton "vboxusers");
-      virtualisation.virtualbox.host.enableExtensionPack = useExtensionPack;
-      nixpkgs.config.allowUnfree = useExtensionPack;
+      in lib.mkIf enableHardening [ "vboxusers" ];
+
+      virtualisation.virtualbox.host = {
+        enable = true;
+      } // vboxHostConfig;
+
+      nixpkgs.config.allowUnfree = config.virtualisation.virtualbox.host.enableExtensionPack;
     };
 
     testScript = ''
@@ -390,7 +400,7 @@ let
     };
   };
 
-  unfreeTests = mapAttrs (mkVBoxTest true vboxVMsWithExtpack) {
+  unfreeTests = mapAttrs (mkVBoxTest { enableExtensionPack = true; } vboxVMsWithExtpack) {
     enable-extension-pack = ''
       create_vm_testExtensionPack()
       vbm("startvm testExtensionPack")
@@ -409,7 +419,24 @@ let
     '';
   };
 
-in mapAttrs (mkVBoxTest false vboxVMs) {
+  kvmTests = mapAttrs (mkVBoxTest {
+    enableKvm = true;
+
+    # Once the KVM version supports these, we can enable them.
+    addNetworkInterface = false;
+    enableHardening = false;
+  } vboxVMs) {
+    kvm-headless = ''
+      create_vm_headless()
+      machine.succeed(ru("VBoxHeadless --startvm headless >&2 & disown %1"))
+      wait_for_startup_headless()
+      wait_for_vm_boot_headless()
+      shutdown_vm_headless()
+      destroy_vm_headless()
+    '';
+  };
+
+in mapAttrs (mkVBoxTest {} vboxVMs) {
   simple-gui = ''
     # Home to select Tools, down to move to the VM, enter to start it.
     def send_vm_startup():
@@ -519,4 +546,6 @@ in mapAttrs (mkVBoxTest false vboxVMs) {
     destroy_vm_test1()
     destroy_vm_test2()
   '';
-} // (optionalAttrs enableUnfree unfreeTests)
+}
+// (optionalAttrs enableKvm kvmTests)
+// (optionalAttrs enableUnfree unfreeTests)
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;
   };