about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/development/option-declarations.section.md2
-rw-r--r--nixos/doc/manual/development/settings-options.section.md21
-rw-r--r--nixos/doc/manual/release-notes/release-notes.md1
-rw-r--r--nixos/doc/manual/release-notes/rl-2111.section.md2
-rw-r--r--nixos/doc/manual/release-notes/rl-2405.section.md863
-rw-r--r--nixos/doc/manual/release-notes/rl-2411.section.md28
-rw-r--r--nixos/lib/test-driver/default.nix3
-rw-r--r--nixos/lib/test-driver/pyproject.toml8
-rwxr-xr-xnixos/lib/test-driver/test_driver/__init__.py30
-rw-r--r--nixos/lib/test-driver/test_driver/driver.py44
-rw-r--r--nixos/lib/test-driver/test_driver/logger.py249
-rw-r--r--nixos/lib/test-driver/test_driver/machine.py16
-rw-r--r--nixos/lib/test-driver/test_driver/polling_condition.py11
-rw-r--r--nixos/lib/test-driver/test_driver/vlan.py15
-rw-r--r--nixos/lib/test-script-prepend.py4
-rw-r--r--nixos/lib/testing/driver.nix2
-rw-r--r--nixos/maintainers/scripts/ec2/README.md35
-rwxr-xr-xnixos/maintainers/scripts/ec2/create-amis.sh368
-rw-r--r--nixos/modules/config/no-x-libs.nix3
-rw-r--r--nixos/modules/config/nsswitch.nix26
-rw-r--r--nixos/modules/hardware/video/nvidia.nix871
-rw-r--r--nixos/modules/misc/version.nix2
-rw-r--r--nixos/modules/module-list.nix13
-rw-r--r--nixos/modules/programs/atop.nix4
-rw-r--r--nixos/modules/programs/benchexec.nix98
-rw-r--r--nixos/modules/programs/cpu-energy-meter.nix27
-rw-r--r--nixos/modules/programs/firefox.nix2
-rw-r--r--nixos/modules/programs/gnupg.nix16
-rw-r--r--nixos/modules/programs/kdeconnect.nix1
-rw-r--r--nixos/modules/programs/pantheon-tweaks.nix17
-rw-r--r--nixos/modules/programs/pqos-wrapper.nix27
-rw-r--r--nixos/modules/programs/screen.nix37
-rw-r--r--nixos/modules/programs/steam.nix42
-rw-r--r--nixos/modules/programs/virt-manager.nix18
-rw-r--r--nixos/modules/programs/wayland/hyprland.nix100
-rw-r--r--nixos/modules/programs/wayland/lib.nix12
-rw-r--r--nixos/modules/programs/wayland/river.nix58
-rw-r--r--nixos/modules/programs/wayland/sway.nix178
-rw-r--r--nixos/modules/programs/wayland/wayland-session.nix31
-rw-r--r--nixos/modules/programs/yazi.nix63
-rw-r--r--nixos/modules/rename.nix8
-rw-r--r--nixos/modules/services/admin/pgadmin.nix10
-rw-r--r--nixos/modules/services/audio/navidrome.nix37
-rw-r--r--nixos/modules/services/backup/borgbackup.nix2
-rw-r--r--nixos/modules/services/continuous-integration/hydra/default.nix2
-rw-r--r--nixos/modules/services/desktop-managers/lomiri.nix5
-rw-r--r--nixos/modules/services/desktop-managers/plasma6.nix49
-rw-r--r--nixos/modules/services/desktops/espanso.nix12
-rw-r--r--nixos/modules/services/display-managers/default.nix4
-rw-r--r--nixos/modules/services/display-managers/sddm.nix15
-rw-r--r--nixos/modules/services/games/archisteamfarm.nix7
-rw-r--r--nixos/modules/services/hardware/kanata.nix23
-rw-r--r--nixos/modules/services/hardware/thermald.nix11
-rw-r--r--nixos/modules/services/home-automation/ebusd.nix2
-rw-r--r--nixos/modules/services/home-automation/wyoming/faster-whisper.nix3
-rw-r--r--nixos/modules/services/home-automation/wyoming/openwakeword.nix3
-rw-r--r--nixos/modules/services/home-automation/wyoming/piper.nix3
-rw-r--r--nixos/modules/services/mail/stalwart-mail.nix9
-rw-r--r--nixos/modules/services/misc/bcg.nix6
-rw-r--r--nixos/modules/services/misc/invidious-router.nix2
-rw-r--r--nixos/modules/services/misc/llama-cpp.nix1
-rw-r--r--nixos/modules/services/misc/plex.nix29
-rw-r--r--nixos/modules/services/misc/portunus.nix4
-rw-r--r--nixos/modules/services/misc/snapper.nix21
-rw-r--r--nixos/modules/services/misc/tzupdate.nix2
-rw-r--r--nixos/modules/services/monitoring/arbtt.nix2
-rw-r--r--nixos/modules/services/monitoring/loki.nix14
-rw-r--r--nixos/modules/services/networking/kea.nix3
-rw-r--r--nixos/modules/services/networking/oink.nix84
-rw-r--r--nixos/modules/services/networking/rosenpass.nix6
-rw-r--r--nixos/modules/services/networking/vsftpd.nix2
-rw-r--r--nixos/modules/services/networking/wireguard.nix10
-rw-r--r--nixos/modules/services/security/bitwarden-directory-connector-cli.nix1
-rw-r--r--nixos/modules/services/security/oauth2-proxy-nginx.nix18
-rw-r--r--nixos/modules/services/security/oauth2-proxy.nix30
-rw-r--r--nixos/modules/services/system/dbus.nix8
-rw-r--r--nixos/modules/services/video/frigate.nix4
-rw-r--r--nixos/modules/services/web-apps/artalk.nix131
-rw-r--r--nixos/modules/services/web-apps/commafeed.nix114
-rw-r--r--nixos/modules/services/web-apps/filesender.md49
-rw-r--r--nixos/modules/services/web-apps/filesender.nix253
-rw-r--r--nixos/modules/services/web-apps/firefly-iii.nix91
-rw-r--r--nixos/modules/services/web-apps/flarum.nix210
-rw-r--r--nixos/modules/services/web-apps/keycloak.nix5
-rw-r--r--nixos/modules/services/web-apps/nextcloud.nix2
-rw-r--r--nixos/modules/services/web-apps/nextjs-ollama-llm-ui.nix87
-rw-r--r--nixos/modules/services/web-apps/pretalx.nix35
-rw-r--r--nixos/modules/services/web-apps/simplesamlphp.nix128
-rw-r--r--nixos/modules/services/web-apps/your_spotify.nix191
-rw-r--r--nixos/modules/services/web-servers/bluemap.nix311
-rw-r--r--nixos/modules/services/web-servers/garage.nix35
-rw-r--r--nixos/modules/services/x11/window-managers/qtile.nix14
-rw-r--r--nixos/modules/system/boot/binfmt.nix2
-rw-r--r--nixos/modules/system/boot/resolved.nix143
-rw-r--r--nixos/modules/system/boot/systemd/initrd.nix4
-rw-r--r--nixos/modules/virtualisation/lxc-image-metadata.nix4
-rw-r--r--nixos/modules/virtualisation/qemu-vm.nix2
-rw-r--r--nixos/release-combined.nix2
-rw-r--r--nixos/tests/all-tests.nix11
-rw-r--r--nixos/tests/artalk.nix28
-rw-r--r--nixos/tests/ayatana-indicators.nix7
-rw-r--r--nixos/tests/benchexec.nix54
-rw-r--r--nixos/tests/castopod.nix3
-rw-r--r--nixos/tests/commafeed.nix21
-rw-r--r--nixos/tests/filesender.nix137
-rw-r--r--nixos/tests/firefly-iii.nix97
-rw-r--r--nixos/tests/garage/default.nix1
-rw-r--r--nixos/tests/garage/with-3node-replication.nix8
-rw-r--r--nixos/tests/installer-systemd-stage-1.nix2
-rw-r--r--nixos/tests/installer.nix14
-rw-r--r--nixos/tests/keepalived.nix4
-rw-r--r--nixos/tests/kernel-generic.nix2
-rw-r--r--nixos/tests/lomiri.nix143
-rw-r--r--nixos/tests/mediamtx.nix95
-rw-r--r--nixos/tests/mysql/common.nix2
-rw-r--r--nixos/tests/mysql/mysql.nix2
-rw-r--r--nixos/tests/stalwart-mail.nix20
-rw-r--r--nixos/tests/step-ca.nix23
-rw-r--r--nixos/tests/stub-ld.nix4
-rw-r--r--nixos/tests/systemd-initrd-luks-fido2.nix1
-rw-r--r--nixos/tests/systemd-resolved.nix75
-rw-r--r--nixos/tests/tigervnc.nix8
-rw-r--r--nixos/tests/vector.nix37
-rw-r--r--nixos/tests/vector/api.nix39
-rw-r--r--nixos/tests/vector/default.nix11
-rw-r--r--nixos/tests/vector/dnstap.nix118
-rw-r--r--nixos/tests/vector/file-sink.nix49
-rw-r--r--nixos/tests/vector/nginx-clickhouse.nix168
-rw-r--r--nixos/tests/virtualbox.nix1
-rw-r--r--nixos/tests/web-apps/nextjs-ollama-llm-ui.nix22
-rw-r--r--nixos/tests/web-apps/pretalx.nix5
-rw-r--r--nixos/tests/your_spotify.nix33
132 files changed, 4889 insertions, 1969 deletions
diff --git a/nixos/doc/manual/development/option-declarations.section.md b/nixos/doc/manual/development/option-declarations.section.md
index 325f4d11cb083..ee4540d0cf6fd 100644
--- a/nixos/doc/manual/development/option-declarations.section.md
+++ b/nixos/doc/manual/development/option-declarations.section.md
@@ -173,7 +173,7 @@ lib.mkOption {
 
 ## Extensible Option Types {#sec-option-declarations-eot}
 
-Extensible option types is a feature that allow to extend certain types
+Extensible option types is a feature that allows to extend certain types
 declaration through multiple module files. This feature only work with a
 restricted set of types, namely `enum` and `submodules` and any composed
 forms of them.
diff --git a/nixos/doc/manual/development/settings-options.section.md b/nixos/doc/manual/development/settings-options.section.md
index 806eee5637907..cedc82d32f89a 100644
--- a/nixos/doc/manual/development/settings-options.section.md
+++ b/nixos/doc/manual/development/settings-options.section.md
@@ -146,6 +146,27 @@ have a predefined type and string generator already declared under
     :   Outputs the given attribute set as an Elixir map, instead of the
         default Elixir keyword list
 
+`pkgs.formats.php { finalVariable }` []{#pkgs-formats-php}
+
+:   A function taking an attribute set with values
+
+    `finalVariable`
+
+    :   The variable that will store generated expression (usually `config`). If set to `null`, generated expression will contain `return`.
+
+    It returns a set with PHP-Config-specific attributes `type`, `lib`, and
+    `generate` as specified [below](#pkgs-formats-result).
+
+    The `lib` attribute contains functions to be used in settings, for
+    generating special PHP values:
+
+    `mkRaw phpCode`
+
+    :   Outputs the given string as raw PHP code
+
+    `mkMixedArray list set`
+
+    :   Creates PHP array that contains both indexed and associative values. For example, `lib.mkMixedArray [ "hello" "world" ] { "nix" = "is-great"; }` returns `['hello', 'world', 'nix' => 'is-great']`
 
 []{#pkgs-formats-result}
 These functions all return an attribute set with these values:
diff --git a/nixos/doc/manual/release-notes/release-notes.md b/nixos/doc/manual/release-notes/release-notes.md
index 0514a1b0044af..24494ed95ca88 100644
--- a/nixos/doc/manual/release-notes/release-notes.md
+++ b/nixos/doc/manual/release-notes/release-notes.md
@@ -3,6 +3,7 @@
 This section lists the release notes for each stable version of NixOS and current unstable revision.
 
 ```{=include=} sections
+rl-2411.section.md
 rl-2405.section.md
 rl-2311.section.md
 rl-2305.section.md
diff --git a/nixos/doc/manual/release-notes/rl-2111.section.md b/nixos/doc/manual/release-notes/rl-2111.section.md
index 8edf4fd35e4fb..4143f440f2890 100644
--- a/nixos/doc/manual/release-notes/rl-2111.section.md
+++ b/nixos/doc/manual/release-notes/rl-2111.section.md
@@ -146,7 +146,7 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - [touchegg](https://github.com/JoseExposito/touchegg), a multi-touch gesture recognizer. Available as [services.touchegg](#opt-services.touchegg.enable).
 
-- [pantheon-tweaks](https://github.com/pantheon-tweaks/pantheon-tweaks), an unofficial system settings panel for Pantheon. Available as [programs.pantheon-tweaks](#opt-programs.pantheon-tweaks.enable).
+- [pantheon-tweaks](https://github.com/pantheon-tweaks/pantheon-tweaks), an unofficial system settings panel for Pantheon. Available as `programs.pantheon-tweaks`.
 
 - [joycond](https://github.com/DanielOgorchock/joycond), a service that uses `hid-nintendo` to provide nintendo joycond pairing and better nintendo switch pro controller support.
 
diff --git a/nixos/doc/manual/release-notes/rl-2405.section.md b/nixos/doc/manual/release-notes/rl-2405.section.md
index 489474468466c..4187d4a58afec 100644
--- a/nixos/doc/manual/release-notes/rl-2405.section.md
+++ b/nixos/doc/manual/release-notes/rl-2405.section.md
@@ -6,21 +6,17 @@ Support is planned until the end of December 2024, handing over to 24.11.
 
 In addition to numerous new and upgraded packages, this release has the following highlights:
 
-<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
-
-- `cryptsetup` has been upgraded from 2.6.1 to 2.7.0. Cryptsetup is a critical component enabling LUKS-based (but not only) full disk encryption.
-  Take the time to review [the release notes](https://gitlab.com/cryptsetup/cryptsetup/-/raw/v2.7.0/docs/v2.7.0-ReleaseNotes).
-  One of the highlights is that it is now possible to use hardware OPAL-based encryption of your disk with `cryptsetup`. It has a lot of caveats, see the above notes for the full details.
-
-- `screen`'s module has been cleaned, and will now require you to set `programs.screen.enable` in order to populate `screenrc` and add the program to the environment.
-
-- `linuxPackages_testing_bcachefs` is now fully deprecated by `linuxPackages_latest`, and is therefore no longer available.
+<!-- Please keep entries alphabetically sorted. -->
 
 - The default kernel package has been updated from 6.1 to 6.6. All supported kernels remain available.
 
+- For each supporting version of the Linux kernel, firmware blobs
+  are compressed with zstd. For firmware blobs this means an increase of 4.4% in size, however
+  a significantly higher decompression speed.
+
 - 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.
+  - 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.
 
@@ -30,33 +26,6 @@ In addition to numerous new and upgraded packages, this release has the followin
 
   To disable this, set [nixpkgs.flake.setNixPath](#opt-nixpkgs.flake.setNixPath) and [nixpkgs.flake.setFlakeRegistry](#opt-nixpkgs.flake.setFlakeRegistry) to false.
 
-- `nixVersions.unstable` was removed. Instead the following attributes are provided:
-  - `nixVersions.git` which tracks the latest Nix master and is roughly updated once a week. This is intended to enable people to easily test unreleased changes of Nix to catch regressions earlier.
-  - `nixVersions.latest` which points to the latest Nix version packaged in nixpkgs.
-
-- `julia` environments can now be built with arbitrary packages from the ecosystem using the `.withPackages` function. For example: `julia.withPackages ["Plots"]`.
-
-- `pipewire` and `wireplumber` modules have removed support for using
-`environment.etc."pipewire/..."` and `environment.etc."wireplumber/..."`.
-Use `services.pipewire.extraConfig` or `services.pipewire.configPackages` for PipeWire and
-`services.pipewire.wireplumber.configPackages` for WirePlumber instead."
-
-- `teleport` has been upgraded from major version 14 to major version 15.
-  Refer to upstream [upgrade instructions](https://goteleport.com/docs/management/operations/upgrading/)
-  and release notes for [v15](https://goteleport.com/docs/changelog/#1500-013124).
-
-- `systemd.sysusers.enable` option was added. If enabled, users and
-  groups are created with systemd-sysusers instead of with a custom perl script.
-
-- `virtualisation.docker.enableNvidia` and `virtualisation.podman.enableNvidia` options are deprecated. `hardware.nvidia-container-toolkit.enable` should be used instead. This option will expose GPUs on containers with the `--device` CLI option. This is supported by Docker 25, Podman 3.2.0 and Singularity 4. Any container runtime that supports the CDI specification will take advantage of this feature.
-
-- `system.etc.overlay.enable` option was added. If enabled, `/etc` is
-  mounted via an overlayfs instead of being created by a custom perl script.
-
-- For each supporting version of the Linux kernel firmware blobs
-  are compressed with zstd. For firmware blobs this means an increase of 4.4% in size, however
-  a significantly higher decompression speed.
-
 - NixOS AMIs are now uploaded regularly to a new AWS Account.
   Instructions on how to use them can be found on <https://nixos.github.io/amis>.
   We are working on integration the data into the NixOS homepage.
@@ -73,272 +42,310 @@ Use `services.pipewire.extraConfig` or `services.pipewire.configPackages` for Pi
   }
   ```
 
-- `virtialisation.incus` now defaults to the newly-added `incus-lts` release (v6.0.x). Users who wish to continue using the non-LTS release will need to set `virtualisation.incus.package = pkgs.incus`. Stable release users are encouraged to stay on the LTS release as non-LTS releases will by default not be backported.
+- Lomiri (formerly known as Unity8) desktop mode, using Mir 2.x to function as a Wayland compositor, is now available and can be installed with `services.desktopManager.lomiri.enable = true`. Note that some core applications, services and indicators have yet to be packaged, and some functions may remain incomplete, but the base experience should be there.
 
-- Canonical `lxd` has been upgraded to v5.21.x, an LTS release. The LTS release is now the only supported LXD release. Users are encouraged to [migrate to Incus](https://linuxcontainers.org/incus/docs/main/howto/server_migrate_lxd/) for better support on NixOS.
+- MATE has been updated to 1.28.
+  - To properly support panel plugins built with Wayland (in-process) support, we are introducing `services.xserver.desktopManager.mate.extraPanelApplets` option, please use that for installing panel applets.
+  - Similarly, please use `services.xserver.desktopManager.mate.extraCajaExtensions` option for installing Caja extensions.
+  - To use the Wayland session, enable `services.xserver.desktopManager.mate.enableWaylandSession`. This is opt-in for now as it is in early stage and introduces a new set of Wayfire closure. Due to [known issues with LightDM](https://github.com/canonical/lightdm/issues/63), we suggest using SDDM for display manager.
 
-- `lua` interpreters default LUA_PATH and LUA_CPATH are not overriden by nixpkgs
-  anymore, we patch LUA_ROOT instead which is more respectful to upstream.
+- 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.
 
-- `plasma6` 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}
 
-- `lomiri` (formerly known as Unity8) desktop mode, using Mir 2.x to function as a Wayland compositor, is now available and can be installed with `services.desktopManager.lomiri.enable = true`. Note that some core applications, services and indicators have yet to be packaged, and some functions may remain incomplete, but the base experience should be there.
+<!-- Please keep entries alphabetically sorted. -->
 
-## New Services {#sec-release-24.05-new-services}
+- [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 software.
 
-<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
+- [ALVR](https://github.com/alvr-org/alvr), a VR desktop streamer. Available as [programs.alvr](#opt-programs.alvr.enable).
 
-- [ownCloud Infinite Scale Stack](https://owncloud.com/infinite-scale-4-0/), a modern and scalable rewrite of ownCloud.
+- [AppImage](https://appimage.org/), a tool to package desktop applications, now has a `binfmt` option to support running AppImages seamlessly on NixOS. Available as [programs.appimage.binfmt](#opt-programs.appimage.binfmt).
 
-- [Handheld Daemon](https://github.com/hhd-dev/hhd), support for gaming handhelds like the Legion Go, ROG Ally, and GPD Win. Available as [services.handheld-daemon](#opt-services.handheld-daemon.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`.
 
-- [Guix](https://guix.gnu.org), a functional package manager inspired by Nix. Available as [services.guix](#opt-services.guix.enable).
+- [BenchExec](https://github.com/sosy-lab/benchexec), a framework for reliable benchmarking and resource measurement, available as [programs.benchexec](#opt-programs.benchexec.enable),
+  As well as related programs
+  [CPU Energy Meter](https://github.com/sosy-lab/cpu-energy-meter), available as [programs.cpu-energy-meter](#opt-programs.cpu-energy-meter.enable), and
+  [PQoS Wrapper](https://gitlab.com/sosy-lab/software/pqos-wrapper), available as [programs.pqos-wrapper](#opt-programs.pqos-wrapper.enable).
 
-- [PhotonVision](https://photonvision.org/), a free, fast, and easy-to-use computer vision solution for the FIRST® Robotics Competition.
+- [Bluemap](https://bluemap.bluecolored.de/), a 3D minecraft map renderer. Available as [services.bluemap](#opt-services.bluemap.enable).
 
 - [clatd](https://github.com/toreanderson/clatd), a CLAT / SIIT-DC Edge Relay implementation for Linux.
 
-- [pyLoad](https://pyload.net/), a FOSS download manager written in Python. Available as [services.pyload](#opt-services.pyload.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).
 
-- [maubot](https://github.com/maubot/maubot), a plugin-based Matrix bot framework. Available as [services.maubot](#opt-services.maubot.enable).
+- [CommaFeed](https://github.com/Athou/commafeed), a Google Reader-inspired self-hosted RSS reader. Available as [services.commafeed](#opt-services.commafeed.enable).
 
-- [ryzen-monitor-ng](https://github.com/mann1x/ryzen_monitor_ng), a desktop AMD CPU power monitor and controller, similar to Ryzen Master but for Linux. Available as [programs.ryzen-monitor-ng](#opt-programs.ryzen-monitor-ng.enable).
+- [davis](https://github.com/tchapi/davis), a simple CardDav and CalDav server inspired by Baïkal. Available as [services.davis](#opt-services.davis.enable).
 
-- [ryzen-smu](https://gitlab.com/leogx9r/ryzen_smu), Linux kernel driver to expose the SMU (System Management Unit) for certain AMD Ryzen Processors. Includes the userspace program `monitor_cpu`. Available at [hardward.cpu.amd.ryzen-smu](#opt-hardware.cpu.amd.ryzen-smu.enable).
+- [db-rest](https://github.com/derhuerst/db-rest), a wrapper around Deutsche Bahn's internal API for public transport data. Available as [services.db-rest](#opt-services.db-rest.enable).
 
-- `systemd`'s `gateway`, `upload`, and `remote` services, which provide ways of sending journals across the network. Enable using [services.journald.gateway](#opt-services.journald.gateway.enable), [services.journald.upload](#opt-services.journald.upload.enable), and [services.journald.remote](#opt-services.journald.remote.enable).
+- [dnsproxy](https://github.com/AdguardTeam/dnsproxy), a simple DNS proxy with DoH, DoT, DoQ and DNSCrypt support. Available as [services.dnsproxy](#opt-services.dnsproxy.enable).
 
-- [GNS3](https://www.gns3.com/), a network software emulator. Available as [services.gns3-server](#opt-services.gns3-server.enable).
+- [FCast Receiver](https://fcast.org), an open-source alternative to Chromecast and AirPlay. Available as [programs.fcast-receiver](#opt-programs.fcast-receiver.enable).
 
-- [pretalx](https://github.com/pretalx/pretalx), a conference planning tool. Available as [services.pretalx](#opt-services.pretalx.enable).
+- [FileSender](https://filesender.org/), a file sharing software. Available as [services.filesender](#opt-services.filesender.enable).
 
-- [dnsproxy](https://github.com/AdguardTeam/dnsproxy), a simple DNS proxy with DoH, DoT, DoQ and DNSCrypt support. Available as [services.dnsproxy](#opt-services.dnsproxy.enable).
-
-- [manticoresearch](https://manticoresearch.com), easy to use open source fast database for search. Available as [services.manticore](#opt-services.manticore.enable).
+- [Firefly-iii](https://www.firefly-iii.org), a free and open source personal finance manager. Available as [services.firefly-iii](#opt-services.firefly-iii.enable).
 
-- [rspamd-trainer](https://gitlab.com/onlime/rspamd-trainer), script triggered by a helper which reads mails from a specific mail inbox and feeds them into rspamd for spam/ham training.
+- [Flarum](https://flarum.org/), a delightfully simple discussion platform for your website. Available as [services.flarum](#opt-services.flarum.enable).
 
-- [ollama](https://ollama.ai), server for running large language models locally.
+- [fritz-exporter](https://github.com/pdreker/fritz_exporter), a Prometheus exporter for extracting metrics from [FRITZ!](https://avm.de/produkte/) devices. Available as [services.prometheus.exporters.fritz](#opt-services.prometheus.exporters.fritz.enable).
 
-- [Mihomo](https://github.com/MetaCubeX/mihomo/tree/Alpha), a rule-based proxy in Go. Available as [services.mihomo.enable](#opt-services.mihomo.enable).
+- [GNS3](https://www.gns3.com/), a network software emulator. Available as [services.gns3-server](#opt-services.gns3-server.enable).
 
-- [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).
+- [go-camo](https://github.com/cactus/go-camo), a secure image proxy server. Available as [services.go-camo](#opt-services.go-camo.enable).
 
-- [Workout-tracker](https://github.com/jovandeginste/workout-tracker), a workout tracking web application for personal use.
+- [Guix](https://guix.gnu.org), a functional package manager inspired by Nix. Available as [services.guix](#opt-services.guix.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).
+- [Handheld Daemon](https://github.com/hhd-dev/hhd), support for gaming handhelds like the Legion Go, ROG Ally, and GPD Win. Available as [services.handheld-daemon](#opt-services.handheld-daemon.enable).
 
-- [db-rest](https://github.com/derhuerst/db-rest), a wrapper around Deutsche Bahn's internal API for public transport data. Available as [services.db-rest](#opt-services.db-rest.enable).
+- [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).
 
-- [mautrix-signal](https://github.com/mautrix/signal), a Matrix-Signal puppeting bridge. Available as [services.mautrix-signal](#opt-services.mautrix-signal.enable).
+- [inadyn](https://github.com/troglobit/inadyn), a Dynamic DNS client with built-in support for multiple providers. Available as [services.inadyn](#opt-services.inadyn.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 software.
+- [intel-gpu-tools](https://drm.pages.freedesktop.org/igt-gpu-tools), tools for development and testing of the Intel DRM driver. Available as [hardware.intel-gpu-tools](#opt-hardware.intel-gpu-tools.enable).
 
-- [mautrix-meta](https://github.com/mautrix/meta), a Matrix <-> Facebook and Matrix <-> Instagram hybrid puppeting/relaybot bridge. Available as services.mautrix-meta.
+- [isolate](https://github.com/ioi/isolate), a sandbox for securely executing untrusted programs. Available as [security.isolate](#opt-security.isolate.enable).
 
 - [Jottacloud Command-line Tool](https://docs.jottacloud.com/en/articles/1436834-jottacloud-command-line-tool), a CLI for the [Jottacloud](https://jottacloud.com/) cloud storage provider. Available as [services.jotta-cli](#opt-services.jotta-cli.enable).
 
-- [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).
+- [keto](https://www.ory.sh/keto/), a permission & access control server, the first open source implementation of ["Zanzibar: Google's Consistent, Global Authorization System"](https://research.google/pubs/zanzibar-googles-consistent-global-authorization-system/).
 
-- [FCast Receiver](https://fcast.org), an open-source alternative to Chromecast and AirPlay. Available as [programs.fcast-receiver](#opt-programs.fcast-receiver.enable).
+- [manticoresearch](https://manticoresearch.com), easy to use open source fast database for search. Available as [services.manticore](#opt-services.manticore.enable).
 
-- [MollySocket](https://github.com/mollyim/mollysocket) which allows getting Signal notifications via UnifiedPush.
+- [maubot](https://github.com/maubot/maubot), a plugin-based Matrix bot framework. Available as [services.maubot](#opt-services.maubot.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).
+- [mautrix-meta](https://github.com/mautrix/meta), a Matrix <-> Facebook and Matrix <-> Instagram hybrid puppeting/relaybot bridge. Available as services.mautrix-meta.
 
-- [Netbird](https://netbird.io), an open-source VPN management platform, now has a self-hosted management server. Available as [services.netbird.server](#opt-services.netbird.server.enable).
+- [mautrix-signal](https://github.com/mautrix/signal), a Matrix-Signal puppeting bridge. Available as [services.mautrix-signal](#opt-services.mautrix-signal.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).
+- [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).
 
-- [Prometheus DNSSEC Exporter](https://github.com/chrj/prometheus-dnssec-exporter), check for validity and expiration in DNSSEC signatures and expose metrics for Prometheus. Available as [services.prometheus.exporters.dnssec](#opt-services.prometheus.exporters.dnssec.enable).
+- [MollySocket](https://github.com/mollyim/mollysocket) which allows getting Signal notifications via UnifiedPush.
 
-- [TigerBeetle](https://tigerbeetle.com/), a distributed financial accounting database designed for mission critical safety and performance. Available as [services.tigerbeetle](#opt-services.tigerbeetle.enable).
+- [microsocks](https://github.com/rofl0r/microsocks), a tiny, portable SOCKS5 server with very moderate resource usage. Available as [services.microsocks](#opt-services.microsocks.enable).
 
-- [go-camo](https://github.com/cactus/go-camo), a secure image proxy server. Available as [services.go-camo](#opt-services.go-camo.enable).
+- [Mihomo](https://github.com/MetaCubeX/mihomo/tree/Alpha), a rule-based proxy in Go. Available as [services.mihomo.enable](#opt-services.mihomo.enable).
 
 - [Monado](https://monado.freedesktop.org/), an open source XR runtime. Available as [services.monado](#opt-services.monado.enable).
 
-- [intel-gpu-tools](https://drm.pages.freedesktop.org/igt-gpu-tools), tools for development and testing of the Intel DRM driver. Available as [hardware.intel-gpu-tools](#opt-hardware.intel-gpu-tools.enable).
+- [Netbird](https://netbird.io), an open-source VPN management platform, now has a self-hosted management server. Available as [services.netbird.server](#opt-services.netbird.server.enable).
 
-- [Pretix](https://pretix.eu/about/en/), an open source ticketing software for events. Available as [services.pretix](#opt-services.pretix.enable).
+- [nh](https://github.com/viperML/nh), yet another Nix CLI helper. Available as [programs.nh](#opt-programs.nh.enable).
 
-- [microsocks](https://github.com/rofl0r/microsocks), a tiny, portable SOCKS5 server with very moderate resource usage. Available as [services.microsocks](#opt-services.microsocks.enable).
+- [oink](https://github.com/rlado/oink), a dynamic DNS client for Porkbun. Available as [services.oink](#opt-services.oink.enable).
 
-- [inadyn](https://github.com/troglobit/inadyn), a Dynamic DNS client with built-in support for multiple providers. Available as [services.inadyn](#opt-services.inadyn.enable).
+- [ollama](https://ollama.ai), server for running large language models locally.
 
-- [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).
+- [nextjs-ollama-llm-ui](https://github.com/jakobhoeg/nextjs-ollama-llm-ui), light-weight frontend server to chat with Ollama models through a web app.
 
-- [fritz-exporter](https://github.com/pdreker/fritz_exporter), a Prometheus exporter for extracting metrics from [FRITZ!](https://avm.de/produkte/) devices. Available as [services.prometheus.exporters.fritz](#opt-services.prometheus.exporters.fritz.enable).
+- [ownCloud Infinite Scale Stack](https://owncloud.com/infinite-scale-4-0/), a modern and scalable rewrite of ownCloud.
 
-- [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`.
+- [PhotonVision](https://photonvision.org/), a free, fast, and easy-to-use computer vision solution for the FIRST® Robotics Competition.
 
-- [wyoming-satellite](https://github.com/rhasspy/wyoming-satellite), a voice assistant satellite for Home Assistant using the Wyoming protocol. Available as [services.wyoming.satellite](#opt-services.wyoming.satellite.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).
 
-- [TuxClocker](https://github.com/Lurkki14/tuxclocker), a hardware control and monitoring program. Available as [programs.tuxclocker](#opt-programs.tuxclocker.enable).
+- [Pretix](https://pretix.eu/about/en/), an open source ticketing software for events. Available as [services.pretix](#opt-services.pretix.enable).
 
-- [AppImage](https://appimage.org/), a tool to package desktop applications, now has a `binfmt` option to support running AppImages seamlessly on NixOS. Available as [programs.appimage.binfmt](#opt-programs.appimage.binfmt).
+- [pretalx](https://github.com/pretalx/pretalx), a conference planning tool. Available as [services.pretalx](#opt-services.pretalx.enable).
 
-- [nh](https://github.com/viperML/nh), yet another Nix CLI helper. Available as [programs.nh](#opt-programs.nh.enable).
+- [private-gpt](https://github.com/zylon-ai/private-gpt), a service to interact with your documents using the power of LLMs, 100% privately, no data leaks. Available as [services.private-gpt](#opt-services.private-gpt.enable).
 
-- [ALVR](https://github.com/alvr-org/alvr), a VR desktop streamer. Available as [programs.alvr](#opt-programs.alvr.enable).
+- [Prometheus DNSSEC Exporter](https://github.com/chrj/prometheus-dnssec-exporter), check for validity and expiration in DNSSEC signatures and expose metrics for Prometheus. Available as [services.prometheus.exporters.dnssec](#opt-services.prometheus.exporters.dnssec.enable).
 
-- [xdg-terminal-exec](https://github.com/Vladimir-csp/xdg-terminal-exec), the proposed Default Terminal Execution Specification.
+- [prometheus-nats-exporter](https://github.com/nats-io/prometheus-nats-exporter), a Prometheus exporter for NATS. Available as [services.prometheus.exporters.nats](#opt-services.prometheus.exporters.nats.enable).
+
+- [pyLoad](https://pyload.net/), a FOSS download manager written in Python. Available as [services.pyload](#opt-services.pyload.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).
 
 - [RustDesk](https://rustdesk.com), a full-featured open source remote control alternative for self-hosting and security with minimal configuration. Alternative to TeamViewer. Available as [services.rustdesk-server](#opt-services.rustdesk-server.enable).
 
+- [ryzen-monitor-ng](https://github.com/mann1x/ryzen_monitor_ng), a desktop AMD CPU power monitor and controller, similar to Ryzen Master but for Linux. Available as [programs.ryzen-monitor-ng](#opt-programs.ryzen-monitor-ng.enable).
+
+- [ryzen-smu](https://gitlab.com/leogx9r/ryzen_smu), Linux kernel driver to expose the SMU (System Management Unit) for certain AMD Ryzen Processors. Includes the userspace program `monitor_cpu`. Available at [hardward.cpu.amd.ryzen-smu](#opt-hardware.cpu.amd.ryzen-smu.enable).
+
 - [Scrutiny](https://github.com/AnalogJ/scrutiny), a S.M.A.R.T monitoring tool for hard disks with a web frontend. Available as [services.scrutiny](#opt-services.scrutiny.enable).
 
-- [davis](https://github.com/tchapi/davis), a simple CardDav and CalDav server inspired by Baïkal. Available as [services.davis](#opt-services.davis.enable).
+- [SimpleSAMLphp](https://simplesamlphp.org/), an application written in native PHP that deals with authentication (SQL, .htpasswd, YubiKey, LDAP, PAPI, Radius). Available as [services.simplesamlphp](#opt-services.simplesamlphp).
 
-- [Firefly-iii](https://www.firefly-iii.org), a free and open source personal finance manager. Available as [services.firefly-iii](#opt-services.firefly-iii.enable).
+- `systemd`'s `gateway`, `upload`, and `remote` services, which provide ways of sending journals across the network. Enable using [services.journald.gateway](#opt-services.journald.gateway.enable), [services.journald.upload](#opt-services.journald.upload.enable), and [services.journald.remote](#opt-services.journald.remote.enable).
 
 - [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).
 
-- [wastebin](https://github.com/matze/wastebin), a pastebin server written in rust. Available as [services.wastebin](#opt-services.wastebin.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).
+- [rspamd-trainer](https://gitlab.com/onlime/rspamd-trainer), script triggered by a helper which reads mails from a specific mail inbox and feeds them into rspamd for spam/ham training.
 
 - [Sunshine](https://app.lizardbyte.dev/Sunshine), a self-hosted game stream host for Moonlight. Available as [services.sunshine](#opt-services.sunshine.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).
+
+- [TigerBeetle](https://tigerbeetle.com/), a distributed financial accounting database designed for mission critical safety and performance. Available as [services.tigerbeetle](#opt-services.tigerbeetle.enable).
+
+- [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).
+
+- [TuxClocker](https://github.com/Lurkki14/tuxclocker), a hardware control and monitoring program. Available as [programs.tuxclocker](#opt-programs.tuxclocker.enable).
+
 - [Uni-Sync](https://github.com/EightB1ts/uni-sync), a synchronization tool for Lian Li Uni Controllers. Available as [hardware.uni-sync](#opt-hardware.uni-sync.enable).
 
-- [prometheus-nats-exporter](https://github.com/nats-io/prometheus-nats-exporter), a Prometheus exporter for NATS. Available as [services.prometheus.exporters.nats](#opt-services.prometheus.exporters.nats.enable).
+- [wastebin](https://github.com/matze/wastebin), a pastebin server written in rust. Available as [services.wastebin](#opt-services.wastebin.enable).
 
-- [isolate](https://github.com/ioi/isolate), a sandbox for securely executing untrusted programs. Available as [security.isolate](#opt-security.isolate.enable).
+- [watchdogd](https://troglobit.com/projects/watchdogd/), a system and process supervisor using watchdog timers. Available as [services.watchdogd](#opt-services.watchdogd.enable).
 
-- [ydotool](https://github.com/ReimuNotMoe/ydotool), a generic command-line automation tool now has a module. Available as [programs.ydotool](#opt-programs.ydotool.enable).
+- [Workout-tracker](https://github.com/jovandeginste/workout-tracker), a workout tracking web application for personal use.
 
-- [private-gpt](https://github.com/zylon-ai/private-gpt), a service to interact with your documents using the power of LLMs, 100% privately, no data leaks. Available as [services.private-gpt](#opt-services.private-gpt.enable).
+- [wyoming-satellite](https://github.com/rhasspy/wyoming-satellite), a voice assistant satellite for Home Assistant using the Wyoming protocol. Available as [services.wyoming.satellite](#opt-services.wyoming.satellite.enable).
 
-- [keto](https://www.ory.sh/keto/), a permission & access control server, the first open source implementation of ["Zanzibar: Google's Consistent, Global Authorization System"](https://research.google/pubs/zanzibar-googles-consistent-global-authorization-system/).
+- [xdg-terminal-exec](https://github.com/Vladimir-csp/xdg-terminal-exec), the proposed Default Terminal Execution Specification.
+
+- [ydotool](https://github.com/ReimuNotMoe/ydotool), a generic command-line automation tool now has a module. Available as [programs.ydotool](#opt-programs.ydotool.enable).
+
+- [your_spotify](https://github.com/Yooooomi/your_spotify), a self hosted Spotify tracking dashboard. Available as [services.your_spotify](#opt-services.your_spotify.enable)
 
 ## Backward Incompatibilities {#sec-release-24.05-incompatibilities}
 
 <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
 
-- `k3s`: was updated to version [v1.29](https://github.com/k3s-io/k3s/releases/tag/v1.29.1%2Bk3s2), all previous versions (k3s_1_26, k3s_1_27, k3s_1_28) will be removed. See [changelog and upgrade notes](https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.29.md#urgent-upgrade-notes) for more information.
+- `akkoma` now requires explicitly setting the base URL for uploaded media (`settings."Pleroma.Upload".base_url`), as well as for the media proxy if enabled (`settings."Media"`).
+  This is recommended to be a separate (sub)domain to the one Akkoma is hosted at.
+  See [here](https://meta.akkoma.dev/t/akkoma-stable-2024-03-securer-i-barely-know-her/681#explicit-upload-and-media-proxy-domains-5) for more details.
 
-- `himalaya` was updated to v1.0.0-beta.4, which introduces breaking changes. Check out the [release note](https://github.com/soywod/himalaya/releases/tag/v1.0.0-beta.4) for details.
+- `appimageTools.wrapAppImage` now creates the binary at `$out/bin/${pname}` rather than `$out/bin/${pname}-${version}`, which will break downstream workarounds.
 
-- `security.pam.enableSSHAgentAuth` was replaced by the `sshAgentAuth` attrset, and **only**
-  `authorized_keys` files listed in [`sshAgentAuth.authorizedKeysFiles`] are trusted,
-  defaulting to `/etc/ssh/authorized_keys.d/%u`.
-  ::: {.warning}
-  Users of {manpage}`pam_ssh_agent_auth(8)` must take care that the pubkeys they use (for instance with `sudo`)
-  are listed in [`sshAgentAuth.authorizedKeysFiles`].
-  :::
-  ::: {.note}
-  Previously, all `services.openssh.authorizedKeysFiles` were trusted, including `~/.ssh/authorized_keys`,
-  which results in an **insecure** configuration; see [#31611](https://github.com/NixOS/nixpkgs/issues/31611).
-  :::
+- `azure-cli` now has extension support. For example, to install the `aks-preview` extension, use
 
-[`sshAgentAuth.authorizedKeysFiles`]: #opt-security.pam.sshAgentAuth.authorizedKeysFiles
+  ```nix
+  environment.systemPackages = [
+    (azure-cli.withExtensions [ azure-cli.extensions.aks-preview ])
+  ];
+  ```
+  To make the `azure-cli` immutable and prevent clashes in case `azure-cli` is also installed via other package managers, some configuration files were moved into the derivation.
+  This can be disabled by overriding `withImmutableConfig = false` when building `azure-cli`.
 
-- 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.
+- `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.
 
-- `programs.nix-ld.libraries` no longer sets `baseLibraries` via the option's default but in config and now merges any additional libraries with the default ones.
-  This means that `lib.mkForce` must be used to clear the list of default libraries.
+- `buildGoModule` now throws an error when `vendorHash` is not specified. `vendorSha256`, deprecated in Nixpkgs 23.11, is now ignored and is no longer a `vendorHash` alias.
+
+- `chromium` and `ungoogled-chromium` had a long-standing issue regarding Widevine DRM handling in nixpkgs fixed.
+  `chromium` now no longer automatically downloads Widevine when encountering DRM protected content.
+  To be able to play DRM protected content in `chromium` now, you have to explicitly opt-in as originally intended using `chromium.override { enableWideVine = true; }`.
+  This override was added almost 10 years ago.
+
+- `craftos-pc` package has been updated to v2.8, which includes [breaking changes](https://github.com/MCJack123/craftos2/releases/tag/v2.8).
+  - Files are now handled in binary mode; this could break programs with embedded UTF-8 characters.
+  - The ROM was updated to match ComputerCraft version v1.109.2.
+  - The bundled Lua was updated to Lua v5.2, which includes breaking changes. See the [Lua manual](https://www.lua.org/manual/5.2/manual.html#8) for more information.
+  - The WebSocket API [was rewritten](https://github.com/MCJack123/craftos2/issues/337), which introduced breaking changes.
+
+- `cryptsetup` has been upgraded from 2.6.1 to 2.7.0. Cryptsetup is a critical component enabling LUKS-based (but not only) full disk encryption.
+  Take the time to review [the release notes](https://gitlab.com/cryptsetup/cryptsetup/-/raw/v2.7.0/docs/v2.7.0-ReleaseNotes).
+  One of the highlights is that it is now possible to use hardware OPAL-based encryption of your disk with `cryptsetup`. It has a lot of caveats, see the above notes for the full details.
+
+- `crystal` package has been updated to 1.11.x, which has some breaking changes.
+  Refer to crystal's changelog for more information. ([v1.10](https://github.com/crystal-lang/crystal/blob/master/CHANGELOG.md#1100-2023-10-09), [v1.11](https://github.com/crystal-lang/crystal/blob/master/CHANGELOG.md#1110-2024-01-08))
+
+- `cudaPackages` package scope has been updated to `cudaPackages_12`.
 
 - `cudaPackages.autoAddOpenGLRunpathHook` and `cudaPackages.autoAddDriverRunpath` have been deprecated for `pkgs.autoAddDriverRunpath`. Functionality has not changed, but the setuphook has been renamed and moved to the top-level package scope.
 
+- `cudaPackages.cudatoolkit` has been deprecated and replaced with a
+  symlink-based wrapper for the splayed redistributable CUDA packages. The
+  wrapper only includes tools and libraries necessary to build common packages
+  such as tensorflow. The original runfile-based `cudatoolkit` is still
+  available as `cudatoolkit-legacy-runfile`.
+
+- `cudaPackages.nsight_systems` now has most vendored third party-libraries removed, though we now only ship it for `cudaPackages_11_8` and later, due to outdated dependencies. Users comfortable with the vendored dependencies may use `overrideAttrs` to amend the `postPatch` phase and the `meta.broken` correspondingly. Alternatively, one could package the deprecated `boost170` locally, as required for `cudaPackages_11_4.nsight_systems`.
+
 - `cudaPackages.autoFixElfFiles` has been deprecated for `pkgs.autoFixElfFiles`. Functionality has not changed, but the setuphook has been renamed and moved to the top-level package scope.
 
-- `appimageTools.wrapAppImage` now creates the binary at `$out/bin/${pname}` rather than `$out/bin/${pname}-${version}`, which will break downstream workarounds.
+- `davfs2`'s `services.davfs2.extraConfig` setting has been deprecated and converted to the free-form type option named `services.davfs2.settings` according to RFC42.
 
-- `pdns` was updated to version [v4.9.x](https://doc.powerdns.com/authoritative/changelog/4.9.html), which introduces breaking changes. Check out the [Upgrade Notes](https://doc.powerdns.com/authoritative/upgrading.html#to-4-9-0) for details.
+- `dwarf-fortress` has been updated to version 50, and its derivations continue to menace with spikes of Nix and bash [TODO what does this mean?]. Version 50 is identical to the version on Steam, but without the paid elements like tilepacks.
+  dfhack and Dwarf Therapist still work, and older versions are still packaged in case you'd like to roll back. Note that DF 50 saves will not be compatible with DF 0.47 and earlier.
+  See [Bay 12 Games](http://www.bay12games.com/dwarves/) for more details on what's new in Dwarf Fortress.
 
-- `unrar` was updated to v7. See [changelog](https://www.rarlab.com/unrar7notes.htm) for more information.
+  - Running an earlier version can be achieved through an override: `dwarf-fortress-packages.dwarf-fortress-full.override { dfVersion = "0.47.5"; }`
 
-- `git-town` was updated from version 11 to 13. See the [changelog](https://github.com/git-town/git-town/blob/main/CHANGELOG.md#1300-2024-03-22) for breaking changes.
+  - Ruby plugin support has been disabled in DFHack. Many of the Ruby plugins have been converted to Lua, and support was removed upstream due to frequent crashes.
 
-- `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.
+- `erlang-ls` package no longer ships the `els_dap` binary as of v0.51.0.
 
-- the .csv format used to define lua packages to be updated via
-  `luarocks-packages-updater` has changed: `src` (URL towards a git repository) has now become `rockspec` (URL towards a rockspec) to remove ambiguity regarding which rockspec to use and simplify implementation.
+- `erlang_node_short_name`, `erlang_node_name`, `port` and `options` configuration parameters are gone, and have been replaced with an `environment` parameter.
+    Use the appropriate [environment variables](https://hexdocs.pm/livebook/readme.html#environment-variables) inside `environment` to configure the service instead.
 
-- NixOS AMIs are now uploaded regularly to a new AWS Account.
-  Instructions on how to use them can be found on <https://nixos.github.io/amis>.
-  We are working on integration the data into the NixOS homepage.
-  The list in `nixos/modules/virtualisation/amazon-ec2-amis.nix` will stop
-  being updated and will be removed in the future.
+- `firefox-devedition`, `firefox-beta`, `firefox-esr` executable file names for now match their package names, which is consistent with the `firefox-*-bin` packages. The desktop entries are also updated so that you can have multiple editions of firefox in your app launcher.
 
-- The option `services.postgresql.ensureUsers._.ensurePermissions` has been removed as it is
-  not declarative and is broken with newer postgresql versions. Consider using
-  [](#opt-services.postgresql.ensureUsers._.ensureDBOwnership)
-  instead or a tool that is more suited for managing the data inside a postgresql database.
+- `gauge` now supports installing plugins using nix. For the old imperative approach, switch to `gauge-unwrapped`.
+  You can load plugins from an existing gauge manifest file using `gauge.fromManifest ./path/to/manifest.json` or
+  specify plugins in nix using `gauge.withPlugins (p: with p; [ js html-report xml-report ])`.
 
-- `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.
+- `gitea` has been updated to 1.21, which introduces several breaking changes, including:
+  - Custom themes and other assets that were previously stored in `custom/public/*` now belong in `custom/public/assets/*`
+  - New instances of Gitea using MySQL now ignore the `[database].CHARSET` config option and always use the `utf8mb4` charset, existing instances should migrate via the `gitea doctor convert` CLI command.
 
-- `nvtop` family of packages was reorganized into nested attrset. `nvtop` has been renamed to `nvtopPackages.full`, and all `nvtop-{amd,nvidia,intel,msm}` packages are now named as `nvtopPackages.{amd,nvidia,intel,msm}`.
+- `git-town` was updated from version 11 to 13. See the [changelog](https://github.com/git-town/git-town/blob/main/CHANGELOG.md#1300-2024-03-22) for breaking changes.
 
-- `neo4j` has been updated to version 5. You may want to read the [release notes for Neo4j 5](https://neo4j.com/release-notes/database/neo4j-5/).
+- `gonic` has been updated to v0.16.4. Config now requires `playlists-path` to be set. See the rest of the [v0.16.0 release notes](https://github.com/sentriz/gonic/releases/tag/v0.16.0) for more details.
 
-- `services.neo4j.allowUpgrade` was removed and no longer has any effect. Neo4j 5 supports automatic rolling upgrades.
+- `go-ethereum` has been updated to v1.14.3. Geth v1.14.0 introduced a brand new live-tracing feature,
+  which required a number of breaking internal API changes. If you had your own native tracers implemented before this change,
+  the [changelog](https://github.com/ethereum/go-ethereum/blob/master/core/tracing/CHANGELOG.md) contains the necessary steps needed to update your old code for the new APIs.
+  Geth v1.14.0 drops support for running pre-merge networks ([#29169](https://github.com/ethereum/go-ethereum/pull/29169)).
+  It also stops automatically constructing the pending block ([#28623](https://github.com/ethereum/go-ethereum/pull/28623)),
+  removes support for filtering pending logs, switched to using Go v1.22 by default (#28946), which means we've dropped support for Go v1.20.
+  See [the 1.14.0 release notes](https://github.com/ethereum/go-ethereum/releases/tag/v1.14.0) for more details.
 
-- `unifiLTS`, `unifi5` and `unifi6` have been removed, as they require MongoDB versions which are end-of-life. All these versions can be upgraded to `unifi7` directly.
+- `grafana-loki` has been updated to 3.0.0, which includes [breaking changes](https://github.com/grafana/loki/releases/tag/v3.0.0).
 
-- `mongodb-4_4` has been removed as it has reached end of life. Consequently, `unifi7` and `unifi8` now use MongoDB 5.0 by default.
+- `gtest` package has been updated past v1.13.0, which requires C++14 or higher.
 
-- `mongodb-5_0` and newer requires a cpu with the avx instruction set to run.
+- `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`.
 
-- `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.
+- `himalaya` has been updated to v1.0.0-beta.4, which introduces breaking changes. Check out the [release note](https://github.com/soywod/himalaya/releases/tag/v1.0.0-beta.4) 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.
+- `halloy` has been updated to 2024.5, which introduced a breaking change by switching the config format from YAML to TOML. See https://github.com/squidowl/halloy/releases/tag/2024.5 for details.
 
-- `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 to create a file with the same exact string, and change
-  your module options to point to that file. For example, `services.aria2.rpcSecret =
-  "mysecret"` becomes `services.aria2.rpcSecretFile = "/path/to/secret_file"`
-  where the file `secret_file` contains the string `mysecret`.
+- `hvm` was updated to version 2.
 
-- The `system.forbiddenDependenciesRegex` option has been renamed to `system.forbiddenDependenciesRegexes` and now has the type of `listOf string` instead of `string` to accept multiple regexes.
+- `icu` no longer includes `install-sh` and `mkinstalldirs` in the shared folder.
 
-- `openssh`, `openssh_hpn` and `openssh_gssapi` are now compiled without support for the DSA signature algorithm as it is being deprecated upstream. Users still relying on DSA keys should consider upgrading
-  to another signature algorithm. However, for the time being it is possible to restore DSA key support using `override` to set `dsaKeysSupport = true`.
+- `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.
 
-- `buildGoModule` now throws an error when `vendorHash` is not specified. `vendorSha256`, deprecated in Nixpkgs 23.11, is now ignored and is no longer a `vendorHash` alias.
+- `inetutils` now has a lower priority to avoid shadowing the commonly used `util-linux`. If one wishes to restore the default priority, simply use `lib.setPrio 5 inetutils` or override with `meta.priority = 5`.
 
-- `services.invidious.settings.db.user`, the default database username has changed from `kemal` to `invidious`. Setups involving an externally-provisioned database (i.e. `services.invidious.database.createLocally == false`) should adjust their configuration accordingly. The old `kemal` user will not be removed automatically even when the database is provisioned automatically.(https://github.com/NixOS/nixpkgs/pull/265857).
+- `jdt-language-server` package now uses upstream's provided python wrapper instead of our own custom wrapper. This results in the following breaking and notable changes:
 
-- `writeReferencesToFile` is deprecated in favour of the new trivial build helper `writeClosure`. The latter accepts a list of paths and has an unambiguous name and cleaner implementation.
+  - The main binary for the package is now named `jdtls` instead of `jdt-language-server`, equivalent to what most editors expect the binary to be named.
 
-- `inetutils` now has a lower priority to avoid shadowing the commonly used `util-linux`. If one wishes to restore the default priority, simply use `lib.setPrio 5 inetutils` or override with `meta.priority = 5`.
+  - JVM arguments should now be provided with the `--jvm-arg` flag instead of setting `JAVA_OPTS`.
 
-- `paperless`' `services.paperless.extraConfig` setting has been removed and converted to the free-form type and option named `services.paperless.settings`.
+  - The `-data` path is no longer required to run the package, and will be set to point to a folder in `$TMP` if missing.
 
-- `davfs2`' `services.davfs2.extraConfig` setting has been deprecated and converted to the free-form type option named `services.davfs2.settings` according to RFC42.
+- `julia` environments can now be built with arbitrary packages from the ecosystem using the `.withPackages` function. For example: `julia.withPackages ["Plots"]`.
 
-- `services.homepage-dashboard` now takes its 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.
+- `k3s` has been updated to version [v1.30](https://github.com/k3s-io/k3s/releases/tag/v1.30.0%2Bk3s1), previous supported versions are available under release specific names (e.g. k3s_1_27, k3s_1_28, and k3s_1_29) and present to help you migrate to the latest supported version. See [changelog and upgrade notes](https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.30.md#changelog-since-v1290) for more information.
 
-- `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`.
+- `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.
 
-- `network-interfaces.target` system target was removed as it has been deprecated for a long time. Use `network.target` instead.
+- `kanata` package has been updated to v1.6.1, which includes breaking changes.  Check out the changelog of [v1.5.0](https://github.com/jtroo/kanata/releases/tag/v1.5.0) and [v1.6.0](https://github.com/jtroo/kanata/releases/tag/v1.6.0) for details.
 
-- `services.redis.vmOverCommit` now defaults to `true` and no longer enforces Transparent Hugepages (THP) to be disabled. Redis only works with THP configured to `madvise` which is the kernel's default.
+- `linuxPackages_testing_bcachefs` is now fully deprecated by `linuxPackages_latest`, and is therefore no longer available.
 
-- `azure-cli` now has extension support. For example, to install the `aks-preview` extension, use
+- `livebook` package is now built as a `mix release` instead of an `escript`.
+  This means that configuration now has to be done using [environment variables](https://hexdocs.pm/livebook/readme.html#environment-variables) instead of command line arguments.
+  This has the further implication that the `livebook` service configuration has changed.
 
-  ```nix
-  environment.systemPackages = [
-    (azure-cli.withExtensions [ azure-cli.extensions.aks-preview ])
-  ];
-  ```
-  To make the `azure-cli` immutable and prevent clashes in case `azure-cli` is also installed via other package managers, some configuration files were moved into the derivation.
-  This can be disabled by overriding `withImmutableConfig = false` when building `azure-cli`.
+- `lua` interpreters default LUA_PATH and LUA_CPATH are not overriden by nixpkgs
+  anymore, we patch LUA_ROOT instead which is more respectful to upstream.
 
-- `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.
-  - The `settings.common` section in the configuration is no longer valid and all the options form inside it now goes directly under `settings`.
-  - The `_` separating words in the configuration options is removed so the options are now in camel case. For example: `server_addr` becomes `serverAddr`, `server_port` becomes `serverPort` etc.
-  - Proxies are now defined with a new option `settings.proxies` which takes a list of proxies.
-  - Consult the [upstream documentation](https://github.com/fatedier/frp#example-usage) for more details on the changes.
+- `luarocks-packages-updater`'s .csv format used to define lua packages to be updated, has changed: `src` (URL of a git repository) has now become `rockspec` (URL of a rockspec) to remove ambiguity regarding which rockspec to use and simplify implementation.
 
 - `mkosi` was updated to v22. Parts of the user interface have changed. Consult the
   release notes of [v19](https://github.com/systemd/mkosi/releases/tag/v19),
@@ -346,47 +353,28 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
   [v21](https://github.com/systemd/mkosi/releases/tag/v21) and
   [v22](https://github.com/systemd/mkosi/releases/tag/v22) for a list of changes.
 
-- `gonic` has been updated to v0.16.4. Config now requires `playlists-path` to be set. See the rest of the [v0.16.0 release notes](https://github.com/sentriz/gonic/releases/tag/v0.16.0) for more details.
-
-- `services.vikunja` systemd service now uses `vikunja` as dynamic user instead of `vikunja-api`. Database users might need to be changed.
-
-- `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.
-
-- `services.vmagent` module deprecates `dataDir`, `group` and `user` setting in favor of systemd provided CacheDirectory and DynamicUser.
-
-- `services.vmagent.remoteWriteUrl` setting has been renamed to `services.vmagent.remoteWrite.url` and now defaults to `null`.
-
-- `woodpecker-*` packages have been updated to v2 which includes [breaking changes](https://woodpecker-ci.org/docs/next/migrations#200).
+- `mongodb-4_4` has been removed as it has reached end of life. Consequently, `unifi7` and `unifi8` now use MongoDB 5.0 by default.
 
-- `services.nginx` will no longer advertise HTTP/3 availability automatically. This must now be manually added, preferably to each location block.
-  Example:
+- `mongodb-5_0` and newer requires a cpu with the avx instruction set to run.
 
-  ```nix
-  {
-    locations."/".extraConfig = ''
-      add_header Alt-Svc 'h3=":$server_port"; ma=86400';
-    '';
-    locations."^~ /assets/".extraConfig = ''
-      add_header Alt-Svc 'h3=":$server_port"; ma=86400';
-    '';
-  }
-  ```
+- `neo4j` has been updated to version 5. You may want to read the [release notes for Neo4j 5](https://neo4j.com/release-notes/database/neo4j-5/).
 
-- `optparse-bash` is now dropped due to upstream inactivity. Alternatives available in Nixpkgs include [`argc`](https://github.com/sigoden/argc), [`argbash`](https://github.com/matejak/argbash), [`bashly`](https://github.com/DannyBen/bashly) and [`gum`](https://github.com/charmbracelet/gum), to name a few.
+- `netbox` was updated to v3.7. `services.netbox.package` still defaults
+  to v3.6 if `stateVersion` is earlier than 24.05. Refer to upstream's breaking
+  changes [for
+  v3.7.0](https://github.com/netbox-community/netbox/releases/tag/v3.7.0) and
+  upgrade NetBox by changing `services.netbox.package`. Database migrations
+  will be run automatically.
 
-- `kanata` package has been updated to v1.6.1, which includes breaking changes.  Check out the changelog of [v1.5.0](https://github.com/jtroo/kanata/releases/tag/v1.5.0) and [v1.6.0](https://github.com/jtroo/kanata/releases/tag/v1.6.0) for details.
+- `network-interfaces.target` system target was removed as it has been deprecated for a long time. Use `network.target` instead.
 
-- `craftos-pc` package has been updated to v2.8, which includes [breaking changes](https://github.com/MCJack123/craftos2/releases/tag/v2.8).
-  - Files are now handled in binary mode; this could break programs with embedded UTF-8 characters.
-  - The ROM was updated to match ComputerCraft version v1.109.2.
-  - The bundled Lua was updated to Lua v5.2, which includes breaking changes. See the [Lua manual](https://www.lua.org/manual/5.2/manual.html#8) for more information.
-  - The WebSocket API [was rewritten](https://github.com/MCJack123/craftos2/issues/337), which introduced breaking changes.
+- `networking.iproute2.enable` now does not set `environment.etc."iproute2/rt_tables".text`.
 
-- `gtest` package has been updated past v1.13.0, which requires C++14 or higher.
+  Setting `environment.etc."iproute2/{CONFIG_FILE_NAME}".text` will override the whole configuration file instead of appending it to the upstream configuration file.
 
-- Nextcloud 26 has been removed since it's not maintained anymore by upstream.
+  `CONFIG_FILE_NAME` includes `bpf_pinning`, `ematch_map`, `group`, `nl_protos`, `rt_dsfield`, `rt_protos`, `rt_realms`, `rt_scopes`, and `rt_tables`.
 
-- The latest available version of Nextcloud is v29 (available as `pkgs.nextcloud29`). The installation logic is as follows:
+- `nextcloud26` has been removed since it's not maintained anymore by upstream. The latest available version of Nextcloud is now v29 (available as `pkgs.nextcloud29`). The installation logic is as follows:
   - If [`services.nextcloud.package`](#opt-services.nextcloud.package) is specified explicitly, this package will be installed (**recommended**)
   - If [`system.stateVersion`](#opt-system.stateVersion) is >=24.05, `pkgs.nextcloud29` will be installed by default.
   - If [`system.stateVersion`](#opt-system.stateVersion) is >=23.11, `pkgs.nextcloud27` will be installed by default.
@@ -394,32 +382,82 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
   - Known warnings after the upgrade are documented in [](#module-services-nextcloud-known-warnings) from now on.
   - The "Photos" app only displays Media from inside the `Photos` directory. This can be changed manually in the "Photos" tab below "Photos settings".
 
-- The vendored third party libraries have been mostly removed from `cudaPackages.nsight_systems`, which we now only ship for `cudaPackages_11_8` and later due to outdated dependencies. Users comfortable with the vendored dependencies may use `overrideAttrs` to amend the `postPatch` phase and the `meta.broken` correspondingly. Alternatively, one could package the deprecated `boost170` locally, as required for `cudaPackages_11_4.nsight_systems`.
+- `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.
 
-- `cudaPackages` package scope has been updated to `cudaPackages_12`.
+- `nixVersions.unstable` was removed. Instead the following attributes are provided:
+  - `nixVersions.git` which tracks the latest Nix master and is roughly updated once a week. This is intended to enable people to easily test unreleased changes of Nix to catch regressions earlier.
+  - `nixVersions.latest` which points to the latest Nix version packaged in nixpkgs.
 
-- The deprecated `cudaPackages.cudatoolkit` has been replaced with a
-  symlink-based wrapper for the splayed redistributable CUDA packages. The
-  wrapper only includes tools and libraries necessary to build common packages
-  like e.g. tensorflow. The original runfile-based `cudatoolkit` is still
-  available as `cudatoolkit-legacy-runfile`.
+- `nomad` has been updated - note that HashiCorp recommends updating one minor version at a time. Please check [their upgrade guide](https://developer.hashicorp.com/nomad/docs/upgrade) for information on safely updating clusters and potential breaking changes.
 
-- `halloy` package was updated past 2024.5 which introduced a breaking change by switching the config format from YAML to TOML. See https://github.com/squidowl/halloy/releases/tag/2024.5 for details.
+  - `nomad` is now Nomad 1.7.x.
 
-- If `services.smokeping.webService` was enabled, smokeping is now served via nginx instead of thttpd. This change brings the following consequences:
-  - The default port for smokeping is now the nginx default port 80 instead of 8081.
-  - The option `services.smokeping.port` has been removed. To customize the port, use `services.nginx.virtualHosts.smokeping.listen.*.port`.
+  - `nomad_1_4` has been removed, as it is now unsupported upstream.
 
-- The `wpaperd` package has a breaking change moving to 1.0.1, previous version 0.3.0 had 2 different configuration files, one for wpaperd and one for the wallpapers. Remove the former and move the latter (`wallpaper.toml`) to `config.toml`.
+- `nvtop` family of packages was reorganized into a nested attrset. `nvtop` has been renamed to `nvtopPackages.full`, and all `nvtop-{amd,nvidia,intel,msm}` packages are renamed to `nvtopPackages.{amd,nvidia,intel,msm}`.
 
-- 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.
+- `openssh`, `openssh_hpn` and `openssh_gssapi` are now compiled without support for the DSA signature algorithm as it is being deprecated upstream. Users still relying on DSA keys should consider upgrading
+  to another signature algorithm. However, for the time being it is possible to restore DSA key support using `override` to set `dsaKeysSupport = true`.
 
-- Paths provided as `restartTriggers` and `reloadTriggers` for systemd units will now be copied into the nix store to make the behavior consistent.
-  Previously, `restartTriggers = [ ./config.txt ]`, if defined in a flake, would trigger a restart when any part of the flake changed; and if not defined in a flake, would never trigger a restart even if the contents of `config.txt` changed.
+- `optparse-bash` is now dropped due to upstream inactivity. Alternatives available in Nixpkgs include [`argc`](https://github.com/sigoden/argc), [`argbash`](https://github.com/matejak/argbash), [`bashly`](https://github.com/DannyBen/bashly) and [`gum`](https://github.com/charmbracelet/gum), to name a few.
 
-- `spark2014` has been renamed to `gnatprove`. A version of `gnatprove` matching different GNAT versions is available from the different `gnatPackages` sets.
+- `paperless`' `services.paperless.extraConfig` setting has been removed and converted to the free-form type and option named `services.paperless.settings`.
+
+- `pdns` was updated to version [v4.9.x](https://doc.powerdns.com/authoritative/changelog/4.9.html), which introduces breaking changes. Check out the [Upgrade Notes](https://doc.powerdns.com/authoritative/upgrading.html#to-4-9-0) for details.
+
+- `percona-server` now follows [the same two-fold release cycle](https://www.percona.com/blog/lts-and-innovation-releases-for-percona-server-for-mysql/) as Oracle MySQL and provides a *Long-Term-Support (LTS)* in parallel with a continuous-delivery *Innovation* release. `percona-server` defaults to `percona-server_lts`, will be backed by the same release branch throughout the lifetime of this stable NixOS release, and is still available under the versioned attribute `percona-server_8_0`.
+  The `percona-server_innovation` releases however have support periods shorter than the lifetime of this NixOS release and will continuously be updated to newer Percona releases. Note that Oracle considers the *Innovation* releases to be production-grade, but each release might include backwards-incompatible changes, even in its on-disk format.
+  The same release scheme is applied to the supporting `percona-xtrabackup` tool as well.
+
+- `pipewire` and `wireplumber` modules have removed support for using
+`environment.etc."pipewire/..."` and `environment.etc."wireplumber/..."`.
+Use `services.pipewire.extraConfig` or `services.pipewire.configPackages` for PipeWire and
+`services.pipewire.wireplumber.configPackages` for WirePlumber instead."
+
+- `power.ups` 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.
+
+- `programs.nix-ld.libraries` no longer sets `baseLibraries` via the option's default but in config and now merges any additional libraries with the default ones.
+  This means that `lib.mkForce` must be used to clear the list of default libraries.
+
+- `screen`'s module has been cleaned, and will now require you to set `programs.screen.enable` in order to populate `screenrc` and add the program to the environment.
+
+- `security.pam.enableSSHAgentAuth` now requires `services.openssh.authorizedKeysFiles` to be non-empty,
+  which is the case when `services.openssh.enable` is true. Previously, `pam_ssh_agent_auth` silently failed to work.
 
-- `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.
+- `security.pam.enableSSHAgentAuth` was replaced by the `sshAgentAuth` attrset, and **only**
+  `authorized_keys` files listed in [`sshAgentAuth.authorizedKeysFiles`] are trusted,
+  defaulting to `/etc/ssh/authorized_keys.d/%u`.
+  ::: {.warning}
+  Users of {manpage}`pam_ssh_agent_auth(8)` must take care that the pubkeys they use (for instance with `sudo`)
+  are listed in [`sshAgentAuth.authorizedKeysFiles`].
+  :::
+  ::: {.note}
+  Previously, all `services.openssh.authorizedKeysFiles` were trusted, including `~/.ssh/authorized_keys`,
+  which results in an **insecure** configuration; see [#31611](https://github.com/NixOS/nixpkgs/issues/31611).
+  :::
+
+[`sshAgentAuth.authorizedKeysFiles`]: #opt-security.pam.sshAgentAuth.authorizedKeysFiles
+
+- `services.archisteamfarm` no longer uses the abbreviation `asf` for its state directory (`/var/lib/asf`), user and group (both `asf`). Instead the long name `archisteamfarm` is used.
+  Configurations with `system.stateVersion` 23.11 or earlier, default to the old stateDirectory until the 24.11 release and must either set the option explicitly or move the data to the new directory.
+
+- `frr` was updated to 10.0, which introduces the default of `enforce-first-as` for BGP. Please disable again if needed.
+
+- `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 to create a file with the same exact string, and change
+  your module options to point to that file. For example, `services.aria2.rpcSecret =
+  "mysecret"` becomes `services.aria2.rpcSecretFile = "/path/to/secret_file"`
+  where the file `secret_file` contains the string `mysecret`.
+
+- `services.avahi.nssmdns` was split into `services.avahi.nssmdns4` and `services.avahi.nssmdns6` which enable the mDNS NSS switches for IPv4 and IPv6 respectively.
+  Since most mDNS responders only register IPv4 addresses, most users want to keep the IPv6 support disabled to avoid long timeouts.
+
+- `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:
+  - The `settings.common` section in the configuration is no longer valid and all the options form inside it now go directly under `settings`.
+  - Configuration option names have been changed from snake_case to camelCase. For example: `server_addr` becomes `serverAddr`, `server_port` becomes `serverPort` etc.
+  - Proxies are now defined with a new option `settings.proxies` which takes a list of proxies.
+  - Consult the [upstream documentation](https://github.com/fatedier/frp#example-usage) for more details on the changes.
 
 - `services.hledger-web.capabilities` options has been replaced by a new option `services.hledger-web.allow`.
 
@@ -428,119 +466,135 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
   - `allow = "edit"` means `capabilities = { view = true; add = true; edit = true }`;
   - `allow = "sandstorm"` reads permissions from the `X-Sandstorm-Permissions` request header.
 
-- `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`.
-
-- `stalwart-mail` package has been updated to v0.5.3, which includes [breaking changes](https://github.com/stalwartlabs/mail-server/blob/v0.5.3/UPGRADING.md).
+- `services.homepage-dashboard` now takes its 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.
 
-- `services.zope2` has been removed as `zope2` is unmaintained and was relying on Python2.
+- `services.invidious.settings.db.user`, the default database username has changed from `kemal` to `invidious`. Setups involving an externally-provisioned database (i.e. `services.invidious.database.createLocally == false`) should adjust their configuration accordingly. The old `kemal` user will not be removed automatically even when the database is provisioned automatically.(https://github.com/NixOS/nixpkgs/pull/265857).
 
 - `services.oauth2_proxy` was renamed to `services.oauth2-proxy`. Also the corresponding service, user and group were renamed.
 
-- `services.avahi.nssmdns` got split into `services.avahi.nssmdns4` and `services.avahi.nssmdns6` which enable the mDNS NSS switch for IPv4 and IPv6 respectively.
-  Since most mDNS responders only register IPv4 addresses, most users want to keep the IPv6 support disabled to avoid long timeouts.
+- `services.smokeping` now has an option `webService`. When enabled, smokeping is now served via nginx instead of thttpd. This change brings the following consequences:
+  - The default port for smokeping is now the nginx default port 80 instead of 8081.
+  - The option `services.smokeping.port` has been removed. To customize the port, use `services.nginx.virtualHosts.smokeping.listen.*.port`.
 
-- A warning has been added for services that are
-  `after = [ "network-online.target" ]` but do not depend on it (e.g. using
-  `wants`), because the dependency that `multi-user.target` has on
-  `network-online.target` is planned for removal.
+- `services.neo4j.allowUpgrade` was removed and no longer has any effect. Neo4j 5 supports automatic rolling upgrades.
+
+- `services.nextcloud` has the following options moved into [`services.nextcloud.settings`](#opt-services.nextcloud.settings) and renamed to match the name from Nextcloud's `config.php`:
+  - `logLevel` -> [`loglevel`](#opt-services.nextcloud.settings.loglevel),
+  - `logType` -> [`log_type`](#opt-services.nextcloud.settings.log_type),
+  - `defaultPhoneRegion` -> [`default_phone_region`](#opt-services.nextcloud.settings.default_phone_region),
+  - `overwriteProtocol` -> [`overwriteprotocol`](#opt-services.nextcloud.settings.overwriteprotocol),
+  - `skeletonDirectory` -> [`skeletondirectory`](#opt-services.nextcloud.settings.skeletondirectory),
+  - `globalProfiles` -> [`profile.enabled`](#opt-services.nextcloud.settings._profile.enabled_),
+  - `extraTrustedDomains` -> [`trusted_domains`](#opt-services.nextcloud.settings.trusted_domains) and
+  - `trustedProxies` -> [`trusted_proxies`](#opt-services.nextcloud.settings.trusted_proxies).
+
+- `services.nginx` will no longer advertise HTTP/3 availability automatically. This must now be manually added, preferably to each location block.
+  Example:
+
+  ```nix
+  {
+    locations."/".extraConfig = ''
+      add_header Alt-Svc 'h3=":$server_port"; ma=86400';
+    '';
+    locations."^~ /assets/".extraConfig = ''
+      add_header Alt-Svc 'h3=":$server_port"; ma=86400';
+    '';
+  }
+  ```
 
 - `services.pgbouncer` now has systemd support enabled and will log to journald. The default setting for `services.pgbouncer.logFile` is now `null` to disable logging to a separate log file.
 
-- `services.archisteamfarm` no longer uses the abbreviation `asf` for its state directory (`/var/lib/asf`), user and group (both `asf`). Instead the long name `archisteamfarm` is used.
-  Configurations with `system.stateVersion` 23.11 or earlier, default to the old stateDirectory until the 24.11 release and must either set the option explicitly or move the data to the new directory.
+- `services.postgresql.ensureUsers._.ensurePermissions` has been removed as it is
+  not declarative and is broken with newer postgresql versions. Consider using
+  [](#opt-services.postgresql.ensureUsers._.ensureDBOwnership)
+  instead or a tool that is more suited for managing the data inside a postgresql database.
 
-- `xfsprogs` was updated to version 6.6.0, which enables reverse mapping (rmapbt) and large extent counts (nrext64) by default.
-   Support for these features was added in kernel 4.9 and 5.19 and nrext64 was deemed stable in kernel 6.5.
-   Format your filesystems with `mkfs.xfs -i nrext64=0`, if they need to be readable by GRUB2 before 2.12 or kernels older than 5.19.
+- `services.redis.vmOverCommit` now defaults to `true` and no longer enforces Transparent Hugepages (THP) to be disabled. Redis only works with THP configured to `madvise` which is the kernel's default.
 
-- `networking.iproute2.enable` now does not set `environment.etc."iproute2/rt_tables".text`.
+- `services.resolved.fallbackDns`
+  - can now be used to disable the upstream fallback servers entirely by setting it to `[]`
+  - to get previous behaviour of upstream defaults, set it to `null`
+  - default value has changed from `[]` to `null`, in order to preserve default behaviour
 
-  Setting `environment.etc."iproute2/{CONFIG_FILE_NAME}".text` will override the whole configuration file instead of appending it to the upstream configuration file.
+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.
 
-  `CONFIG_FILE_NAME` includes `bpf_pinning`, `ematch_map`, `group`, `nl_protos`, `rt_dsfield`, `rt_protos`, `rt_realms`, `rt_scopes`, and `rt_tables`.
+- `services.vikunja` systemd service now uses `vikunja` as dynamic user instead of `vikunja-api`. Database users might need to be changed.
 
-- `netbox` was updated to v3.7. `services.netbox.package` still defaults
-  to v3.6 if `stateVersion` is earlier than 24.05. Refer to upstream's breaking
-  changes [for
-  v3.7.0](https://github.com/netbox-community/netbox/releases/tag/v3.7.0) and
-  upgrade NetBox by changing `services.netbox.package`. Database migrations
-  will be run automatically.
+- `services.vikunja.setupNginx` setting has been removed. Users now need to set up the webserver configuration on their own with a proxy pass to the vikunja service.
 
-- `gauge` now supports installing plugins using nix. For the old imperative approach, switch to `gauge-unwrapped`.
-  You can load plugins from an existing gauge manifest file using `gauge.fromManifest ./path/to/manifest.json` or
-  specify plugins in nix using `gauge.withPlugins (p: with p; [ js html-report xml-report ])`.
+- `services.vmagent` module deprecates `dataDir`, `group` and `user` setting in favor of systemd provided CacheDirectory and DynamicUser.
 
-- `firefox-devedition`, `firefox-beta`, `firefox-esr` executable file names for now match their package names, which is consistent with the `firefox-*-bin` packages. The desktop entries are also updated so that you can have multiple editions of firefox in your app launcher.
+- `services.vmagent.remoteWriteUrl` setting has been renamed to `services.vmagent.remoteWrite.url` and now defaults to `null`.
 
-- `chromium` and `ungoogled-chromium` had a long stanging issue regarding Widevine DRM handling in nixpkgs fixed.
-  `chromium` now no longer automatically downloads Widevine when encountering DRM protected content.
-  To be able to play DRM protected content in `chromium` now, you have to explicitly opt-in as originally intended using `chromium.override { enableWideVine = true; }`.
-  This override has been added almost 10 years ago.
+- `services.zope2` has been removed, as `zope2` is unmaintained and was relying on Python 2.
 
-- switch-to-configuration does not directly call systemd-tmpfiles anymore.
-  Instead, the new artificial sysinit-reactivation.target is introduced which
-  allows to restart multiple services that are ordered before sysinit.target
-  and respect the ordering between the services.
+- `spark2014` has been renamed to `gnatprove`. A version of `gnatprove` matching different GNAT versions is available from the different `gnatPackages` sets.
 
-- `systemd.oomd` module behavior is changed as:
+- `stalwart-mail` has been updated to v0.5.3, which includes [breaking changes](https://github.com/stalwartlabs/mail-server/blob/v0.5.3/UPGRADING.md).
+
+- `system.etc.overlay.enable` option was added. If enabled, `/etc` is
+  mounted via an overlayfs instead of being created by a custom perl script.
+
+- `system.forbiddenDependenciesRegex` has been renamed to `system.forbiddenDependenciesRegexes` and now has the type of `listOf string` instead of `string` to accept multiple regexes.
+
+- `systemd.oomd` module behavior has changed:
 
   - Raise ManagedOOMMemoryPressureLimit from 50% to 80%. This should make systemd-oomd kill things less often, and fix issues like [this](https://pagure.io/fedora-workstation/issue/358).
     Reference: [commit](https://src.fedoraproject.org/rpms/systemd/c/806c95e1c70af18f81d499b24cd7acfa4c36ffd6?branch=806c95e1c70af18f81d499b24cd7acfa4c36ffd6).
 
   - Remove swap policy. This helps prevent killing processes when user's swap is small.
 
-  - Expand the memory pressure policy to system.slice, user-.slice, and all user owned slices. Reference: [commit](https://src.fedoraproject.org/rpms/systemd/c/7665e1796f915dedbf8e014f0a78f4f576d609bb).
+  - Expand the memory pressure policy to `system.slice`, `user-.slice`, and all user owned slices. Reference: [commit](https://src.fedoraproject.org/rpms/systemd/c/7665e1796f915dedbf8e014f0a78f4f576d609bb).
 
   - `systemd.oomd.enableUserServices` is renamed to `systemd.oomd.enableUserSlices`.
 
-- `security.pam.enableSSHAgentAuth` now requires `services.openssh.authorizedKeysFiles` to be non-empty,
-  which is the case when `services.openssh.enable` is true. Previously, `pam_ssh_agent_auth` silently failed to work.
-
-- The configuration format for `services.prometheus.exporters.snmp` changed with release 0.23.0.
-  The module now includes an optional config check, that is enabled by default, to make the change obvious before any deployment.
-  More information about the configuration syntax change is available in the [upstream repository](https://github.com/prometheus/snmp_exporter/blob/b75fc6b839ee3f3ccbee68bee55f1ae99555084a/auth-split-migration.md).
-
-- [watchdogd](https://troglobit.com/projects/watchdogd/), a system and process supervisor using watchdog timers. Available as [services.watchdogd](#opt-services.watchdogd.enable).
+- `systemd.sysusers.enable` option was added. If enabled, users and
+  groups are created with systemd-sysusers instead of with a custom perl script.
 
-- `jdt-language-server` package now uses upstream's provided python wrapper instead of our own custom wrapper. This results in the following breaking and notable changes:
+- `teleport` has been upgraded from major version 14 to major version 15.
+  Refer to upstream [upgrade instructions](https://goteleport.com/docs/management/operations/upgrading/)
+  and release notes for [v15](https://goteleport.com/docs/changelog/#1500-013124).
 
-  - The main binary for the package is now named `jdtls` instead of `jdt-language-server`, equivalent to what most editors expect the binary to be named.
+- `unifiLTS`, `unifi5` and `unifi6` have been removed, as they require MongoDB versions which are end-of-life. All these versions can be upgraded to `unifi7` directly.
 
-  - JVM arguments should now be provided with the `--jvm-arg` flag instead of setting `JAVA_OPTS`.
+- `unrar` was updated to v7. See [changelog](https://www.rarlab.com/unrar7notes.htm) for more information.
 
-  - The `-data` path is no longer required to run the package, and will be set to point to a folder in `$TMP` if missing.
+- `virtualisation.docker.enableNvidia` and `virtualisation.podman.enableNvidia` options are deprecated. `hardware.nvidia-container-toolkit.enable` should be used instead. This option will expose GPUs on containers with the `--device` CLI option. This is supported by Docker 25, Podman 3.2.0 and Singularity 4. Any container runtime that supports the CDI specification will take advantage of this feature.
 
-- `nomad` has been updated - note that HashiCorp recommends updating one minor version at a time. Please check [their upgrade guide](https://developer.hashicorp.com/nomad/docs/upgrade) for information on safely updating clusters and potential breaking changes.
+- `virtialisation.incus` now defaults to the newly-added `incus-lts` release (v6.0.x). Users who wish to continue using the non-LTS release will need to set `virtualisation.incus.package = pkgs.incus`. Stable release users are encouraged to stay on the LTS release as non-LTS releases will by default not be backported.
 
-  - `nomad` is now Nomad 1.7.x.
+- `woodpecker-*` packages have been updated to v2 which includes [breaking changes](https://woodpecker-ci.org/docs/next/migrations#200).
 
-  - `nomad_1_4` has been removed, as it is now unsupported upstream.
+- `wpaperd` has been updated to 1.0.1, which has a breaking change: previous version 0.3.0 had 2 different configuration files, one for wpaperd and one for the wallpapers. Remove the former and move the latter (`wallpaper.toml`) to `config.toml`.
 
-- Dwarf Fortress has been updated to version 50, and its derivations continue to menace with spikes of Nix and bash. Version 50 is identical to the version on Steam, but without the paid elements like tilepacks.
-  dfhack and Dwarf Therapist still work, and older versions are still packaged in case you'd like to roll back. Note that DF 50 saves will not be compatible with DF 0.47 and earlier.
-  See [Bay 12 Games](http://www.bay12games.com/dwarves/) for more details on what's new in Dwarf Fortress.
+- `writeReferencesToFile` is deprecated in favour of the new trivial build helper `writeClosure`. The latter accepts a list of paths and has an unambiguous name and cleaner implementation.
 
-  - Running an earlier version can be achieved through an override: `dwarf-fortress-packages.dwarf-fortress-full.override { dfVersion = "0.47.5"; }`
+- `xfsprogs` was updated to version 6.6.0, which enables reverse mapping (rmapbt) and large extent counts (nrext64) by default.
+   Support for these features was added in kernel 4.9 and 5.19 and nrext64 was deemed stable in kernel 6.5.
+   Format your filesystems with `mkfs.xfs -i nrext64=0`, if they need to be readable by GRUB2 before 2.12 or kernels older than 5.19.
 
-  - Ruby plugin support has been disabled in DFHack. Many of the Ruby plugins have been converted to Lua, and support was removed upstream due to frequent crashes.
+- `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`.
 
-- `livebook` package is now built as a `mix release` instead of an `escript`.
-  This means that configuration now has to be done using [environment variables](https://hexdocs.pm/livebook/readme.html#environment-variables) instead of command line arguments.
-  This has the further implication that the `livebook` service configuration has changed:
+- `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.
 
-- `erlang_node_short_name`, `erlang_node_name`, `port` and `options` configuration parameters are gone, and have been replaced with an `environment` parameter.
-    Use the appropriate [environment variables](https://hexdocs.pm/livebook/readme.html#environment-variables) inside `environment` to configure the service instead.
+- 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.
 
-- `akkoma` now requires explicitly setting the base URL for uploaded media (`settings."Pleroma.Upload".base_url`), as well as for the media proxy if enabled (`settings."Media"`).
-  This is recommended to be a separate (sub)domain to the one Akkoma is hosted at.
-  See [here](https://meta.akkoma.dev/t/akkoma-stable-2024-03-securer-i-barely-know-her/681#explicit-upload-and-media-proxy-domains-5) for more details.
+- Paths provided as `restartTriggers` and `reloadTriggers` for systemd units will now be copied into the nix store to make the behavior consistent.
+  Previously, `restartTriggers = [ ./config.txt ]`, if defined in a flake, would trigger a restart when any part of the flake changed; and if not defined in a flake, would never trigger a restart even if the contents of `config.txt` changed.
 
-- `crystal` package has been updated to 1.11.x, which has some breaking changes.
-  Refer to crystal's changelog for more information. ([v1.10](https://github.com/crystal-lang/crystal/blob/master/CHANGELOG.md#1100-2023-10-09), [v1.11](https://github.com/crystal-lang/crystal/blob/master/CHANGELOG.md#1110-2024-01-08))
+- A warning has been added for services that are
+  `after = [ "network-online.target" ]` but do not depend on it (e.g. using
+  `wants`), because the dependency that `multi-user.target` has on
+  `network-online.target` is planned for removal.
 
-- `erlang-ls` package no longer ships the `els_dap` binary as of v0.51.0.
+- switch-to-configuration does not directly call systemd-tmpfiles anymore.
+  Instead, the new artificial sysinit-reactivation.target is introduced which
+  allows to restart multiple services that are ordered before sysinit.target
+  and respect the ordering between the services.
 
-- `icu` no longer includes `install-sh` and `mkinstalldirs` in the shared folder.
+- `services.prometheus.exporters.snmp`'s configuration format changed with release 0.23.0.
+  The module now includes an optional config check, that is enabled by default, to make the change obvious before any deployment.
+  More information about the configuration syntax change is available in the [upstream repository](https://github.com/prometheus/snmp_exporter/blob/b75fc6b839ee3f3ccbee68bee55f1ae99555084a/auth-split-migration.md).
 
 ## Other Notable Changes {#sec-release-24.05-notable-changes}
 
@@ -548,146 +602,106 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
 
 - `addDriverRunpath` has been added to facilitate the deprecation of the old `addOpenGLRunpath` setuphook. This change is motivated by the evolution of the setuphook to include all hardware acceleration.
 
-- `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 and could potentially [affect Xorg sessions](https://blog.linuxmint.com/?p=4639). We suggest a reboot when switching between sessions.
-
-- `mate` has been updated to 1.28.
-  - To properly support panel plugins built with Wayland (in-process) support, we are introducing `services.xserver.desktopManager.mate.extraPanelApplets` option, please use that for installing panel applets.
-  - Similarly, please use `services.xserver.desktopManager.mate.extraCajaExtensions` option for installing Caja extensions.
-  - To use the Wayland session, enable `services.xserver.desktopManager.mate.enableWaylandSession`. This is opt-in for now as it is in early stage and introduces a new set of Wayfire closure. Due to [known issues with LightDM](https://github.com/canonical/lightdm/issues/63), we suggest using SDDM for display manager.
-
-- `services.xserver.desktopManager.budgie` installs `gnome.gnome-terminal` by default (instead of `mate.mate-terminal`).
-
-- 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 ];``;
+- `appimage`, `appimageTools.wrapAppImage` and `buildFHSEnvBubblewrap` now properly accept `pname` and `version`.
 
-- `services.openssh` now has an option `authorizedKeysInHomedir`, controlling whether `~/.ssh/authorizedKeys` is
-  added to `authorizedKeysFiles`.
-  ::: {.note}
-  This option currently defaults to `true` for NixOS 24.05, preserving the previous behaviour.
-  This is expected to change in NixOS 24.11.
-  :::
-  ::: {.warning}
-  Users should check that their SSH keys are in `users.users.*.openssh`, or that they have another way to access
-  and administer the system, before setting this option to `false`.
-  :::
-
-- [`matrix-synapse`](https://element-hq.github.io/synapse/) homeserver module now supports configuring UNIX domain socket [`listeners`](#opt-services.matrix-synapse.settings.listeners) through the `path` option.
-  The default replication worker on the main instance has been migrated away from TCP sockets to UNIX domain sockets.
+- `bacula` now allows to configure `TLS` for encrypted communication.
 
 - `boot.initrd.network.ssh.authorizedKeyFiles` is a new option in the initrd ssh daemon module, for adding authorized keys via list of files.
 
-- `appimage`, `appimageTools.wrapAppImage` and `buildFHSEnvBubblewrap` now properly accepts `pname` and `version`.
+- `boot.kernel.sysctl."net.core.wmem_max"` changed from a string to an integer because of the addition of a custom merge option (taking the highest value defined to avoid conflicts between 2 services trying to set that value), just as `boot.kernel.sysctl."net.core.rmem_max"` since 22.11.
 
-- Programs written in [Nim](https://nim-lang.org/) are built with libraries selected by lockfiles.
-  The `nimPackages` and `nim2Packages` sets have been removed.
-  See https://nixos.org/manual/nixpkgs/unstable#nim for more information.
+- `boot.loader.systemd-boot.xbootldrMountPoint` is a new option for 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.
 
 - `buildDubPackage` can now be used to build Programs written in [D](https://dlang.org/) using the `dub` build system and package manager.
   See the [D section](https://nixos.org/manual/nixpkgs/unstable#dlang) in the manual for more information.
 
-- [`portunus`](https://github.com/majewsky/portunus) has been updated to major version 2.
-  This version of Portunus supports strong password hashes, but the legacy hash SHA-256 is also still supported to ensure a smooth migration of existing user accounts.
-  After upgrading, follow the instructions on the [upstream release notes](https://github.com/majewsky/portunus/releases/tag/v2.0.0) to upgrade all user accounts to strong password hashes.
-  Support for weak password hashes will be removed in NixOS 24.11.
+- `castopod` has some migration actions to be taken in case of a S3 setup. Some new features may also need some manual migration actions. See [https://code.castopod.org/adaures/castopod/-/releases](https://code.castopod.org/adaures/castopod/-/releases) for more information.
 
-- A stdenv's default set of hardening flags can now be set via its `bintools-wrapper`'s `defaultHardeningFlags` argument. A convenient stdenv adapter, `withDefaultHardeningFlags`, can be used to override an existing stdenv's `defaultHardeningFlags`.
+- `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 and could potentially [affect Xorg sessions](https://blog.linuxmint.com/?p=4639). We suggest a reboot when switching between sessions.
 
-- `libass` now uses the native CoreText backend on Darwin, which may fix subtitle rendering issues with `mpv`, `ffmpeg`, etc.
+- `documentation.man.mandoc` now uses `MANPATH` by defaultwas to set the directories where mandoc will search for manual pages.
+  This enables mandoc to find manual pages in Nix profiles. To set the manual search paths via the `mandoc.conf` configuration file like before, use `documentation.man.mandoc.settings.manpath` instead.
 
-- [`lilypond`](https://lilypond.org/index.html) and [`denemo`](https://www.denemo.org) are now compiled with Guile 3.0.
+- `drbd` out-of-tree Linux kernel driver has been added in version 9.2.7. With it the DRBD 9.x features can be used instead of the 8.x features provided by the 8.4.11 in-tree driver.
 
 - `garage` has been updated to v1.x.x. Users should read the [upstream release notes](https://git.deuxfleurs.fr/Deuxfleurs/garage/releases/tag/v1.0.0) and follow the documentation when changing over their `services.garage.package` and performing this manual upgrade.
 
-- The EC2 image module now enables the [Amazon SSM Agent](https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-agent.html) by default.
-
-- The following options of the Nextcloud module were moved into [`services.nextcloud.settings`](#opt-services.nextcloud.settings) and renamed to match the name from Nextcloud's `config.php`:
-  - `logLevel` -> [`loglevel`](#opt-services.nextcloud.settings.loglevel),
-  - `logType` -> [`log_type`](#opt-services.nextcloud.settings.log_type),
-  - `defaultPhoneRegion` -> [`default_phone_region`](#opt-services.nextcloud.settings.default_phone_region),
-  - `overwriteProtocol` -> [`overwriteprotocol`](#opt-services.nextcloud.settings.overwriteprotocol),
-  - `skeletonDirectory` -> [`skeletondirectory`](#opt-services.nextcloud.settings.skeletondirectory),
-  - `globalProfiles` -> [`profile.enabled`](#opt-services.nextcloud.settings._profile.enabled_),
-  - `extraTrustedDomains` -> [`trusted_domains`](#opt-services.nextcloud.settings.trusted_domains) and
-  - `trustedProxies` -> [`trusted_proxies`](#opt-services.nextcloud.settings.trusted_proxies).
+- `hardware.pulseaudio` module now sets permission of pulse user home directory to 755 when running in "systemWide" mode. It fixes [issue 114399](https://github.com/NixOS/nixpkgs/issues/114399).
 
-- `services.nextcloud.config.dbport` option of the Nextcloud module was removed to match upstream.
-  The port can be specified in [`services.nextcloud.config.dbhost`](#opt-services.nextcloud.config.dbhost).
+- `kavita` has been updated to 0.8.0, requiring a manual forced library scan on all libraries for migration. Refer to upstream's [release notes](https://github.com/Kareadita/Kavita/releases/tag/v0.8.0) for details.
 
-- A new abstraction to create both read-only as well as writable overlay file
-  systems was added. Available via
-  [fileSystems.overlay](#opt-fileSystems._name_.overlay.lowerdir). See also the
-  [NixOS docs](#sec-overlayfs).
+- `krb5` module has been rewritten and moved to `security.krb5`, moving all options but `security.krb5.enable` and `security.krb5.package` into `security.krb5.settings`.
 
-- `systemd` units can now specify the `Upholds=` and `UpheldBy=` unit dependencies via the aptly
-  named `upholds` and `upheldBy` options. These options get systemd to enforce that the
-  dependencies remain continuosly running for as long as the dependent unit is in a running state.
+- `libass` now uses the native CoreText backend on Darwin, which may fix subtitle rendering issues with `mpv`, `ffmpeg`, etc.
 
-- `stdenv`: The `--replace` flag in `substitute`, `substituteInPlace`, `substituteAll`, `substituteAllStream`, and `substituteStream` is now deprecated if favor of the new `--replace-fail`, `--replace-warn` and `--replace-quiet`. The deprecated `--replace` equates to `--replace-warn`.
+- `libjxl` version bumped from 0.8.2 to 0.9.1 [dropped support for the butteraugli API](https://github.com/libjxl/libjxl/pull/2576). You will no longer be able to set `enableButteraugli` on `libaom`.
 
-- A new hardening flag, `zerocallusedregs` was made available, corresponding to the gcc/clang option `-fzero-call-used-regs=used-gpr`.
+- [`lilypond`](https://lilypond.org/index.html) and [`denemo`](https://www.denemo.org) are now compiled with Guile 3.0.
 
-- A new hardening flag, `trivialautovarinit` was made available, corresponding to the gcc/clang option `-ftrivial-auto-var-init=pattern`.
+- `lxd` has been upgraded to v5.21.x, an LTS release. The LTS release is now the only supported LXD release. Users are encouraged to [migrate to Incus](https://linuxcontainers.org/incus/docs/main/howto/server_migrate_lxd/) for better support on NixOS.
 
-- 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.
+- [`matrix-synapse`](https://element-hq.github.io/synapse/) homeserver module now supports configuring UNIX domain socket [`listeners`](#opt-services.matrix-synapse.settings.listeners) through the `path` option.
+  The default replication worker on the main instance has been migrated away from TCP sockets to UNIX domain sockets.
 
-- `sonarr` version bumped to from 3.0.10 to 4.0.3. Consequently existing config database files will be upgraded automatically, but note that some old apparently-working configs [might actually be corrupt and fail to upgrade cleanly](https://forums.sonarr.tv/t/sonarr-v4-released/33089).
+- `mockgen` has changed to the [go.uber.org/mock](https://github.com/uber-go/mock) fork because [the original repository is no longer maintained](https://github.com/golang/mock#gomock).
 
-- The kernel Yama LSM is now enabled by default, which prevents ptracing
-  non-child processes. This means you will not be able to attach gdb to an
-  existing process, but will need to start that process from gdb (so it is a
-  child). Or you can set `boot.kernel.sysctl."kernel.yama.ptrace_scope"` to 0.
+- `mpich` now requires `withPm` to be a list, e.g. `"hydra:gforker"` becomes `[ "hydra" "gforker" ]`.
 
-- `netbird` module now allows running multiple tunnels in parallel through [`services.netbird.tunnels`](#opt-services.netbird.tunnels).
+- `nextcloud-setup.service` no longer changes the group of each file & directory inside `/var/lib/nextcloud/{config,data,store-apps}` if one of these directories has the wrong owner group. This was part of transitioning the group used for `/var/lib/nextcloud`, but isn't necessary anymore.
 
-- [Nginx virtual hosts](#opt-services.nginx.virtualHosts) using `forceSSL` or
-  `globalRedirect` can now have redirect codes other than 301 through `redirectCode`.
+- `oils-for-unix`, the oil shell's c++ version is now available. The python version is still available as `oil`.
 
-- `bacula` now allows to configure `TLS` for encrypted communication.
+- `pkgsExtraHardening`, a new top-level package set, was added. This is a set of packages built with stricter hardening flags - those that have not yet received enough testing to be applied universally, those that are more likely to cause build failures or those that have drawbacks to their use (e.g. performance or required hardware features).
 
-- `libjxl` version bumped from 0.8.2 to 0.9.1 [dropped support for the butteraugli API](https://github.com/libjxl/libjxl/pull/2576). You will no longer be able to set `enableButteraugli` on `libaom`.
+- [`portunus`](https://github.com/majewsky/portunus) has been updated to major version 2.
+  This version of Portunus supports strong password hashes, but the legacy hash SHA-256 is also still supported to ensure a smooth migration of existing user accounts.
+  After upgrading, follow the instructions on the [upstream release notes](https://github.com/majewsky/portunus/releases/tag/v2.0.0) to upgrade all user accounts to strong password hashes.
+  Support for weak password hashes will be removed in NixOS 24.11.
 
-- `mockgen` package source has changed to the [go.uber.org/mock](https://github.com/uber-go/mock) fork because [the original repository is no longer maintained](https://github.com/golang/mock#gomock).
+- `programs.fish.package` now allows you to override the package used in the `fish` module.
 
-- [](#opt-boot.kernel.sysctl._net.core.wmem_max_) changed from a string to an integer because of the addition of a custom merge option (taking the highest value defined to avoid conflicts between 2 services trying to set that value), just as [](#opt-boot.kernel.sysctl._net.core.rmem_max_) since 22.11.
+- `qt6.qtmultimedia` has changed its default backend to `QT_MEDIA_BACKEND=ffmpeg` (previously `gstreamer` on Linux or `darwin` on macOS).
+  The previous native backends remain available but are now minimally maintained. Refer to [upstream documentation](https://doc.qt.io/qt-6/qtmultimedia-index.html#ffmpeg-as-the-default-backend) for further details about each platform.
 
-- `pkgsExtraHardening`, a new top-level package set, was added. This is a set of packages built with stricter hardening flags - those that have not yet received enough testing to be applied universally, those that are more likely to cause build failures or those that have drawbacks to their use (e.g. performance or required hardware features).
+- `services.btrbk` now automatically selects and provides required compression
+  program depending on the configured `stream_compress` option. Since this
+  replaces the need for the `extraPackages` option, this option will be
+  deprecated in future releases.
 
-- `services.zfs.zed.enableMail` now uses the global `sendmail` wrapper defined by an email module
-  (such as msmtp or Postfix). It no longer requires using a special ZFS build with email support.
+- `services.github-runner` module has been removed. To configure a single GitHub Actions Runner refer to `services.github-runners.*`. Note that this will trigger a new runner registration.
 
-- `castopod` has some migration actions to be taken in case of a S3 setup. Some new features may also need some manual migration actions. See [https://code.castopod.org/adaures/castopod/-/releases](https://code.castopod.org/adaures/castopod/-/releases) for more informations.
+- `services.networkmanager.extraConfig` was renamed to `services.networkmanager.settings` and changed to use the ini type instead of using a multiline string.
 
-- `nextcloud-setup.service` no longer changes the group of each file & directory inside `/var/lib/nextcloud/{config,data,store-apps}` if one of these directories has the wrong owner group. This was part of transitioning the group used for `/var/lib/nextcloud`, but isn't necessary anymore.
+- `services.nextcloud.config.dbport` option of the Nextcloud module was removed to match upstream.
+  The port can be specified in [`services.nextcloud.config.dbhost`](#opt-services.nextcloud.config.dbhost).
 
 - `services.kavita` now uses the free-form option `services.kavita.settings` for the application settings file.
   The options `services.kavita.ipAdresses` and `services.kavita.port` now exist at `services.kavita.settings.IpAddresses`
   and `services.kavita.settings.IpAddresses`. The file at `services.kavita.tokenKeyFile` now needs to contain a secret with
   512+ bits instead of 128+ bits.
 
-- `kavita` has been updated to 0.8.0, requiring a manual forced library scan on all libraries for migration. Refer to upstream's [release notes](https://github.com/Kareadita/Kavita/releases/tag/v0.8.0) for details.
-
-- `krb5` module has been rewritten and moved to `security.krb5`, moving all options but `security.krb5.enable` and `security.krb5.package` into `security.krb5.settings`.
+- `services.netbird` now allows running multiple tunnels in parallel through [`services.netbird.tunnels`](#opt-services.netbird.tunnels).
 
-- `services.soju` now has a wrapper for the `sojuctl` command, pointed at the service config file. It also has the new option `adminSocket.enable`, which creates a unix admin socket at `/run/soju/admin`.
+- `services.nginx.virtualHosts` using `forceSSL` or
+  `globalRedirect` can now have redirect codes other than 301 through `redirectCode`.
 
-- `gitea` upgrade to 1.21 has several breaking changes, including:
-  - Custom themes and other assets that were previously stored in `custom/public/*` now belong in `custom/public/assets/*`
-  - New instances of Gitea using MySQL now ignore the `[database].CHARSET` config option and always use the `utf8mb4` charset, existing instances should migrate via the `gitea doctor convert` CLI command.
+- `services.openssh` now has an option `authorizedKeysInHomedir`, controlling whether `~/.ssh/authorizedKeys` is
+  added to `authorizedKeysFiles`.
+  ::: {.note}
+  This option currently defaults to `true` for NixOS 24.05, preserving the previous behaviour.
+  This is expected to change in NixOS 24.11.
+  :::
+  ::: {.warning}
+  Users should check that their SSH keys are in `users.users.*.openssh`, or that they have another way to access
+  and administer the system, before setting this option to `false`.
+  :::
 
 - `services.paperless` module no longer uses the previously downloaded NLTK data stored in `/var/cache/paperless/nltk`. This directory can be removed.
 
-- `services.teeworlds` module now has a wealth of configuration options, including a new `package` option.
-
-- `hardware.pulseaudio` module now sets permission of pulse user home directory to 755 when running in "systemWide" mode. It fixes [issue 114399](https://github.com/NixOS/nixpkgs/issues/114399).
-
-- `services.networkmanager.extraConfig` was renamed to `services.networkmanager.settings` and was changed to use the ini type instead of using a multiline string.
-
-- `services.github-runner` module has been removed. To configure a single GitHub Actions Runner refer to `services.github-runners.*`. Note that this will trigger a new runner registration.
+- `services.postgresql.extraPlugins`' type has expanded. Previously it was a list of packages, now it can also be 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 ];``;
 
 - `services.slskd` has been refactored to include more configuation options in
   the free-form `services.slskd.settings` option, and some defaults (including listen ports)
@@ -695,34 +709,55 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
   disabled by default, and the log rotation timer has been removed.
   The nginx virtualhost option is now of the `vhost-options` type.
 
-- `services.btrbk` now automatically selects and provides required compression
-  program depending on the configured `stream_compress` option. Since this
-  replaces the need for the `extraPackages` option, this option will be
-  deprecated in future releases.
+- `services.soju` now has a wrapper for the `sojuctl` command, pointed at the service config file. It also has the new option `adminSocket.enable`, which creates a unix admin socket at `/run/soju/admin`.
+
+- `services.stalwart-mail` uses the legacy version 0.6.X as default because newer `stalwart-mail` versions require a [manual upgrade process](https://github.com/stalwartlabs/mail-server/blob/main/UPGRADING.md). Change [`services.stalwart-mail.package`](#opt-services.stalwart-mail.package) to `pkgs.stalwart-mail` if you wish to switch to the new version.
+
+- `services.teeworlds` module now has a wealth of configuration options, including a new `package` option.
+
+- `services.xserver.desktopManager.budgie` installs `gnome.gnome-terminal` by default (instead of `mate.mate-terminal`).
 
-- `mpich` package expression now requires `withPm` to be a list, e.g. `"hydra:gforker"` becomes `[ "hydra" "gforker" ]`.
+- `services.zfs.zed.enableMail` now uses the global `sendmail` wrapper defined by an email module
+  (such as msmtp or Postfix). It no longer requires using a special ZFS build with email support.
+
+- `sonarr` version bumped to from 3.0.10 to 4.0.3. Consequently existing config database files will be upgraded automatically, but note that some old apparently-working configs [might actually be corrupt and fail to upgrade cleanly](https://forums.sonarr.tv/t/sonarr-v4-released/33089).
+
+- `stdenv`: The `--replace` flag in `substitute`, `substituteInPlace`, `substituteAll`, `substituteAllStream`, and `substituteStream` is now deprecated if favor of the new `--replace-fail`, `--replace-warn` and `--replace-quiet`. The deprecated `--replace` equates to `--replace-warn`.
 
 - `systemd`: when merging 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.
 
-- Lisp modules: previously deprecated interface based on `common-lisp.sh` has now been removed.
+- `systemd` units can now specify the `Upholds=` and `UpheldBy=` unit dependencies via the aptly
+  named `upholds` and `upheldBy` options. These options get systemd to enforce that the
+  dependencies remain continuosly running for as long as the dependent unit is in a running state.
 
-- `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.
+- A stdenv's default set of hardening flags can now be set via its `bintools-wrapper`'s `defaultHardeningFlags` argument. A convenient stdenv adapter, `withDefaultHardeningFlags`, can be used to override an existing stdenv's `defaultHardeningFlags`.
 
-- `qt6.qtmultimedia` has changed its default backend to `QT_MEDIA_BACKEND=ffmpeg` (previously `gstreamer` on Linux or `darwin` on MacOS).
-  The previous native backends remain available but are now minimally maintained. Refer to [upstream documentation](https://doc.qt.io/qt-6/qtmultimedia-index.html#ffmpeg-as-the-default-backend) for further details about each platform.
+- Programs written in [Nim](https://nim-lang.org/) are built with libraries selected by lockfiles.
+  The `nimPackages` and `nim2Packages` sets have been removed.
+  See https://nixos.org/manual/nixpkgs/unstable#nim for more information.
 
-- `drbd` out-of-tree Linux kernel driver has been added in version 9.2.7. With it the DRBD 9.x features can be used instead of the 8.x features provided by the 8.4.11 in-tree driver.
+- The EC2 image module now enables the [Amazon SSM Agent](https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-agent.html) by default.
 
-- `oils-for-unix`, the oil shell's c++ version is now available. The python version is still available as `oil`.
+- A new abstraction to create both read-only as well as writable overlay file
+  systems was added. Available via
+  [fileSystems.overlay](#opt-fileSystems._name_.overlay.lowerdir). See also the
+  [NixOS docs](#sec-overlayfs).
 
-- `documentation.man.mandoc` now by default uses `MANPATH` to set the directories where mandoc will search for manual pages.
-  This enables mandoc to find manual pages in Nix profiles. To set the manual search paths via the `mandoc.conf` configuration file like before, use `documentation.man.mandoc.settings.manpath` instead.
+- A new hardening flag, `zerocallusedregs` was made available, corresponding to the gcc/clang option `-fzero-call-used-regs=used-gpr`.
 
-- The `systemd-confinement` module extension is now compatible with `DynamicUser=true` and thus `ProtectSystem=strict` too.
+- A new hardening flag, `trivialautovarinit` was made available, corresponding to the gcc/clang option `-ftrivial-auto-var-init=pattern`.
 
-- `grafana-loki` package was updated to 3.0.0 which includes [breaking changes](https://github.com/grafana/loki/releases/tag/v3.0.0).
+- `dnsdist` has new options to enable and configure a DNSCrypt endpoint (see `services.dnsdist.dnscrypt.enable`, etc.).
+  The module can generate the DNSCrypt provider key pair and certificates, and also rotates them automatically with no downtime.
 
-- `programs.fish.package` now allows you to override the package used in the `fish` module.
+- The kernel Yama LSM is now enabled by default, which prevents ptracing
+  non-child processes. This means you will not be able to attach gdb to an
+  existing process, but will need to start that process from gdb (so it is a
+  child). Or you can set `boot.kernel.sysctl."kernel.yama.ptrace_scope"` to 0.
+
+- Lisp modules: previously deprecated interface based on `common-lisp.sh` has now been removed.
+
+- The `systemd-confinement` module extension is now compatible with `DynamicUser=true` and thus `ProtectSystem=strict` too.
diff --git a/nixos/doc/manual/release-notes/rl-2411.section.md b/nixos/doc/manual/release-notes/rl-2411.section.md
new file mode 100644
index 0000000000000..343c1fc5933d9
--- /dev/null
+++ b/nixos/doc/manual/release-notes/rl-2411.section.md
@@ -0,0 +1,28 @@
+# Release 24.11 (“Vicuña”, 2024.11/??) {#sec-release-24.11}
+
+<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
+
+## Highlights {#sec-release-24.11-highlights}
+
+- Create the first release note entry in this section!
+
+## New Services {#sec-release-24.11-new-services}
+
+- Create the first release note entry in this section!
+
+## Backward Incompatibilities {#sec-release-24.11-incompatibilities}
+
+- `nvimpager` was updated to version 0.13.0, which changes the order of user and
+  nvimpager settings: user commands in `-c` and `--cmd` now override the
+  respective default settings because they are executed later.
+
+- Legacy package `stalwart-mail_0_6` was dropped, please note the
+  [manual upgrade process](https://github.com/stalwartlabs/mail-server/blob/main/UPGRADING.md)
+  before changing the package to `pkgs.stalwart-mail` in
+  [`services.stalwart-mail.package`](#opt-services.stalwart-mail.package).
+
+## Other Notable Changes {#sec-release-24.11-notable-changes}
+
+- Create the first release note entry in this section!
+
+<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
diff --git a/nixos/lib/test-driver/default.nix b/nixos/lib/test-driver/default.nix
index 1acdaacc4e658..7a88694b3167e 100644
--- a/nixos/lib/test-driver/default.nix
+++ b/nixos/lib/test-driver/default.nix
@@ -24,6 +24,7 @@ python3Packages.buildPythonApplication {
     coreutils
     netpbm
     python3Packages.colorama
+    python3Packages.junit-xml
     python3Packages.ptpython
     qemu_pkg
     socat
@@ -46,7 +47,7 @@ python3Packages.buildPythonApplication {
     echo -e "\x1b[32m## run mypy\x1b[0m"
     mypy test_driver extract-docstrings.py
     echo -e "\x1b[32m## run ruff\x1b[0m"
-    ruff .
+    ruff check .
     echo -e "\x1b[32m## run black\x1b[0m"
     black --check --diff .
   '';
diff --git a/nixos/lib/test-driver/pyproject.toml b/nixos/lib/test-driver/pyproject.toml
index 17b7130a4bad7..714139bc1b25c 100644
--- a/nixos/lib/test-driver/pyproject.toml
+++ b/nixos/lib/test-driver/pyproject.toml
@@ -19,8 +19,8 @@ test_driver = ["py.typed"]
 [tool.ruff]
 line-length = 88
 
-select = ["E", "F", "I", "U", "N"]
-ignore = ["E501"]
+lint.select = ["E", "F", "I", "U", "N"]
+lint.ignore = ["E501"]
 
 # xxx: we can import https://pypi.org/project/types-colorama/ here
 [[tool.mypy.overrides]]
@@ -31,6 +31,10 @@ ignore_missing_imports = true
 module = "ptpython.*"
 ignore_missing_imports = true
 
+[[tool.mypy.overrides]]
+module = "junit_xml.*"
+ignore_missing_imports = true
+
 [tool.black]
 line-length = 88
 target-version = ['py39']
diff --git a/nixos/lib/test-driver/test_driver/__init__.py b/nixos/lib/test-driver/test_driver/__init__.py
index 9daae1e941a65..42b6d29b76714 100755
--- a/nixos/lib/test-driver/test_driver/__init__.py
+++ b/nixos/lib/test-driver/test_driver/__init__.py
@@ -6,7 +6,12 @@ from pathlib import Path
 import ptpython.repl
 
 from test_driver.driver import Driver
-from test_driver.logger import rootlog
+from test_driver.logger import (
+    CompositeLogger,
+    JunitXMLLogger,
+    TerminalLogger,
+    XMLLogger,
+)
 
 
 class EnvDefault(argparse.Action):
@@ -93,6 +98,11 @@ def main() -> None:
         type=writeable_dir,
     )
     arg_parser.add_argument(
+        "--junit-xml",
+        help="Enable JunitXML report generation to the given path",
+        type=Path,
+    )
+    arg_parser.add_argument(
         "testscript",
         action=EnvDefault,
         envvar="testScript",
@@ -102,14 +112,24 @@ def main() -> None:
 
     args = arg_parser.parse_args()
 
+    output_directory = args.output_directory.resolve()
+    logger = CompositeLogger([TerminalLogger()])
+
+    if "LOGFILE" in os.environ.keys():
+        logger.add_logger(XMLLogger(os.environ["LOGFILE"]))
+
+    if args.junit_xml:
+        logger.add_logger(JunitXMLLogger(output_directory / args.junit_xml))
+
     if not args.keep_vm_state:
-        rootlog.info("Machine state will be reset. To keep it, pass --keep-vm-state")
+        logger.info("Machine state will be reset. To keep it, pass --keep-vm-state")
 
     with Driver(
         args.start_scripts,
         args.vlans,
         args.testscript.read_text(),
-        args.output_directory.resolve(),
+        output_directory,
+        logger,
         args.keep_vm_state,
         args.global_timeout,
     ) as driver:
@@ -125,7 +145,7 @@ def main() -> None:
             tic = time.time()
             driver.run_tests()
             toc = time.time()
-            rootlog.info(f"test script finished in {(toc-tic):.2f}s")
+            logger.info(f"test script finished in {(toc-tic):.2f}s")
 
 
 def generate_driver_symbols() -> None:
@@ -134,7 +154,7 @@ def generate_driver_symbols() -> None:
     in user's test scripts. That list is then used by pyflakes to lint those
     scripts.
     """
-    d = Driver([], [], "", Path())
+    d = Driver([], [], "", Path(), CompositeLogger([]))
     test_symbols = d.test_symbols()
     with open("driver-symbols", "w") as fp:
         fp.write(",".join(test_symbols.keys()))
diff --git a/nixos/lib/test-driver/test_driver/driver.py b/nixos/lib/test-driver/test_driver/driver.py
index f792c04591996..10092fc966c8f 100644
--- a/nixos/lib/test-driver/test_driver/driver.py
+++ b/nixos/lib/test-driver/test_driver/driver.py
@@ -9,7 +9,7 @@ from typing import Any, Callable, ContextManager, Dict, Iterator, List, Optional
 
 from colorama import Fore, Style
 
-from test_driver.logger import rootlog
+from test_driver.logger import AbstractLogger
 from test_driver.machine import Machine, NixStartScript, retry
 from test_driver.polling_condition import PollingCondition
 from test_driver.vlan import VLan
@@ -49,6 +49,7 @@ class Driver:
     polling_conditions: List[PollingCondition]
     global_timeout: int
     race_timer: threading.Timer
+    logger: AbstractLogger
 
     def __init__(
         self,
@@ -56,6 +57,7 @@ class Driver:
         vlans: List[int],
         tests: str,
         out_dir: Path,
+        logger: AbstractLogger,
         keep_vm_state: bool = False,
         global_timeout: int = 24 * 60 * 60 * 7,
     ):
@@ -63,12 +65,13 @@ class Driver:
         self.out_dir = out_dir
         self.global_timeout = global_timeout
         self.race_timer = threading.Timer(global_timeout, self.terminate_test)
+        self.logger = logger
 
         tmp_dir = get_tmp_dir()
 
-        with rootlog.nested("start all VLans"):
+        with self.logger.nested("start all VLans"):
             vlans = list(set(vlans))
-            self.vlans = [VLan(nr, tmp_dir) for nr in vlans]
+            self.vlans = [VLan(nr, tmp_dir, self.logger) for nr in vlans]
 
         def cmd(scripts: List[str]) -> Iterator[NixStartScript]:
             for s in scripts:
@@ -84,6 +87,7 @@ class Driver:
                 tmp_dir=tmp_dir,
                 callbacks=[self.check_polling_conditions],
                 out_dir=self.out_dir,
+                logger=self.logger,
             )
             for cmd in cmd(start_scripts)
         ]
@@ -92,19 +96,19 @@ class Driver:
         return self
 
     def __exit__(self, *_: Any) -> None:
-        with rootlog.nested("cleanup"):
+        with self.logger.nested("cleanup"):
             self.race_timer.cancel()
             for machine in self.machines:
                 machine.release()
 
     def subtest(self, name: str) -> Iterator[None]:
         """Group logs under a given test name"""
-        with rootlog.nested("subtest: " + name):
+        with self.logger.subtest(name):
             try:
                 yield
                 return True
             except Exception as e:
-                rootlog.error(f'Test "{name}" failed with error: "{e}"')
+                self.logger.error(f'Test "{name}" failed with error: "{e}"')
                 raise e
 
     def test_symbols(self) -> Dict[str, Any]:
@@ -118,7 +122,7 @@ class Driver:
             machines=self.machines,
             vlans=self.vlans,
             driver=self,
-            log=rootlog,
+            log=self.logger,
             os=os,
             create_machine=self.create_machine,
             subtest=subtest,
@@ -150,13 +154,13 @@ class Driver:
 
     def test_script(self) -> None:
         """Run the test script"""
-        with rootlog.nested("run the VM test script"):
+        with self.logger.nested("run the VM test script"):
             symbols = self.test_symbols()  # call eagerly
             exec(self.tests, symbols, None)
 
     def run_tests(self) -> None:
         """Run the test script (for non-interactive test runs)"""
-        rootlog.info(
+        self.logger.info(
             f"Test will time out and terminate in {self.global_timeout} seconds"
         )
         self.race_timer.start()
@@ -168,13 +172,13 @@ class Driver:
 
     def start_all(self) -> None:
         """Start all machines"""
-        with rootlog.nested("start all VMs"):
+        with self.logger.nested("start all VMs"):
             for machine in self.machines:
                 machine.start()
 
     def join_all(self) -> None:
         """Wait for all machines to shut down"""
-        with rootlog.nested("wait for all VMs to finish"):
+        with self.logger.nested("wait for all VMs to finish"):
             for machine in self.machines:
                 machine.wait_for_shutdown()
             self.race_timer.cancel()
@@ -182,7 +186,7 @@ class Driver:
     def terminate_test(self) -> None:
         # This will be usually running in another thread than
         # the thread actually executing the test script.
-        with rootlog.nested("timeout reached; test terminating..."):
+        with self.logger.nested("timeout reached; test terminating..."):
             for machine in self.machines:
                 machine.release()
             # As we cannot `sys.exit` from another thread
@@ -227,7 +231,7 @@ class Driver:
                     f"Unsupported arguments passed to create_machine: {args}"
                 )
 
-            rootlog.warning(
+            self.logger.warning(
                 Fore.YELLOW
                 + Style.BRIGHT
                 + "WARNING: Using create_machine with a single dictionary argument is deprecated and will be removed in NixOS 24.11"
@@ -246,13 +250,14 @@ class Driver:
             start_command=cmd,
             name=name,
             keep_vm_state=keep_vm_state,
+            logger=self.logger,
         )
 
     def serial_stdout_on(self) -> None:
-        rootlog._print_serial_logs = True
+        self.logger.print_serial_logs(True)
 
     def serial_stdout_off(self) -> None:
-        rootlog._print_serial_logs = False
+        self.logger.print_serial_logs(False)
 
     def check_polling_conditions(self) -> None:
         for condition in self.polling_conditions:
@@ -271,6 +276,7 @@ class Driver:
             def __init__(self, fun: Callable):
                 self.condition = PollingCondition(
                     fun,
+                    driver.logger,
                     seconds_interval,
                     description,
                 )
@@ -285,15 +291,17 @@ class Driver:
             def wait(self, timeout: int = 900) -> None:
                 def condition(last: bool) -> bool:
                     if last:
-                        rootlog.info(f"Last chance for {self.condition.description}")
+                        driver.logger.info(
+                            f"Last chance for {self.condition.description}"
+                        )
                     ret = self.condition.check(force=True)
                     if not ret and not last:
-                        rootlog.info(
+                        driver.logger.info(
                             f"({self.condition.description} failure not fatal yet)"
                         )
                     return ret
 
-                with rootlog.nested(f"waiting for {self.condition.description}"):
+                with driver.logger.nested(f"waiting for {self.condition.description}"):
                     retry(condition, timeout=timeout)
 
         if fun_ is None:
diff --git a/nixos/lib/test-driver/test_driver/logger.py b/nixos/lib/test-driver/test_driver/logger.py
index 0b0623bddfa1e..484829254b812 100644
--- a/nixos/lib/test-driver/test_driver/logger.py
+++ b/nixos/lib/test-driver/test_driver/logger.py
@@ -1,33 +1,238 @@
+import atexit
 import codecs
 import os
 import sys
 import time
 import unicodedata
-from contextlib import contextmanager
+from abc import ABC, abstractmethod
+from contextlib import ExitStack, contextmanager
+from pathlib import Path
 from queue import Empty, Queue
-from typing import Any, Dict, Iterator
+from typing import Any, Dict, Iterator, List
 from xml.sax.saxutils import XMLGenerator
 from xml.sax.xmlreader import AttributesImpl
 
 from colorama import Fore, Style
+from junit_xml import TestCase, TestSuite
 
 
-class Logger:
-    def __init__(self) -> None:
-        self.logfile = os.environ.get("LOGFILE", "/dev/null")
-        self.logfile_handle = codecs.open(self.logfile, "wb")
-        self.xml = XMLGenerator(self.logfile_handle, encoding="utf-8")
-        self.queue: "Queue[Dict[str, str]]" = Queue()
+class AbstractLogger(ABC):
+    @abstractmethod
+    def log(self, message: str, attributes: Dict[str, str] = {}) -> None:
+        pass
 
-        self.xml.startDocument()
-        self.xml.startElement("logfile", attrs=AttributesImpl({}))
+    @abstractmethod
+    @contextmanager
+    def subtest(self, name: str, attributes: Dict[str, str] = {}) -> Iterator[None]:
+        pass
+
+    @abstractmethod
+    @contextmanager
+    def nested(self, message: str, attributes: Dict[str, str] = {}) -> Iterator[None]:
+        pass
+
+    @abstractmethod
+    def info(self, *args, **kwargs) -> None:  # type: ignore
+        pass
+
+    @abstractmethod
+    def warning(self, *args, **kwargs) -> None:  # type: ignore
+        pass
+
+    @abstractmethod
+    def error(self, *args, **kwargs) -> None:  # type: ignore
+        pass
+
+    @abstractmethod
+    def log_serial(self, message: str, machine: str) -> None:
+        pass
+
+    @abstractmethod
+    def print_serial_logs(self, enable: bool) -> None:
+        pass
+
+
+class JunitXMLLogger(AbstractLogger):
+    class TestCaseState:
+        def __init__(self) -> None:
+            self.stdout = ""
+            self.stderr = ""
+            self.failure = False
+
+    def __init__(self, outfile: Path) -> None:
+        self.tests: dict[str, JunitXMLLogger.TestCaseState] = {
+            "main": self.TestCaseState()
+        }
+        self.currentSubtest = "main"
+        self.outfile: Path = outfile
+        self._print_serial_logs = True
+        atexit.register(self.close)
+
+    def log(self, message: str, attributes: Dict[str, str] = {}) -> None:
+        self.tests[self.currentSubtest].stdout += message + os.linesep
+
+    @contextmanager
+    def subtest(self, name: str, attributes: Dict[str, str] = {}) -> Iterator[None]:
+        old_test = self.currentSubtest
+        self.tests.setdefault(name, self.TestCaseState())
+        self.currentSubtest = name
+
+        yield
+
+        self.currentSubtest = old_test
+
+    @contextmanager
+    def nested(self, message: str, attributes: Dict[str, str] = {}) -> Iterator[None]:
+        self.log(message)
+        yield
+
+    def info(self, *args, **kwargs) -> None:  # type: ignore
+        self.tests[self.currentSubtest].stdout += args[0] + os.linesep
+
+    def warning(self, *args, **kwargs) -> None:  # type: ignore
+        self.tests[self.currentSubtest].stdout += args[0] + os.linesep
+
+    def error(self, *args, **kwargs) -> None:  # type: ignore
+        self.tests[self.currentSubtest].stderr += args[0] + os.linesep
+        self.tests[self.currentSubtest].failure = True
+
+    def log_serial(self, message: str, machine: str) -> None:
+        if not self._print_serial_logs:
+            return
+
+        self.log(f"{machine} # {message}")
+
+    def print_serial_logs(self, enable: bool) -> None:
+        self._print_serial_logs = enable
+
+    def close(self) -> None:
+        with open(self.outfile, "w") as f:
+            test_cases = []
+            for name, test_case_state in self.tests.items():
+                tc = TestCase(
+                    name,
+                    stdout=test_case_state.stdout,
+                    stderr=test_case_state.stderr,
+                )
+                if test_case_state.failure:
+                    tc.add_failure_info("test case failed")
+
+                test_cases.append(tc)
+            ts = TestSuite("NixOS integration test", test_cases)
+            f.write(TestSuite.to_xml_string([ts]))
+
+
+class CompositeLogger(AbstractLogger):
+    def __init__(self, logger_list: List[AbstractLogger]) -> None:
+        self.logger_list = logger_list
+
+    def add_logger(self, logger: AbstractLogger) -> None:
+        self.logger_list.append(logger)
+
+    def log(self, message: str, attributes: Dict[str, str] = {}) -> None:
+        for logger in self.logger_list:
+            logger.log(message, attributes)
+
+    @contextmanager
+    def subtest(self, name: str, attributes: Dict[str, str] = {}) -> Iterator[None]:
+        with ExitStack() as stack:
+            for logger in self.logger_list:
+                stack.enter_context(logger.subtest(name, attributes))
+            yield
+
+    @contextmanager
+    def nested(self, message: str, attributes: Dict[str, str] = {}) -> Iterator[None]:
+        with ExitStack() as stack:
+            for logger in self.logger_list:
+                stack.enter_context(logger.nested(message, attributes))
+            yield
+
+    def info(self, *args, **kwargs) -> None:  # type: ignore
+        for logger in self.logger_list:
+            logger.info(*args, **kwargs)
+
+    def warning(self, *args, **kwargs) -> None:  # type: ignore
+        for logger in self.logger_list:
+            logger.warning(*args, **kwargs)
+
+    def error(self, *args, **kwargs) -> None:  # type: ignore
+        for logger in self.logger_list:
+            logger.error(*args, **kwargs)
+        sys.exit(1)
 
+    def print_serial_logs(self, enable: bool) -> None:
+        for logger in self.logger_list:
+            logger.print_serial_logs(enable)
+
+    def log_serial(self, message: str, machine: str) -> None:
+        for logger in self.logger_list:
+            logger.log_serial(message, machine)
+
+
+class TerminalLogger(AbstractLogger):
+    def __init__(self) -> None:
         self._print_serial_logs = True
 
+    def maybe_prefix(self, message: str, attributes: Dict[str, str]) -> str:
+        if "machine" in attributes:
+            return f"{attributes['machine']}: {message}"
+        return message
+
     @staticmethod
     def _eprint(*args: object, **kwargs: Any) -> None:
         print(*args, file=sys.stderr, **kwargs)
 
+    def log(self, message: str, attributes: Dict[str, str] = {}) -> None:
+        self._eprint(self.maybe_prefix(message, attributes))
+
+    @contextmanager
+    def subtest(self, name: str, attributes: Dict[str, str] = {}) -> Iterator[None]:
+        with self.nested("subtest: " + name, attributes):
+            yield
+
+    @contextmanager
+    def nested(self, message: str, attributes: Dict[str, str] = {}) -> Iterator[None]:
+        self._eprint(
+            self.maybe_prefix(
+                Style.BRIGHT + Fore.GREEN + message + Style.RESET_ALL, attributes
+            )
+        )
+
+        tic = time.time()
+        yield
+        toc = time.time()
+        self.log(f"(finished: {message}, in {toc - tic:.2f} seconds)")
+
+    def info(self, *args, **kwargs) -> None:  # type: ignore
+        self.log(*args, **kwargs)
+
+    def warning(self, *args, **kwargs) -> None:  # type: ignore
+        self.log(*args, **kwargs)
+
+    def error(self, *args, **kwargs) -> None:  # type: ignore
+        self.log(*args, **kwargs)
+
+    def print_serial_logs(self, enable: bool) -> None:
+        self._print_serial_logs = enable
+
+    def log_serial(self, message: str, machine: str) -> None:
+        if not self._print_serial_logs:
+            return
+
+        self._eprint(Style.DIM + f"{machine} # {message}" + Style.RESET_ALL)
+
+
+class XMLLogger(AbstractLogger):
+    def __init__(self, outfile: str) -> None:
+        self.logfile_handle = codecs.open(outfile, "wb")
+        self.xml = XMLGenerator(self.logfile_handle, encoding="utf-8")
+        self.queue: Queue[dict[str, str]] = Queue()
+
+        self._print_serial_logs = True
+
+        self.xml.startDocument()
+        self.xml.startElement("logfile", attrs=AttributesImpl({}))
+
     def close(self) -> None:
         self.xml.endElement("logfile")
         self.xml.endDocument()
@@ -54,17 +259,19 @@ class Logger:
 
     def error(self, *args, **kwargs) -> None:  # type: ignore
         self.log(*args, **kwargs)
-        sys.exit(1)
 
     def log(self, message: str, attributes: Dict[str, str] = {}) -> None:
-        self._eprint(self.maybe_prefix(message, attributes))
         self.drain_log_queue()
         self.log_line(message, attributes)
 
+    def print_serial_logs(self, enable: bool) -> None:
+        self._print_serial_logs = enable
+
     def log_serial(self, message: str, machine: str) -> None:
+        if not self._print_serial_logs:
+            return
+
         self.enqueue({"msg": message, "machine": machine, "type": "serial"})
-        if self._print_serial_logs:
-            self._eprint(Style.DIM + f"{machine} # {message}" + Style.RESET_ALL)
 
     def enqueue(self, item: Dict[str, str]) -> None:
         self.queue.put(item)
@@ -80,13 +287,12 @@ class Logger:
             pass
 
     @contextmanager
-    def nested(self, message: str, attributes: Dict[str, str] = {}) -> Iterator[None]:
-        self._eprint(
-            self.maybe_prefix(
-                Style.BRIGHT + Fore.GREEN + message + Style.RESET_ALL, attributes
-            )
-        )
+    def subtest(self, name: str, attributes: Dict[str, str] = {}) -> Iterator[None]:
+        with self.nested("subtest: " + name, attributes):
+            yield
 
+    @contextmanager
+    def nested(self, message: str, attributes: Dict[str, str] = {}) -> Iterator[None]:
         self.xml.startElement("nest", attrs=AttributesImpl({}))
         self.xml.startElement("head", attrs=AttributesImpl(attributes))
         self.xml.characters(message)
@@ -100,6 +306,3 @@ class Logger:
         self.log(f"(finished: {message}, in {toc - tic:.2f} seconds)")
 
         self.xml.endElement("nest")
-
-
-rootlog = Logger()
diff --git a/nixos/lib/test-driver/test_driver/machine.py b/nixos/lib/test-driver/test_driver/machine.py
index 652cc600fad59..3a1d5bc1be764 100644
--- a/nixos/lib/test-driver/test_driver/machine.py
+++ b/nixos/lib/test-driver/test_driver/machine.py
@@ -17,7 +17,7 @@ from pathlib import Path
 from queue import Queue
 from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple
 
-from test_driver.logger import rootlog
+from test_driver.logger import AbstractLogger
 
 from .qmp import QMPSession
 
@@ -270,6 +270,7 @@ class Machine:
         out_dir: Path,
         tmp_dir: Path,
         start_command: StartCommand,
+        logger: AbstractLogger,
         name: str = "machine",
         keep_vm_state: bool = False,
         callbacks: Optional[List[Callable]] = None,
@@ -280,6 +281,7 @@ class Machine:
         self.name = name
         self.start_command = start_command
         self.callbacks = callbacks if callbacks is not None else []
+        self.logger = logger
 
         # set up directories
         self.shared_dir = self.tmp_dir / "shared-xchg"
@@ -307,15 +309,15 @@ class Machine:
         return self.booted and self.connected
 
     def log(self, msg: str) -> None:
-        rootlog.log(msg, {"machine": self.name})
+        self.logger.log(msg, {"machine": self.name})
 
     def log_serial(self, msg: str) -> None:
-        rootlog.log_serial(msg, self.name)
+        self.logger.log_serial(msg, self.name)
 
     def nested(self, msg: str, attrs: Dict[str, str] = {}) -> _GeneratorContextManager:
         my_attrs = {"machine": self.name}
         my_attrs.update(attrs)
-        return rootlog.nested(msg, my_attrs)
+        return self.logger.nested(msg, my_attrs)
 
     def wait_for_monitor_prompt(self) -> str:
         assert self.monitor is not None
@@ -1113,8 +1115,8 @@ class Machine:
 
     def cleanup_statedir(self) -> None:
         shutil.rmtree(self.state_dir)
-        rootlog.log(f"deleting VM state directory {self.state_dir}")
-        rootlog.log("if you want to keep the VM state, pass --keep-vm-state")
+        self.logger.log(f"deleting VM state directory {self.state_dir}")
+        self.logger.log("if you want to keep the VM state, pass --keep-vm-state")
 
     def shutdown(self) -> None:
         """
@@ -1221,7 +1223,7 @@ class Machine:
     def release(self) -> None:
         if self.pid is None:
             return
-        rootlog.info(f"kill machine (pid {self.pid})")
+        self.logger.info(f"kill machine (pid {self.pid})")
         assert self.process
         assert self.shell
         assert self.monitor
diff --git a/nixos/lib/test-driver/test_driver/polling_condition.py b/nixos/lib/test-driver/test_driver/polling_condition.py
index 12cbad69e34e9..1cccaf2c71e74 100644
--- a/nixos/lib/test-driver/test_driver/polling_condition.py
+++ b/nixos/lib/test-driver/test_driver/polling_condition.py
@@ -2,7 +2,7 @@ import time
 from math import isfinite
 from typing import Callable, Optional
 
-from .logger import rootlog
+from test_driver.logger import AbstractLogger
 
 
 class PollingConditionError(Exception):
@@ -13,6 +13,7 @@ class PollingCondition:
     condition: Callable[[], bool]
     seconds_interval: float
     description: Optional[str]
+    logger: AbstractLogger
 
     last_called: float
     entry_count: int
@@ -20,11 +21,13 @@ class PollingCondition:
     def __init__(
         self,
         condition: Callable[[], Optional[bool]],
+        logger: AbstractLogger,
         seconds_interval: float = 2.0,
         description: Optional[str] = None,
     ):
         self.condition = condition  # type: ignore
         self.seconds_interval = seconds_interval
+        self.logger = logger
 
         if description is None:
             if condition.__doc__:
@@ -41,7 +44,7 @@ class PollingCondition:
         if (self.entered or not self.overdue) and not force:
             return True
 
-        with self, rootlog.nested(self.nested_message):
+        with self, self.logger.nested(self.nested_message):
             time_since_last = time.monotonic() - self.last_called
             last_message = (
                 f"Time since last: {time_since_last:.2f}s"
@@ -49,13 +52,13 @@ class PollingCondition:
                 else "(not called yet)"
             )
 
-            rootlog.info(last_message)
+            self.logger.info(last_message)
             try:
                 res = self.condition()  # type: ignore
             except Exception:
                 res = False
             res = res is None or res
-            rootlog.info(self.status_message(res))
+            self.logger.info(self.status_message(res))
             return res
 
     def maybe_raise(self) -> None:
diff --git a/nixos/lib/test-driver/test_driver/vlan.py b/nixos/lib/test-driver/test_driver/vlan.py
index ec9679108e58d..9340fc92ed4c4 100644
--- a/nixos/lib/test-driver/test_driver/vlan.py
+++ b/nixos/lib/test-driver/test_driver/vlan.py
@@ -4,7 +4,7 @@ import pty
 import subprocess
 from pathlib import Path
 
-from test_driver.logger import rootlog
+from test_driver.logger import AbstractLogger
 
 
 class VLan:
@@ -19,17 +19,20 @@ class VLan:
     pid: int
     fd: io.TextIOBase
 
+    logger: AbstractLogger
+
     def __repr__(self) -> str:
         return f"<Vlan Nr. {self.nr}>"
 
-    def __init__(self, nr: int, tmp_dir: Path):
+    def __init__(self, nr: int, tmp_dir: Path, logger: AbstractLogger):
         self.nr = nr
         self.socket_dir = tmp_dir / f"vde{self.nr}.ctl"
+        self.logger = logger
 
         # TODO: don't side-effect environment here
         os.environ[f"QEMU_VDE_SOCKET_{self.nr}"] = str(self.socket_dir)
 
-        rootlog.info("start vlan")
+        self.logger.info("start vlan")
         pty_master, pty_slave = pty.openpty()
 
         # The --hub is required for the scenario determined by
@@ -52,11 +55,11 @@ class VLan:
         assert self.process.stdout is not None
         self.process.stdout.readline()
         if not (self.socket_dir / "ctl").exists():
-            rootlog.error("cannot start vde_switch")
+            self.logger.error("cannot start vde_switch")
 
-        rootlog.info(f"running vlan (pid {self.pid}; ctl {self.socket_dir})")
+        self.logger.info(f"running vlan (pid {self.pid}; ctl {self.socket_dir})")
 
     def __del__(self) -> None:
-        rootlog.info(f"kill vlan (pid {self.pid})")
+        self.logger.info(f"kill vlan (pid {self.pid})")
         self.fd.close()
         self.process.terminate()
diff --git a/nixos/lib/test-script-prepend.py b/nixos/lib/test-script-prepend.py
index 976992ea00158..9d2efdf973031 100644
--- a/nixos/lib/test-script-prepend.py
+++ b/nixos/lib/test-script-prepend.py
@@ -4,7 +4,7 @@
 from test_driver.driver import Driver
 from test_driver.vlan import VLan
 from test_driver.machine import Machine
-from test_driver.logger import Logger
+from test_driver.logger import AbstractLogger
 from typing import Callable, Iterator, ContextManager, Optional, List, Dict, Any, Union
 from typing_extensions import Protocol
 from pathlib import Path
@@ -44,7 +44,7 @@ test_script: Callable[[], None]
 machines: List[Machine]
 vlans: List[VLan]
 driver: Driver
-log: Logger
+log: AbstractLogger
 create_machine: CreateMachineProtocol
 run_tests: Callable[[], None]
 join_all: Callable[[], None]
diff --git a/nixos/lib/testing/driver.nix b/nixos/lib/testing/driver.nix
index 7eb06e023918b..d4f8e0f0c6e38 100644
--- a/nixos/lib/testing/driver.nix
+++ b/nixos/lib/testing/driver.nix
@@ -139,7 +139,7 @@ in
     enableOCR = mkOption {
       description = ''
         Whether to enable Optical Character Recognition functionality for
-        testing graphical programs. See [Machine objects](`ssec-machine-objects`).
+        testing graphical programs. See [`Machine objects`](#ssec-machine-objects).
       '';
       type = types.bool;
       default = false;
diff --git a/nixos/maintainers/scripts/ec2/README.md b/nixos/maintainers/scripts/ec2/README.md
index 1328109d464a3..eb2c9088d5a2c 100644
--- a/nixos/maintainers/scripts/ec2/README.md
+++ b/nixos/maintainers/scripts/ec2/README.md
@@ -1,7 +1,36 @@
 # Amazon images
 
-* The `create-amis.sh` script will be replaced by https://github.com/NixOS/amis which will regularly upload AMIs per NixOS channel bump.
+AMIs are regularly uploaded from Hydra. This automation lives in
+https://github.com/NixOS/amis
 
-* @arianvp is planning to drop zfs support
-* @arianvp is planning to rewrite the image builder to use the repart-based image builder.
 
+## How to upload an AMI for testing
+
+If you want to upload an AMI from changes in a local nixpkgs checkout.
+
+```bash
+nix-build nixos/release.nix -A amazonImage
+
+export AWS_REGION=us-west-2
+export AWS_PROFILE=my-profile
+nix run nixpkgs#upload-ami -- --image-info ./result/nix-support/image-info.json
+```
+
+## How to build your own NixOS config into an AMI
+
+I suggest looking at https://github.com/nix-community/nixos-generators for a user-friendly interface.
+
+```bash
+nixos-generate -c ./my-config.nix -f amazon
+
+export AWS_REGION=us-west-2
+export AWS_PROFILE=my-profile
+nix run github:NixOS/amis#upload-ami -- --image-info ./result/nix-support/image-info.json
+```
+
+## Roadmap
+
+* @arianvp is planning to drop zfs support unless someone else picks it up
+* @arianvp is planning to rewrite the image builder to use the repart-based image builder.
+* @arianvp is planning to perhaps rewrite `upload-ami` to use coldnsap
+* @arianvp is planning to move `upload-ami` tooling into nixpkgs once it has stabilized. And only keep the Github Action in separate repo
diff --git a/nixos/maintainers/scripts/ec2/create-amis.sh b/nixos/maintainers/scripts/ec2/create-amis.sh
deleted file mode 100755
index d182c5c2a4794..0000000000000
--- a/nixos/maintainers/scripts/ec2/create-amis.sh
+++ /dev/null
@@ -1,368 +0,0 @@
-#!/usr/bin/env nix-shell
-#!nix-shell -p awscli -p jq -p qemu -i bash
-# shellcheck shell=bash
-#
-# Future Deprecation?
-# This entire thing should probably be replaced with a generic terraform config
-
-# Uploads and registers NixOS images built from the
-# <nixos/release.nix> amazonImage attribute. Images are uploaded and
-# registered via a home region, and then copied to other regions.
-
-# The home region requires an s3 bucket, and an IAM role named "vmimport"
-# (by default) with access to the S3 bucket. The name can be
-# configured with the "service_role_name" variable. Configuration of the
-# vmimport role is documented in
-# https://docs.aws.amazon.com/vm-import/latest/userguide/vmimport-image-import.html
-
-# set -x
-set -euo pipefail
-
-var () { true; }
-
-# configuration
-var ${state_dir:=$HOME/amis/ec2-images}
-var ${home_region:=eu-west-1}
-var ${bucket:=nixos-amis}
-var ${service_role_name:=vmimport}
-
-# Output of the command:
-# $ nix-shell -I nixpkgs=. -p awscli --run 'aws ec2 describe-regions --region us-east-1 --all-regions --query "Regions[].{Name:RegionName}" --output text | sort | sed -e s/^/\ \ /'
-var ${regions:=
-  af-south-1
-  ap-east-1
-  ap-northeast-1
-  ap-northeast-2
-  ap-northeast-3
-  ap-south-1
-  ap-south-2
-  ap-southeast-1
-  ap-southeast-2
-  ap-southeast-3
-  ap-southeast-4
-  ca-central-1
-  eu-central-1
-  eu-central-2
-  eu-north-1
-  eu-south-1
-  eu-south-2
-  eu-west-1
-  eu-west-2
-  eu-west-3
-  il-central-1
-  me-central-1
-  me-south-1
-  sa-east-1
-  us-east-1
-  us-east-2
-  us-west-1
-  us-west-2
-}
-
-regions=($regions)
-
-log() {
-    echo "$@" >&2
-}
-
-if [ "$#" -ne 1 ]; then
-    log "Usage: ./upload-amazon-image.sh IMAGE_OUTPUT"
-    exit 1
-fi
-
-# result of the amazon-image from nixos/release.nix
-store_path=$1
-
-if [ ! -e "$store_path" ]; then
-    log "Store path: $store_path does not exist, fetching..."
-    nix-store --realise "$store_path"
-fi
-
-if [ ! -d "$store_path" ]; then
-    log "store_path: $store_path is not a directory. aborting"
-    exit 1
-fi
-
-read_image_info() {
-    if [ ! -e "$store_path/nix-support/image-info.json" ]; then
-        log "Image missing metadata"
-        exit 1
-    fi
-    jq -r "$1" "$store_path/nix-support/image-info.json"
-}
-
-# We handle a single image per invocation, store all attributes in
-# globals for convenience.
-zfs_disks=$(read_image_info .disks)
-is_zfs_image=
-if jq -e .boot <<< "$zfs_disks"; then
-  is_zfs_image=1
-  zfs_boot=".disks.boot"
-fi
-image_label="$(read_image_info .label)${is_zfs_image:+-ZFS}"
-image_system=$(read_image_info .system)
-image_files=( $(read_image_info ".disks.root.file") )
-
-image_logical_bytes=$(read_image_info "${zfs_boot:-.disks.root}.logical_bytes")
-
-if [[ -n "$is_zfs_image" ]]; then
-  image_files+=( $(read_image_info .disks.boot.file) )
-fi
-
-# Derived attributes
-
-image_logical_gigabytes=$(((image_logical_bytes-1)/1024/1024/1024+1)) # Round to the next GB
-
-case "$image_system" in
-    aarch64-linux)
-        amazon_arch=arm64
-        ;;
-    x86_64-linux)
-        amazon_arch=x86_64
-        ;;
-    *)
-        log "Unknown system: $image_system"
-        exit 1
-esac
-
-image_name="NixOS-${image_label}-${image_system}"
-image_description="NixOS ${image_label} ${image_system}"
-
-log "Image Details:"
-log " Name: $image_name"
-log " Description: $image_description"
-log " Size (gigabytes): $image_logical_gigabytes"
-log " System: $image_system"
-log " Amazon Arch: $amazon_arch"
-
-read_state() {
-    local state_key=$1
-    local type=$2
-
-    cat "$state_dir/$state_key.$type" 2>/dev/null || true
-}
-
-write_state() {
-    local state_key=$1
-    local type=$2
-    local val=$3
-
-    mkdir -p "$state_dir"
-    echo "$val" > "$state_dir/$state_key.$type"
-}
-
-wait_for_import() {
-    local region=$1
-    local task_id=$2
-    local state snapshot_id
-    log "Waiting for import task $task_id to be completed"
-    while true; do
-        read -r state message snapshot_id < <(
-            aws ec2 describe-import-snapshot-tasks --region "$region" --import-task-ids "$task_id" | \
-                jq -r '.ImportSnapshotTasks[].SnapshotTaskDetail | "\(.Status) \(.StatusMessage) \(.SnapshotId)"'
-        )
-        log " ... state=$state message=$message snapshot_id=$snapshot_id"
-        case "$state" in
-            active)
-                sleep 10
-                ;;
-            completed)
-                echo "$snapshot_id"
-                return
-                ;;
-            *)
-                log "Unexpected snapshot import state: '${state}'"
-                log "Full response: "
-                aws ec2 describe-import-snapshot-tasks --region "$region" --import-task-ids "$task_id" >&2
-                exit 1
-                ;;
-        esac
-    done
-}
-
-wait_for_image() {
-    local region=$1
-    local ami_id=$2
-    local state
-    log "Waiting for image $ami_id to be available"
-
-    while true; do
-        read -r state < <(
-            aws ec2 describe-images --image-ids "$ami_id" --region "$region" | \
-                jq -r ".Images[].State"
-        )
-        log " ... state=$state"
-        case "$state" in
-            pending)
-                sleep 10
-                ;;
-            available)
-                return
-                ;;
-            *)
-                log "Unexpected AMI state: '${state}'"
-                exit 1
-                ;;
-        esac
-    done
-}
-
-
-make_image_public() {
-    local region=$1
-    local ami_id=$2
-
-    wait_for_image "$region" "$ami_id"
-
-    log "Making image $ami_id public"
-
-    aws ec2 modify-image-attribute \
-        --image-id "$ami_id" --region "$region" --launch-permission 'Add={Group=all}' >&2
-}
-
-upload_image() {
-    local region=$1
-
-    for image_file in "${image_files[@]}"; do
-        local aws_path=${image_file#/}
-
-        if [[ -n "$is_zfs_image" ]]; then
-            local suffix=${image_file%.*}
-            suffix=${suffix##*.}
-        fi
-
-        local state_key="$region.$image_label${suffix:+.${suffix}}.$image_system"
-        local task_id
-        task_id=$(read_state "$state_key" task_id)
-        local snapshot_id
-        snapshot_id=$(read_state "$state_key" snapshot_id)
-        local ami_id
-        ami_id=$(read_state "$state_key" ami_id)
-
-        if [ -z "$task_id" ]; then
-            log "Checking for image on S3"
-            if ! aws s3 ls --region "$region" "s3://${bucket}/${aws_path}" >&2; then
-                log "Image missing from aws, uploading"
-                aws s3 cp --region "$region" "$image_file" "s3://${bucket}/${aws_path}" >&2
-            fi
-
-            log "Importing image from S3 path s3://$bucket/$aws_path"
-
-            task_id=$(aws ec2 import-snapshot --role-name "$service_role_name" --disk-container "{
-              \"Description\": \"nixos-image-${image_label}-${image_system}\",
-              \"Format\": \"vhd\",
-              \"UserBucket\": {
-                  \"S3Bucket\": \"$bucket\",
-                  \"S3Key\": \"$aws_path\"
-              }
-            }" --region "$region" | jq -r '.ImportTaskId')
-
-            write_state "$state_key" task_id "$task_id"
-        fi
-
-        if [ -z "$snapshot_id" ]; then
-            snapshot_id=$(wait_for_import "$region" "$task_id")
-            write_state "$state_key" snapshot_id "$snapshot_id"
-        fi
-    done
-
-    if [ -z "$ami_id" ]; then
-        log "Registering snapshot $snapshot_id as AMI"
-
-        local block_device_mappings=(
-            "DeviceName=/dev/xvda,Ebs={SnapshotId=$snapshot_id,VolumeSize=$image_logical_gigabytes,DeleteOnTermination=true,VolumeType=gp3}"
-        )
-
-        if [[ -n "$is_zfs_image" ]]; then
-            local root_snapshot_id=$(read_state "$region.$image_label.root.$image_system" snapshot_id)
-
-            local root_image_logical_bytes=$(read_image_info ".disks.root.logical_bytes")
-            local root_image_logical_gigabytes=$(((root_image_logical_bytes-1)/1024/1024/1024+1)) # Round to the next GB
-
-            block_device_mappings+=(
-                "DeviceName=/dev/xvdb,Ebs={SnapshotId=$root_snapshot_id,VolumeSize=$root_image_logical_gigabytes,DeleteOnTermination=true,VolumeType=gp3}"
-            )
-        fi
-
-
-        local extra_flags=(
-            --root-device-name /dev/xvda
-            --sriov-net-support simple
-            --ena-support
-            --virtualization-type hvm
-        )
-
-        block_device_mappings+=("DeviceName=/dev/sdb,VirtualName=ephemeral0")
-        block_device_mappings+=("DeviceName=/dev/sdc,VirtualName=ephemeral1")
-        block_device_mappings+=("DeviceName=/dev/sdd,VirtualName=ephemeral2")
-        block_device_mappings+=("DeviceName=/dev/sde,VirtualName=ephemeral3")
-
-        ami_id=$(
-            aws ec2 register-image \
-                --name "$image_name" \
-                --description "$image_description" \
-                --region "$region" \
-                --architecture $amazon_arch \
-                --block-device-mappings "${block_device_mappings[@]}" \
-                --boot-mode $(read_image_info .boot_mode) \
-                "${extra_flags[@]}" \
-                | jq -r '.ImageId'
-              )
-
-        write_state "$state_key" ami_id "$ami_id"
-    fi
-
-    [[ -v PRIVATE ]] || make_image_public "$region" "$ami_id"
-
-    echo "$ami_id"
-}
-
-copy_to_region() {
-    local region=$1
-    local from_region=$2
-    local from_ami_id=$3
-
-    state_key="$region.$image_label.$image_system"
-    ami_id=$(read_state "$state_key" ami_id)
-
-    if [ -z "$ami_id" ]; then
-        log "Copying $from_ami_id to $region"
-        ami_id=$(
-            aws ec2 copy-image \
-                --region "$region" \
-                --source-region "$from_region" \
-                --source-image-id "$from_ami_id" \
-                --name "$image_name" \
-                --description "$image_description" \
-                | jq -r '.ImageId'
-              )
-
-        write_state "$state_key" ami_id "$ami_id"
-    fi
-
-    [[ -v PRIVATE ]] || make_image_public "$region" "$ami_id"
-
-    echo "$ami_id"
-}
-
-upload_all() {
-    home_image_id=$(upload_image "$home_region")
-    jq -n \
-       --arg key "$home_region.$image_system" \
-       --arg value "$home_image_id" \
-       '$ARGS.named'
-
-    for region in "${regions[@]}"; do
-        if [ "$region" = "$home_region" ]; then
-            continue
-        fi
-        copied_image_id=$(copy_to_region "$region" "$home_region" "$home_image_id")
-
-        jq -n \
-           --arg key "$region.$image_system" \
-           --arg value "$copied_image_id" \
-           '$ARGS.named'
-    done
-}
-
-upload_all | jq --slurp from_entries
diff --git a/nixos/modules/config/no-x-libs.nix b/nixos/modules/config/no-x-libs.nix
index 1d7976cef36a2..eb593b2e7729f 100644
--- a/nixos/modules/config/no-x-libs.nix
+++ b/nixos/modules/config/no-x-libs.nix
@@ -54,10 +54,11 @@ with lib;
       intel-vaapi-driver = super.intel-vaapi-driver.override { enableGui = false; };
       libdevil = super.libdevil-nox;
       libextractor = super.libextractor.override { gtkSupport = false; };
+      libplacebo = super.libplacebo.override { vulkanSupport = false; };
       libva = super.libva-minimal;
       limesuite = super.limesuite.override { withGui = false; };
       mc = super.mc.override { x11Support = false; };
-      mpv-unwrapped = super.mpv-unwrapped.override { sdl2Support = false; x11Support = false; waylandSupport = false; };
+      mpv-unwrapped = super.mpv-unwrapped.override { drmSupport = false; screenSaverSupport = false; sdl2Support = false; vulkanSupport = false; waylandSupport = false; x11Support = false; };
       msmtp = super.msmtp.override { withKeyring = false; };
       mupdf = super.mupdf.override { enableGL = false; enableX11 = false; };
       neofetch = super.neofetch.override { x11Support = false; };
diff --git a/nixos/modules/config/nsswitch.nix b/nixos/modules/config/nsswitch.nix
index c7ba9b8eec6a2..fe0402ee9e666 100644
--- a/nixos/modules/config/nsswitch.nix
+++ b/nixos/modules/config/nsswitch.nix
@@ -12,7 +12,7 @@ with lib;
     system.nssModules = mkOption {
       type = types.listOf types.path;
       internal = true;
-      default = [];
+      default = [ ];
       description = ''
         Search path for NSS (Name Service Switch) modules.  This allows
         several DNS resolution methods to be specified via
@@ -35,7 +35,7 @@ with lib;
 
           This option only takes effect if nscd is enabled.
         '';
-        default = [];
+        default = [ ];
       };
 
       group = mkOption {
@@ -47,7 +47,7 @@ with lib;
 
           This option only takes effect if nscd is enabled.
         '';
-        default = [];
+        default = [ ];
       };
 
       shadow = mkOption {
@@ -59,7 +59,19 @@ with lib;
 
           This option only takes effect if nscd is enabled.
         '';
-        default = [];
+        default = [ ];
+      };
+
+      sudoers = mkOption {
+        type = types.listOf types.str;
+        description = ''
+          List of sudoers entries to configure in {file}`/etc/nsswitch.conf`.
+
+          Note that "files" is always prepended.
+
+          This option only takes effect if nscd is enabled.
+        '';
+        default = [ ];
       };
 
       hosts = mkOption {
@@ -71,7 +83,7 @@ with lib;
 
           This option only takes effect if nscd is enabled.
         '';
-        default = [];
+        default = [ ];
       };
 
       services = mkOption {
@@ -83,7 +95,7 @@ with lib;
 
           This option only takes effect if nscd is enabled.
         '';
-        default = [];
+        default = [ ];
       };
     };
   };
@@ -112,6 +124,7 @@ with lib;
       passwd:    ${concatStringsSep " " config.system.nssDatabases.passwd}
       group:     ${concatStringsSep " " config.system.nssDatabases.group}
       shadow:    ${concatStringsSep " " config.system.nssDatabases.shadow}
+      sudoers:   ${concatStringsSep " " config.system.nssDatabases.sudoers}
 
       hosts:     ${concatStringsSep " " config.system.nssDatabases.hosts}
       networks:  files
@@ -126,6 +139,7 @@ with lib;
       passwd = mkBefore [ "files" ];
       group = mkBefore [ "files" ];
       shadow = mkBefore [ "files" ];
+      sudoers = mkBefore [ "files" ];
       hosts = mkMerge [
         (mkOrder 998 [ "files" ])
         (mkOrder 1499 [ "dns" ])
diff --git a/nixos/modules/hardware/video/nvidia.nix b/nixos/modules/hardware/video/nvidia.nix
index 37d8e53a2e049..949b1e346269e 100644
--- a/nixos/modules/hardware/video/nvidia.nix
+++ b/nixos/modules/hardware/video/nvidia.nix
@@ -3,12 +3,10 @@
   lib,
   pkgs,
   ...
-}: let
+}:
+let
   nvidiaEnabled = (lib.elem "nvidia" config.services.xserver.videoDrivers);
-  nvidia_x11 =
-    if nvidiaEnabled || cfg.datacenter.enable
-    then cfg.package
-    else null;
+  nvidia_x11 = if nvidiaEnabled || cfg.datacenter.enable then cfg.package else null;
 
   cfg = config.hardware.nvidia;
 
@@ -19,8 +17,9 @@
   primeEnabled = syncCfg.enable || reverseSyncCfg.enable || offloadCfg.enable;
   busIDType = lib.types.strMatching "([[:print:]]+[\:\@][0-9]{1,3}\:[0-9]{1,2}\:[0-9])?";
   ibtSupport = cfg.open || (nvidia_x11.ibtSupport or false);
-  settingsFormat = pkgs.formats.keyValue {};
-in {
+  settingsFormat = pkgs.formats.keyValue { };
+in
+{
   options = {
     hardware.nvidia = {
       datacenter.enable = lib.mkEnableOption ''
@@ -29,50 +28,50 @@ in {
       datacenter.settings = lib.mkOption {
         type = settingsFormat.type;
         default = {
-          LOG_LEVEL=4;
-          LOG_FILE_NAME="/var/log/fabricmanager.log";
-          LOG_APPEND_TO_LOG=1;
-          LOG_FILE_MAX_SIZE=1024;
-          LOG_USE_SYSLOG=0;
-          DAEMONIZE=1;
-          BIND_INTERFACE_IP="127.0.0.1";
-          STARTING_TCP_PORT=16000;
-          FABRIC_MODE=0;
-          FABRIC_MODE_RESTART=0;
-          STATE_FILE_NAME="/var/tmp/fabricmanager.state";
-          FM_CMD_BIND_INTERFACE="127.0.0.1";
-          FM_CMD_PORT_NUMBER=6666;
-          FM_STAY_RESIDENT_ON_FAILURES=0;
-          ACCESS_LINK_FAILURE_MODE=0;
-          TRUNK_LINK_FAILURE_MODE=0;
-          NVSWITCH_FAILURE_MODE=0;
-          ABORT_CUDA_JOBS_ON_FM_EXIT=1;
-          TOPOLOGY_FILE_PATH="${nvidia_x11.fabricmanager}/share/nvidia-fabricmanager/nvidia/nvswitch";
-          DATABASE_PATH="${nvidia_x11.fabricmanager}/share/nvidia-fabricmanager/nvidia/nvswitch";
+          LOG_LEVEL = 4;
+          LOG_FILE_NAME = "/var/log/fabricmanager.log";
+          LOG_APPEND_TO_LOG = 1;
+          LOG_FILE_MAX_SIZE = 1024;
+          LOG_USE_SYSLOG = 0;
+          DAEMONIZE = 1;
+          BIND_INTERFACE_IP = "127.0.0.1";
+          STARTING_TCP_PORT = 16000;
+          FABRIC_MODE = 0;
+          FABRIC_MODE_RESTART = 0;
+          STATE_FILE_NAME = "/var/tmp/fabricmanager.state";
+          FM_CMD_BIND_INTERFACE = "127.0.0.1";
+          FM_CMD_PORT_NUMBER = 6666;
+          FM_STAY_RESIDENT_ON_FAILURES = 0;
+          ACCESS_LINK_FAILURE_MODE = 0;
+          TRUNK_LINK_FAILURE_MODE = 0;
+          NVSWITCH_FAILURE_MODE = 0;
+          ABORT_CUDA_JOBS_ON_FM_EXIT = 1;
+          TOPOLOGY_FILE_PATH = "${nvidia_x11.fabricmanager}/share/nvidia-fabricmanager/nvidia/nvswitch";
+          DATABASE_PATH = "${nvidia_x11.fabricmanager}/share/nvidia-fabricmanager/nvidia/nvswitch";
         };
         defaultText = lib.literalExpression ''
-        {
-          LOG_LEVEL=4;
-          LOG_FILE_NAME="/var/log/fabricmanager.log";
-          LOG_APPEND_TO_LOG=1;
-          LOG_FILE_MAX_SIZE=1024;
-          LOG_USE_SYSLOG=0;
-          DAEMONIZE=1;
-          BIND_INTERFACE_IP="127.0.0.1";
-          STARTING_TCP_PORT=16000;
-          FABRIC_MODE=0;
-          FABRIC_MODE_RESTART=0;
-          STATE_FILE_NAME="/var/tmp/fabricmanager.state";
-          FM_CMD_BIND_INTERFACE="127.0.0.1";
-          FM_CMD_PORT_NUMBER=6666;
-          FM_STAY_RESIDENT_ON_FAILURES=0;
-          ACCESS_LINK_FAILURE_MODE=0;
-          TRUNK_LINK_FAILURE_MODE=0;
-          NVSWITCH_FAILURE_MODE=0;
-          ABORT_CUDA_JOBS_ON_FM_EXIT=1;
-          TOPOLOGY_FILE_PATH="''${nvidia_x11.fabricmanager}/share/nvidia-fabricmanager/nvidia/nvswitch";
-          DATABASE_PATH="''${nvidia_x11.fabricmanager}/share/nvidia-fabricmanager/nvidia/nvswitch";
-        }
+          {
+            LOG_LEVEL=4;
+            LOG_FILE_NAME="/var/log/fabricmanager.log";
+            LOG_APPEND_TO_LOG=1;
+            LOG_FILE_MAX_SIZE=1024;
+            LOG_USE_SYSLOG=0;
+            DAEMONIZE=1;
+            BIND_INTERFACE_IP="127.0.0.1";
+            STARTING_TCP_PORT=16000;
+            FABRIC_MODE=0;
+            FABRIC_MODE_RESTART=0;
+            STATE_FILE_NAME="/var/tmp/fabricmanager.state";
+            FM_CMD_BIND_INTERFACE="127.0.0.1";
+            FM_CMD_PORT_NUMBER=6666;
+            FM_STAY_RESIDENT_ON_FAILURES=0;
+            ACCESS_LINK_FAILURE_MODE=0;
+            TRUNK_LINK_FAILURE_MODE=0;
+            NVSWITCH_FAILURE_MODE=0;
+            ABORT_CUDA_JOBS_ON_FM_EXIT=1;
+            TOPOLOGY_FILE_PATH="''${nvidia_x11.fabricmanager}/share/nvidia-fabricmanager/nvidia/nvswitch";
+            DATABASE_PATH="''${nvidia_x11.fabricmanager}/share/nvidia-fabricmanager/nvidia/nvswitch";
+          }
         '';
         description = ''
           Additional configuration options for fabricmanager.
@@ -211,7 +210,9 @@ in {
         (lib.mkEnableOption ''
           nvidia-settings, NVIDIA's GUI configuration tool
         '')
-        // {default = true;};
+        // {
+          default = true;
+        };
 
       nvidiaPersistenced = lib.mkEnableOption ''
         nvidia-persistenced a update for NVIDIA GPU headless mode, i.e.
@@ -226,7 +227,8 @@ in {
       '';
 
       package = lib.mkOption {
-        default = config.boot.kernelPackages.nvidiaPackages."${if cfg.datacenter.enable then "dc" else "stable"}";
+        default =
+          config.boot.kernelPackages.nvidiaPackages."${if cfg.datacenter.enable then "dc" else "stable"}";
         defaultText = lib.literalExpression ''
           config.boot.kernelPackages.nvidiaPackages."\$\{if cfg.datacenter.enable then "dc" else "stable"}"
         '';
@@ -242,403 +244,404 @@ in {
     };
   };
 
-  config = let
-    igpuDriver =
-      if pCfg.intelBusId != ""
-      then "modesetting"
-      else "amdgpu";
-    igpuBusId =
-      if pCfg.intelBusId != ""
-      then pCfg.intelBusId
-      else pCfg.amdgpuBusId;
-  in
-    lib.mkIf (nvidia_x11 != null) (lib.mkMerge [
-      # Common
-      ({
-        assertions = [
-          {
-            assertion = !(nvidiaEnabled && cfg.datacenter.enable);
-            message = "You cannot configure both X11 and Data Center drivers at the same time.";
-          }
-        ];
-        boot = {
-          blacklistedKernelModules = ["nouveau" "nvidiafb"];
-
-          # Don't add `nvidia-uvm` to `kernelModules`, because we want
-          # `nvidia-uvm` be loaded only after `udev` rules for `nvidia` kernel
-          # module are applied.
-          #
-          # Instead, we use `softdep` to lazily load `nvidia-uvm` kernel module
-          # after `nvidia` kernel module is loaded and `udev` rules are applied.
-          extraModprobeConfig = ''
-            softdep nvidia post: nvidia-uvm
-          '';
-        };
-        systemd.tmpfiles.rules =
-          lib.optional config.virtualisation.docker.enableNvidia
-            "L+ /run/nvidia-docker/bin - - - - ${nvidia_x11.bin}/origBin";
-        services.udev.extraRules =
-        ''
-          # Create /dev/nvidia-uvm when the nvidia-uvm module is loaded.
-          KERNEL=="nvidia", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidiactl c 195 255'"
-          KERNEL=="nvidia", RUN+="${pkgs.runtimeShell} -c 'for i in $$(cat /proc/driver/nvidia/gpus/*/information | grep Minor | cut -d \  -f 4); do mknod -m 666 /dev/nvidia$${i} c 195 $${i}; done'"
-          KERNEL=="nvidia_modeset", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia-modeset c 195 254'"
-          KERNEL=="nvidia_uvm", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia-uvm c $$(grep nvidia-uvm /proc/devices | cut -d \  -f 1) 0'"
-          KERNEL=="nvidia_uvm", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia-uvm-tools c $$(grep nvidia-uvm /proc/devices | cut -d \  -f 1) 1'"
-        '';
-        hardware.opengl = {
-          extraPackages = [
-            nvidia_x11.out
-          ];
-          extraPackages32 = [
-            nvidia_x11.lib32
+  config =
+    let
+      igpuDriver = if pCfg.intelBusId != "" then "modesetting" else "amdgpu";
+      igpuBusId = if pCfg.intelBusId != "" then pCfg.intelBusId else pCfg.amdgpuBusId;
+    in
+    lib.mkIf (nvidia_x11 != null) (
+      lib.mkMerge [
+        # Common
+        ({
+          assertions = [
+            {
+              assertion = !(nvidiaEnabled && cfg.datacenter.enable);
+              message = "You cannot configure both X11 and Data Center drivers at the same time.";
+            }
           ];
-        };
-        environment.systemPackages = [
-          nvidia_x11.bin
-        ];
-      })
-      # X11
-      (lib.mkIf nvidiaEnabled {
-        assertions = [
-        {
-          assertion = primeEnabled -> pCfg.intelBusId == "" || pCfg.amdgpuBusId == "";
-          message = "You cannot configure both an Intel iGPU and an AMD APU. Pick the one corresponding to your processor.";
-        }
-
-        {
-          assertion = offloadCfg.enableOffloadCmd -> offloadCfg.enable || reverseSyncCfg.enable;
-          message = "Offload command requires offloading or reverse prime sync to be enabled.";
-        }
-
-        {
-          assertion = primeEnabled -> pCfg.nvidiaBusId != "" && (pCfg.intelBusId != "" || pCfg.amdgpuBusId != "");
-          message = "When NVIDIA PRIME is enabled, the GPU bus IDs must be configured.";
-        }
-
-        {
-          assertion = offloadCfg.enable -> lib.versionAtLeast nvidia_x11.version "435.21";
-          message = "NVIDIA PRIME render offload is currently only supported on versions >= 435.21.";
-        }
-
-        {
-          assertion = (reverseSyncCfg.enable && pCfg.amdgpuBusId != "") -> lib.versionAtLeast nvidia_x11.version "470.0";
-          message = "NVIDIA PRIME render offload for AMD APUs is currently only supported on versions >= 470 beta.";
-        }
-
-        {
-          assertion = !(syncCfg.enable && offloadCfg.enable);
-          message = "PRIME Sync and Offload cannot be both enabled";
-        }
-
-        {
-          assertion = !(syncCfg.enable && reverseSyncCfg.enable);
-          message = "PRIME Sync and PRIME Reverse Sync cannot be both enabled";
-        }
-
-        {
-          assertion = !(syncCfg.enable && cfg.powerManagement.finegrained);
-          message = "Sync precludes powering down the NVIDIA GPU.";
-        }
-
-        {
-          assertion = cfg.powerManagement.finegrained -> offloadCfg.enable;
-          message = "Fine-grained power management requires offload to be enabled.";
-        }
-
-        {
-          assertion = cfg.powerManagement.enable -> lib.versionAtLeast nvidia_x11.version "430.09";
-          message = "Required files for driver based power management only exist on versions >= 430.09.";
-        }
-
-        {
-          assertion = cfg.open -> (cfg.package ? open && cfg.package ? firmware);
-          message = "This version of NVIDIA driver does not provide a corresponding opensource kernel driver";
-        }
-
-        {
-          assertion = cfg.dynamicBoost.enable -> lib.versionAtLeast nvidia_x11.version "510.39.01";
-          message = "NVIDIA's Dynamic Boost feature only exists on versions >= 510.39.01";
-        }];
-
-        # If Optimus/PRIME is enabled, we:
-        # - Specify the configured NVIDIA GPU bus ID in the Device section for the
-        #   "nvidia" driver.
-        # - Add the AllowEmptyInitialConfiguration option to the Screen section for the
-        #   "nvidia" driver, in order to allow the X server to start without any outputs.
-        # - Add a separate Device section for the Intel GPU, using the "modesetting"
-        #   driver and with the configured BusID.
-        # - OR add a separate Device section for the AMD APU, using the "amdgpu"
-        #   driver and with the configures BusID.
-        # - Reference that Device section from the ServerLayout section as an inactive
-        #   device.
-        # - Configure the display manager to run specific `xrandr` commands which will
-        #   configure/enable displays connected to the Intel iGPU / AMD APU.
-
-        # reverse sync implies offloading
-        hardware.nvidia.prime.offload.enable = lib.mkDefault reverseSyncCfg.enable;
-
-        services.xserver.drivers =
-          lib.optional primeEnabled {
-            name = igpuDriver;
-            display = offloadCfg.enable;
-            modules = lib.optional (igpuDriver == "amdgpu") pkgs.xorg.xf86videoamdgpu;
-            deviceSection =
-              ''
-                BusID "${igpuBusId}"
-              ''
-              + lib.optionalString (syncCfg.enable && igpuDriver != "amdgpu") ''
-                Option "AccelMethod" "none"
-              '';
-          }
-          ++ lib.singleton {
-            name = "nvidia";
-            modules = [nvidia_x11.bin];
-            display = !offloadCfg.enable;
-            deviceSection =
-              ''
-                Option "SidebandSocketPath" "/run/nvidia-xdriver/"
-              '' +
-              lib.optionalString primeEnabled
-              ''
-                BusID "${pCfg.nvidiaBusId}"
-              ''
-              + lib.optionalString pCfg.allowExternalGpu ''
-                Option "AllowExternalGpus"
-              '';
-            screenSection =
-              ''
-                Option "RandRRotation" "on"
-              ''
-              + lib.optionalString syncCfg.enable ''
-                Option "AllowEmptyInitialConfiguration"
-              ''
-              + lib.optionalString cfg.forceFullCompositionPipeline ''
-                Option         "metamodes" "nvidia-auto-select +0+0 {ForceFullCompositionPipeline=On}"
-                Option         "AllowIndirectGLXProtocol" "off"
-                Option         "TripleBuffer" "on"
-              '';
+          boot = {
+            blacklistedKernelModules = [
+              "nouveau"
+              "nvidiafb"
+            ];
+
+            # Don't add `nvidia-uvm` to `kernelModules`, because we want
+            # `nvidia-uvm` be loaded only after `udev` rules for `nvidia` kernel
+            # module are applied.
+            #
+            # Instead, we use `softdep` to lazily load `nvidia-uvm` kernel module
+            # after `nvidia` kernel module is loaded and `udev` rules are applied.
+            extraModprobeConfig = ''
+              softdep nvidia post: nvidia-uvm
+            '';
           };
-
-        services.xserver.serverLayoutSection =
-          lib.optionalString syncCfg.enable ''
-            Inactive "Device-${igpuDriver}[0]"
-          ''
-          + lib.optionalString reverseSyncCfg.enable ''
-            Inactive "Device-nvidia[0]"
-          ''
-          + lib.optionalString offloadCfg.enable ''
-            Option "AllowNVIDIAGPUScreens"
+          systemd.tmpfiles.rules = lib.optional config.virtualisation.docker.enableNvidia "L+ /run/nvidia-docker/bin - - - - ${nvidia_x11.bin}/origBin";
+          services.udev.extraRules = ''
+            # Create /dev/nvidia-uvm when the nvidia-uvm module is loaded.
+            KERNEL=="nvidia", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidiactl c 195 255'"
+            KERNEL=="nvidia", RUN+="${pkgs.runtimeShell} -c 'for i in $$(cat /proc/driver/nvidia/gpus/*/information | grep Minor | cut -d \  -f 4); do mknod -m 666 /dev/nvidia$${i} c 195 $${i}; done'"
+            KERNEL=="nvidia_modeset", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia-modeset c 195 254'"
+            KERNEL=="nvidia_uvm", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia-uvm c $$(grep nvidia-uvm /proc/devices | cut -d \  -f 1) 0'"
+            KERNEL=="nvidia_uvm", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia-uvm-tools c $$(grep nvidia-uvm /proc/devices | cut -d \  -f 1) 1'"
           '';
+          hardware.opengl = {
+            extraPackages = [ nvidia_x11.out nvidia_x11.settings.libXNVCtrl ];
+            extraPackages32 = [ nvidia_x11.lib32 ];
+          };
+          environment.systemPackages = [ nvidia_x11.bin ];
+        })
+        # X11
+        (lib.mkIf nvidiaEnabled {
+          assertions = [
+            {
+              assertion = primeEnabled -> pCfg.intelBusId == "" || pCfg.amdgpuBusId == "";
+              message = "You cannot configure both an Intel iGPU and an AMD APU. Pick the one corresponding to your processor.";
+            }
+
+            {
+              assertion = offloadCfg.enableOffloadCmd -> offloadCfg.enable || reverseSyncCfg.enable;
+              message = "Offload command requires offloading or reverse prime sync to be enabled.";
+            }
+
+            {
+              assertion =
+                primeEnabled -> pCfg.nvidiaBusId != "" && (pCfg.intelBusId != "" || pCfg.amdgpuBusId != "");
+              message = "When NVIDIA PRIME is enabled, the GPU bus IDs must be configured.";
+            }
+
+            {
+              assertion = offloadCfg.enable -> lib.versionAtLeast nvidia_x11.version "435.21";
+              message = "NVIDIA PRIME render offload is currently only supported on versions >= 435.21.";
+            }
+
+            {
+              assertion =
+                (reverseSyncCfg.enable && pCfg.amdgpuBusId != "") -> lib.versionAtLeast nvidia_x11.version "470.0";
+              message = "NVIDIA PRIME render offload for AMD APUs is currently only supported on versions >= 470 beta.";
+            }
+
+            {
+              assertion = !(syncCfg.enable && offloadCfg.enable);
+              message = "PRIME Sync and Offload cannot be both enabled";
+            }
+
+            {
+              assertion = !(syncCfg.enable && reverseSyncCfg.enable);
+              message = "PRIME Sync and PRIME Reverse Sync cannot be both enabled";
+            }
+
+            {
+              assertion = !(syncCfg.enable && cfg.powerManagement.finegrained);
+              message = "Sync precludes powering down the NVIDIA GPU.";
+            }
+
+            {
+              assertion = cfg.powerManagement.finegrained -> offloadCfg.enable;
+              message = "Fine-grained power management requires offload to be enabled.";
+            }
+
+            {
+              assertion = cfg.powerManagement.enable -> lib.versionAtLeast nvidia_x11.version "430.09";
+              message = "Required files for driver based power management only exist on versions >= 430.09.";
+            }
+
+            {
+              assertion = cfg.open -> (cfg.package ? open && cfg.package ? firmware);
+              message = "This version of NVIDIA driver does not provide a corresponding opensource kernel driver";
+            }
+
+            {
+              assertion = cfg.dynamicBoost.enable -> lib.versionAtLeast nvidia_x11.version "510.39.01";
+              message = "NVIDIA's Dynamic Boost feature only exists on versions >= 510.39.01";
+            }
+          ];
 
-        services.xserver.displayManager.setupCommands = let
-          gpuProviderName =
-            if igpuDriver == "amdgpu"
-            then
-              # find the name of the provider if amdgpu
-              "`${lib.getExe pkgs.xorg.xrandr} --listproviders | ${lib.getExe pkgs.gnugrep} -i AMD | ${lib.getExe pkgs.gnused} -n 's/^.*name://p'`"
-            else igpuDriver;
-          providerCmdParams =
-            if syncCfg.enable
-            then "\"${gpuProviderName}\" NVIDIA-0"
-            else "NVIDIA-G0 \"${gpuProviderName}\"";
-        in
-          lib.optionalString (syncCfg.enable || reverseSyncCfg.enable) ''
-            # Added by nvidia configuration module for Optimus/PRIME.
-            ${lib.getExe pkgs.xorg.xrandr} --setprovideroutputsource ${providerCmdParams}
-            ${lib.getExe pkgs.xorg.xrandr} --auto
-          '';
+          # If Optimus/PRIME is enabled, we:
+          # - Specify the configured NVIDIA GPU bus ID in the Device section for the
+          #   "nvidia" driver.
+          # - Add the AllowEmptyInitialConfiguration option to the Screen section for the
+          #   "nvidia" driver, in order to allow the X server to start without any outputs.
+          # - Add a separate Device section for the Intel GPU, using the "modesetting"
+          #   driver and with the configured BusID.
+          # - OR add a separate Device section for the AMD APU, using the "amdgpu"
+          #   driver and with the configures BusID.
+          # - Reference that Device section from the ServerLayout section as an inactive
+          #   device.
+          # - Configure the display manager to run specific `xrandr` commands which will
+          #   configure/enable displays connected to the Intel iGPU / AMD APU.
+
+          # reverse sync implies offloading
+          hardware.nvidia.prime.offload.enable = lib.mkDefault reverseSyncCfg.enable;
+
+          services.xserver.drivers =
+            lib.optional primeEnabled {
+              name = igpuDriver;
+              display = offloadCfg.enable;
+              modules = lib.optional (igpuDriver == "amdgpu") pkgs.xorg.xf86videoamdgpu;
+              deviceSection =
+                ''
+                  BusID "${igpuBusId}"
+                ''
+                + lib.optionalString (syncCfg.enable && igpuDriver != "amdgpu") ''
+                  Option "AccelMethod" "none"
+                '';
+            }
+            ++ lib.singleton {
+              name = "nvidia";
+              modules = [ nvidia_x11.bin ];
+              display = !offloadCfg.enable;
+              deviceSection =
+                ''
+                  Option "SidebandSocketPath" "/run/nvidia-xdriver/"
+                ''
+                + lib.optionalString primeEnabled ''
+                  BusID "${pCfg.nvidiaBusId}"
+                ''
+                + lib.optionalString pCfg.allowExternalGpu ''
+                  Option "AllowExternalGpus"
+                '';
+              screenSection =
+                ''
+                  Option "RandRRotation" "on"
+                ''
+                + lib.optionalString syncCfg.enable ''
+                  Option "AllowEmptyInitialConfiguration"
+                ''
+                + lib.optionalString cfg.forceFullCompositionPipeline ''
+                  Option         "metamodes" "nvidia-auto-select +0+0 {ForceFullCompositionPipeline=On}"
+                  Option         "AllowIndirectGLXProtocol" "off"
+                  Option         "TripleBuffer" "on"
+                '';
+            };
 
-        environment.etc = {
-          "nvidia/nvidia-application-profiles-rc" = lib.mkIf nvidia_x11.useProfiles {source = "${nvidia_x11.bin}/share/nvidia/nvidia-application-profiles-rc";};
+          services.xserver.serverLayoutSection =
+            lib.optionalString syncCfg.enable ''
+              Inactive "Device-${igpuDriver}[0]"
+            ''
+            + lib.optionalString reverseSyncCfg.enable ''
+              Inactive "Device-nvidia[0]"
+            ''
+            + lib.optionalString offloadCfg.enable ''
+              Option "AllowNVIDIAGPUScreens"
+            '';
+
+          services.xserver.displayManager.setupCommands =
+            let
+              gpuProviderName =
+                if igpuDriver == "amdgpu" then
+                  # find the name of the provider if amdgpu
+                  "`${lib.getExe pkgs.xorg.xrandr} --listproviders | ${lib.getExe pkgs.gnugrep} -i AMD | ${lib.getExe pkgs.gnused} -n 's/^.*name://p'`"
+                else
+                  igpuDriver;
+              providerCmdParams =
+                if syncCfg.enable then "\"${gpuProviderName}\" NVIDIA-0" else "NVIDIA-G0 \"${gpuProviderName}\"";
+            in
+            lib.optionalString (syncCfg.enable || reverseSyncCfg.enable) ''
+              # Added by nvidia configuration module for Optimus/PRIME.
+              ${lib.getExe pkgs.xorg.xrandr} --setprovideroutputsource ${providerCmdParams}
+              ${lib.getExe pkgs.xorg.xrandr} --auto
+            '';
+
+          environment.etc = {
+            "nvidia/nvidia-application-profiles-rc" = lib.mkIf nvidia_x11.useProfiles {
+              source = "${nvidia_x11.bin}/share/nvidia/nvidia-application-profiles-rc";
+            };
 
-          # 'nvidia_x11' installs it's files to /run/opengl-driver/...
-          "egl/egl_external_platform.d".source = "/run/opengl-driver/share/egl/egl_external_platform.d/";
-        };
+            # 'nvidia_x11' installs it's files to /run/opengl-driver/...
+            "egl/egl_external_platform.d".source = "/run/opengl-driver/share/egl/egl_external_platform.d/";
+          };
 
-        hardware.opengl = {
-          extraPackages = [
-            pkgs.nvidia-vaapi-driver
-          ];
-          extraPackages32 = [
-            pkgs.pkgsi686Linux.nvidia-vaapi-driver
-          ];
-        };
-        environment.systemPackages =
-          lib.optional cfg.nvidiaSettings nvidia_x11.settings
-          ++ lib.optional cfg.nvidiaPersistenced nvidia_x11.persistenced
-          ++ lib.optional offloadCfg.enableOffloadCmd
-          (pkgs.writeShellScriptBin "nvidia-offload" ''
-            export __NV_PRIME_RENDER_OFFLOAD=1
-            export __NV_PRIME_RENDER_OFFLOAD_PROVIDER=NVIDIA-G0
-            export __GLX_VENDOR_LIBRARY_NAME=nvidia
-            export __VK_LAYER_NV_optimus=NVIDIA_only
-            exec "$@"
-          '');
-
-        systemd.packages = lib.optional cfg.powerManagement.enable nvidia_x11.out;
-
-        systemd.services = let
-          nvidiaService = state: {
-            description = "NVIDIA system ${state} actions";
-            path = [pkgs.kbd];
-            serviceConfig = {
-              Type = "oneshot";
-              ExecStart = "${nvidia_x11.out}/bin/nvidia-sleep.sh '${state}'";
-            };
-            before = ["systemd-${state}.service"];
-            requiredBy = ["systemd-${state}.service"];
+          hardware.opengl = {
+            extraPackages = [ pkgs.nvidia-vaapi-driver ];
+            extraPackages32 = [ pkgs.pkgsi686Linux.nvidia-vaapi-driver ];
           };
-        in
-          lib.mkMerge [
-            (lib.mkIf cfg.powerManagement.enable {
-              nvidia-suspend = nvidiaService "suspend";
-              nvidia-hibernate = nvidiaService "hibernate";
-              nvidia-resume =
-                (nvidiaService "resume")
-                // {
-                  before = [];
-                  after = ["systemd-suspend.service" "systemd-hibernate.service"];
-                  requiredBy = ["systemd-suspend.service" "systemd-hibernate.service"];
-                };
-            })
-            (lib.mkIf cfg.nvidiaPersistenced {
-              "nvidia-persistenced" = {
-                description = "NVIDIA Persistence Daemon";
-                wantedBy = ["multi-user.target"];
+          environment.systemPackages =
+            lib.optional cfg.nvidiaSettings nvidia_x11.settings
+            ++ lib.optional cfg.nvidiaPersistenced nvidia_x11.persistenced
+            ++ lib.optional offloadCfg.enableOffloadCmd (
+              pkgs.writeShellScriptBin "nvidia-offload" ''
+                export __NV_PRIME_RENDER_OFFLOAD=1
+                export __NV_PRIME_RENDER_OFFLOAD_PROVIDER=NVIDIA-G0
+                export __GLX_VENDOR_LIBRARY_NAME=nvidia
+                export __VK_LAYER_NV_optimus=NVIDIA_only
+                exec "$@"
+              ''
+            );
+
+          systemd.packages = lib.optional cfg.powerManagement.enable nvidia_x11.out;
+
+          systemd.services =
+            let
+              nvidiaService = state: {
+                description = "NVIDIA system ${state} actions";
+                path = [ pkgs.kbd ];
                 serviceConfig = {
-                  Type = "forking";
-                  Restart = "always";
-                  PIDFile = "/var/run/nvidia-persistenced/nvidia-persistenced.pid";
-                  ExecStart = "${lib.getExe nvidia_x11.persistenced} --verbose";
-                  ExecStopPost = "${pkgs.coreutils}/bin/rm -rf /var/run/nvidia-persistenced";
+                  Type = "oneshot";
+                  ExecStart = "${nvidia_x11.out}/bin/nvidia-sleep.sh '${state}'";
                 };
+                before = [ "systemd-${state}.service" ];
+                requiredBy = [ "systemd-${state}.service" ];
               };
-            })
-            (lib.mkIf cfg.dynamicBoost.enable {
-              "nvidia-powerd" = {
-                description = "nvidia-powerd service";
-                path = [
-                  pkgs.util-linux # nvidia-powerd wants lscpu
-                ];
-                wantedBy = ["multi-user.target"];
-                serviceConfig = {
-                  Type = "dbus";
-                  BusName = "nvidia.powerd.server";
-                  ExecStart = "${nvidia_x11.bin}/bin/nvidia-powerd";
+            in
+            lib.mkMerge [
+              (lib.mkIf cfg.powerManagement.enable {
+                nvidia-suspend = nvidiaService "suspend";
+                nvidia-hibernate = nvidiaService "hibernate";
+                nvidia-resume = (nvidiaService "resume") // {
+                  before = [ ];
+                  after = [
+                    "systemd-suspend.service"
+                    "systemd-hibernate.service"
+                  ];
+                  requiredBy = [
+                    "systemd-suspend.service"
+                    "systemd-hibernate.service"
+                  ];
                 };
-              };
-            })
-          ];
-        services.acpid.enable = true;
-
-        services.dbus.packages = lib.optional cfg.dynamicBoost.enable nvidia_x11.bin;
-
-        hardware.firmware = lib.optional cfg.open nvidia_x11.firmware;
-
-        systemd.tmpfiles.rules = [
-          # Remove the following log message:
-          #    (WW) NVIDIA: Failed to bind sideband socket to
-          #    (WW) NVIDIA:     '/var/run/nvidia-xdriver-b4f69129' Permission denied
-          #
-          # https://bbs.archlinux.org/viewtopic.php?pid=1909115#p1909115
-          "d /run/nvidia-xdriver 0770 root users"
-        ] ++ lib.optional (nvidia_x11.persistenced != null && config.virtualisation.docker.enableNvidia)
-          "L+ /run/nvidia-docker/extras/bin/nvidia-persistenced - - - - ${nvidia_x11.persistenced}/origBin/nvidia-persistenced";
-
-        boot = {
-          extraModulePackages =
-            if cfg.open
-            then [nvidia_x11.open]
-            else [nvidia_x11.bin];
-          # nvidia-uvm is required by CUDA applications.
-          kernelModules =
-            lib.optionals config.services.xserver.enable ["nvidia" "nvidia_modeset" "nvidia_drm"];
-
-          # If requested enable modesetting via kernel parameter.
-          kernelParams =
-            lib.optional (offloadCfg.enable || cfg.modesetting.enable) "nvidia-drm.modeset=1"
-            ++ lib.optional cfg.powerManagement.enable "nvidia.NVreg_PreserveVideoMemoryAllocations=1"
-            ++ lib.optional cfg.open "nvidia.NVreg_OpenRmEnableUnsupportedGpus=1"
-            ++ lib.optional (config.boot.kernelPackages.kernel.kernelAtLeast "6.2" && !ibtSupport) "ibt=off";
-
-          # enable finegrained power management
-          extraModprobeConfig = lib.optionalString cfg.powerManagement.finegrained ''
-            options nvidia "NVreg_DynamicPowerManagement=0x02"
-          '';
-        };
-        services.udev.extraRules =
-          lib.optionalString cfg.powerManagement.finegrained (
-          lib.optionalString (lib.versionOlder config.boot.kernelPackages.kernel.version "5.5") ''
-            # Remove NVIDIA USB xHCI Host Controller devices, if present
-            ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x0c0330", ATTR{remove}="1"
-
-            # Remove NVIDIA USB Type-C UCSI devices, if present
-            ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x0c8000", ATTR{remove}="1"
-
-            # Remove NVIDIA Audio devices, if present
-            ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x040300", ATTR{remove}="1"
-          ''
-          + ''
-            # Enable runtime PM for NVIDIA VGA/3D controller devices on driver bind
-            ACTION=="bind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030000", TEST=="power/control", ATTR{power/control}="auto"
-            ACTION=="bind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030200", TEST=="power/control", ATTR{power/control}="auto"
-
-            # Disable runtime PM for NVIDIA VGA/3D controller devices on driver unbind
-            ACTION=="unbind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030000", TEST=="power/control", ATTR{power/control}="on"
-            ACTION=="unbind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030200", TEST=="power/control", ATTR{power/control}="on"
-          ''
-        );
-      })
-      # Data Center
-      (lib.mkIf (cfg.datacenter.enable) {
-        boot.extraModulePackages = [
-          nvidia_x11.bin
-        ];
-
-        systemd = {
-          tmpfiles.rules =
-            lib.optional (nvidia_x11.persistenced != null && config.virtualisation.docker.enableNvidia)
-            "L+ /run/nvidia-docker/extras/bin/nvidia-persistenced - - - - ${nvidia_x11.persistenced}/origBin/nvidia-persistenced";
-
-          services = lib.mkMerge [
-            ({
-              nvidia-fabricmanager = {
-                enable = true;
-                description = "Start NVIDIA NVLink Management";
-                wantedBy = [ "multi-user.target" ];
-                unitConfig.After = [ "network-online.target" ];
-                unitConfig.Requires = [ "network-online.target" ];
-                serviceConfig = {
-                  Type = "forking";
-                  TimeoutStartSec = 240;
-                  ExecStart = let
-                    nv-fab-conf = settingsFormat.generate "fabricmanager.conf" cfg.datacenter.settings;
-                    in
+              })
+              (lib.mkIf cfg.nvidiaPersistenced {
+                "nvidia-persistenced" = {
+                  description = "NVIDIA Persistence Daemon";
+                  wantedBy = [ "multi-user.target" ];
+                  serviceConfig = {
+                    Type = "forking";
+                    Restart = "always";
+                    PIDFile = "/var/run/nvidia-persistenced/nvidia-persistenced.pid";
+                    ExecStart = "${lib.getExe nvidia_x11.persistenced} --verbose";
+                    ExecStopPost = "${pkgs.coreutils}/bin/rm -rf /var/run/nvidia-persistenced";
+                  };
+                };
+              })
+              (lib.mkIf cfg.dynamicBoost.enable {
+                "nvidia-powerd" = {
+                  description = "nvidia-powerd service";
+                  path = [
+                    pkgs.util-linux # nvidia-powerd wants lscpu
+                  ];
+                  wantedBy = [ "multi-user.target" ];
+                  serviceConfig = {
+                    Type = "dbus";
+                    BusName = "nvidia.powerd.server";
+                    ExecStart = "${nvidia_x11.bin}/bin/nvidia-powerd";
+                  };
+                };
+              })
+            ];
+          services.acpid.enable = true;
+
+          services.dbus.packages = lib.optional cfg.dynamicBoost.enable nvidia_x11.bin;
+
+          hardware.firmware =
+            let
+              isOpen = cfg.open;
+              isNewUnfree = lib.versionAtLeast nvidia_x11.version "555";
+            in
+            lib.optional (isOpen || isNewUnfree) nvidia_x11.firmware;
+
+          systemd.tmpfiles.rules =
+            [
+              # Remove the following log message:
+              #    (WW) NVIDIA: Failed to bind sideband socket to
+              #    (WW) NVIDIA:     '/var/run/nvidia-xdriver-b4f69129' Permission denied
+              #
+              # https://bbs.archlinux.org/viewtopic.php?pid=1909115#p1909115
+              "d /run/nvidia-xdriver 0770 root users"
+            ]
+            ++ lib.optional (nvidia_x11.persistenced != null && config.virtualisation.docker.enableNvidia)
+              "L+ /run/nvidia-docker/extras/bin/nvidia-persistenced - - - - ${nvidia_x11.persistenced}/origBin/nvidia-persistenced";
+
+          boot = {
+            extraModulePackages = if cfg.open then [ nvidia_x11.open ] else [ nvidia_x11.bin ];
+            # nvidia-uvm is required by CUDA applications.
+            kernelModules = lib.optionals config.services.xserver.enable [
+              "nvidia"
+              "nvidia_modeset"
+              "nvidia_drm"
+            ];
+
+            # If requested enable modesetting via kernel parameter.
+            kernelParams =
+              lib.optional (offloadCfg.enable || cfg.modesetting.enable) "nvidia-drm.modeset=1"
+              ++ lib.optional cfg.powerManagement.enable "nvidia.NVreg_PreserveVideoMemoryAllocations=1"
+              ++ lib.optional cfg.open "nvidia.NVreg_OpenRmEnableUnsupportedGpus=1"
+              ++ lib.optional (config.boot.kernelPackages.kernel.kernelAtLeast "6.2" && !ibtSupport) "ibt=off";
+
+            # enable finegrained power management
+            extraModprobeConfig = lib.optionalString cfg.powerManagement.finegrained ''
+              options nvidia "NVreg_DynamicPowerManagement=0x02"
+            '';
+          };
+          services.udev.extraRules = lib.optionalString cfg.powerManagement.finegrained (
+            lib.optionalString (lib.versionOlder config.boot.kernelPackages.kernel.version "5.5") ''
+              # Remove NVIDIA USB xHCI Host Controller devices, if present
+              ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x0c0330", ATTR{remove}="1"
+
+              # Remove NVIDIA USB Type-C UCSI devices, if present
+              ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x0c8000", ATTR{remove}="1"
+
+              # Remove NVIDIA Audio devices, if present
+              ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x040300", ATTR{remove}="1"
+            ''
+            + ''
+              # Enable runtime PM for NVIDIA VGA/3D controller devices on driver bind
+              ACTION=="bind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030000", TEST=="power/control", ATTR{power/control}="auto"
+              ACTION=="bind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030200", TEST=="power/control", ATTR{power/control}="auto"
+
+              # Disable runtime PM for NVIDIA VGA/3D controller devices on driver unbind
+              ACTION=="unbind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030000", TEST=="power/control", ATTR{power/control}="on"
+              ACTION=="unbind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030200", TEST=="power/control", ATTR{power/control}="on"
+            ''
+          );
+        })
+        # Data Center
+        (lib.mkIf (cfg.datacenter.enable) {
+          boot.extraModulePackages = [ nvidia_x11.bin ];
+
+          systemd = {
+            tmpfiles.rules =
+              lib.optional (nvidia_x11.persistenced != null && config.virtualisation.docker.enableNvidia)
+                "L+ /run/nvidia-docker/extras/bin/nvidia-persistenced - - - - ${nvidia_x11.persistenced}/origBin/nvidia-persistenced";
+
+            services = lib.mkMerge [
+              ({
+                nvidia-fabricmanager = {
+                  enable = true;
+                  description = "Start NVIDIA NVLink Management";
+                  wantedBy = [ "multi-user.target" ];
+                  unitConfig.After = [ "network-online.target" ];
+                  unitConfig.Requires = [ "network-online.target" ];
+                  serviceConfig = {
+                    Type = "forking";
+                    TimeoutStartSec = 240;
+                    ExecStart =
+                      let
+                        nv-fab-conf = settingsFormat.generate "fabricmanager.conf" cfg.datacenter.settings;
+                      in
                       "${lib.getExe nvidia_x11.fabricmanager} -c ${nv-fab-conf}";
-                  LimitCORE="infinity";
+                    LimitCORE = "infinity";
+                  };
                 };
-              };
-            })
-            (lib.mkIf cfg.nvidiaPersistenced {
-              "nvidia-persistenced" = {
-                description = "NVIDIA Persistence Daemon";
-                wantedBy = ["multi-user.target"];
-                serviceConfig = {
-                  Type = "forking";
-                  Restart = "always";
-                  PIDFile = "/var/run/nvidia-persistenced/nvidia-persistenced.pid";
-                  ExecStart = "${lib.getExe nvidia_x11.persistenced} --verbose";
-                  ExecStopPost = "${pkgs.coreutils}/bin/rm -rf /var/run/nvidia-persistenced";
+              })
+              (lib.mkIf cfg.nvidiaPersistenced {
+                "nvidia-persistenced" = {
+                  description = "NVIDIA Persistence Daemon";
+                  wantedBy = [ "multi-user.target" ];
+                  serviceConfig = {
+                    Type = "forking";
+                    Restart = "always";
+                    PIDFile = "/var/run/nvidia-persistenced/nvidia-persistenced.pid";
+                    ExecStart = "${lib.getExe nvidia_x11.persistenced} --verbose";
+                    ExecStopPost = "${pkgs.coreutils}/bin/rm -rf /var/run/nvidia-persistenced";
+                  };
                 };
-              };
-            })
-          ];
-      };
+              })
+            ];
+          };
 
-      environment.systemPackages =
-        lib.optional cfg.datacenter.enable nvidia_x11.fabricmanager
-        ++ lib.optional cfg.nvidiaPersistenced nvidia_x11.persistenced;
-    })
-  ]);
+          environment.systemPackages =
+            lib.optional cfg.datacenter.enable nvidia_x11.fabricmanager
+            ++ lib.optional cfg.nvidiaPersistenced nvidia_x11.persistenced;
+        })
+      ]
+    );
 }
diff --git a/nixos/modules/misc/version.nix b/nixos/modules/misc/version.nix
index d582e0c162de3..29e9498018ec9 100644
--- a/nixos/modules/misc/version.nix
+++ b/nixos/modules/misc/version.nix
@@ -135,7 +135,7 @@ in
       };
 
       version = lib.mkOption {
-        type = types.nullOr (types.strMatching "^[a-z0-9._-]+$");
+        type = types.nullOr (types.strMatching "^[a-z0-9._-~^]+$");
         default = null;
         description = ''
           Image version.
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index a92ae32d06fa2..76fa899ef7cef 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -158,6 +158,7 @@
   ./programs/bash/ls-colors.nix
   ./programs/bash/undistract-me.nix
   ./programs/bcc.nix
+  ./programs/benchexec.nix
   ./programs/browserpass.nix
   ./programs/calls.nix
   ./programs/captive-browser.nix
@@ -167,6 +168,7 @@
   ./programs/chromium.nix
   ./programs/clash-verge.nix
   ./programs/cnping.nix
+  ./programs/cpu-energy-meter.nix
   ./programs/command-not-found/command-not-found.nix
   ./programs/coolercontrol.nix
   ./programs/criu.nix
@@ -247,9 +249,9 @@
   ./programs/oblogout.nix
   ./programs/oddjobd.nix
   ./programs/openvpn3.nix
-  ./programs/pantheon-tweaks.nix
   ./programs/partition-manager.nix
   ./programs/plotinus.nix
+  ./programs/pqos-wrapper.nix
   ./programs/projecteur.nix
   ./programs/proxychains.nix
   ./programs/qdmr.nix
@@ -1106,6 +1108,7 @@
   ./services/networking/ocserv.nix
   ./services/networking/ofono.nix
   ./services/networking/oidentd.nix
+  ./services/networking/oink.nix
   ./services/networking/onedrive.nix
   ./services/networking/openconnect.nix
   ./services/networking/openvpn.nix
@@ -1325,6 +1328,7 @@
   ./services/web-apps/akkoma.nix
   ./services/web-apps/alps.nix
   ./services/web-apps/anuko-time-tracker.nix
+  ./services/web-apps/artalk.nix
   ./services/web-apps/atlassian/confluence.nix
   ./services/web-apps/atlassian/crowd.nix
   ./services/web-apps/atlassian/jira.nix
@@ -1338,6 +1342,7 @@
   ./services/web-apps/chatgpt-retrieval-plugin.nix
   ./services/web-apps/cloudlog.nix
   ./services/web-apps/code-server.nix
+  ./services/web-apps/commafeed.nix
   ./services/web-apps/convos.nix
   ./services/web-apps/crabfit.nix
   ./services/web-apps/davis.nix
@@ -1348,7 +1353,9 @@
   ./services/web-apps/dolibarr.nix
   ./services/web-apps/engelsystem.nix
   ./services/web-apps/ethercalc.nix
+  ./services/web-apps/filesender.nix
   ./services/web-apps/firefly-iii.nix
+  ./services/web-apps/flarum.nix
   ./services/web-apps/fluidd.nix
   ./services/web-apps/freshrss.nix
   ./services/web-apps/galene.nix
@@ -1392,6 +1399,7 @@
   ./services/web-apps/netbox.nix
   ./services/web-apps/nextcloud.nix
   ./services/web-apps/nextcloud-notify_push.nix
+  ./services/web-apps/nextjs-ollama-llm-ui.nix
   ./services/web-apps/nexus.nix
   ./services/web-apps/nifi.nix
   ./services/web-apps/node-red.nix
@@ -1420,6 +1428,7 @@
   ./services/web-apps/selfoss.nix
   ./services/web-apps/shiori.nix
   ./services/web-apps/silverbullet.nix
+  ./services/web-apps/simplesamlphp.nix
   ./services/web-apps/slskd.nix
   ./services/web-apps/snipe-it.nix
   ./services/web-apps/sogo.nix
@@ -1431,11 +1440,13 @@
   ./services/web-apps/windmill.nix
   ./services/web-apps/wordpress.nix
   ./services/web-apps/writefreely.nix
+  ./services/web-apps/your_spotify.nix
   ./services/web-apps/youtrack.nix
   ./services/web-apps/zabbix.nix
   ./services/web-apps/zitadel.nix
   ./services/web-servers/agate.nix
   ./services/web-servers/apache-httpd/default.nix
+  ./services/web-servers/bluemap.nix
   ./services/web-servers/caddy/default.nix
   ./services/web-servers/darkhttpd.nix
   ./services/web-servers/fcgiwrap.nix
diff --git a/nixos/modules/programs/atop.nix b/nixos/modules/programs/atop.nix
index be82e432474be..3738f926ca3d8 100644
--- a/nixos/modules/programs/atop.nix
+++ b/nixos/modules/programs/atop.nix
@@ -120,8 +120,8 @@ in
               wantedBy = [ (if type == "services" then "multi-user.target" else if type == "timers" then "timers.target" else null) ];
             };
           };
-          mkService = lib.mkSystemd "services";
-          mkTimer = lib.mkSystemd "timers";
+          mkService = mkSystemd "services";
+          mkTimer = mkSystemd "timers";
         in
         {
           packages = [ atop (lib.mkIf cfg.netatop.enable cfg.netatop.package) ];
diff --git a/nixos/modules/programs/benchexec.nix b/nixos/modules/programs/benchexec.nix
new file mode 100644
index 0000000000000..652670c117ea3
--- /dev/null
+++ b/nixos/modules/programs/benchexec.nix
@@ -0,0 +1,98 @@
+{ lib
+, pkgs
+, config
+, options
+, ...
+}:
+let
+  cfg = config.programs.benchexec;
+  opt = options.programs.benchexec;
+
+  filterUsers = x:
+    if builtins.isString x then config.users.users ? ${x} else
+    if builtins.isInt    x then x                         else
+    throw "filterUsers expects string (username) or int (UID)";
+
+  uid = x:
+    if builtins.isString x then config.users.users.${x}.uid else
+    if builtins.isInt    x then x                           else
+    throw "uid expects string (username) or int (UID)";
+in
+{
+  options.programs.benchexec = {
+    enable = lib.mkEnableOption "BenchExec";
+    package = lib.options.mkPackageOption pkgs "benchexec" { };
+
+    users = lib.options.mkOption {
+      type = with lib.types; listOf (either str int);
+      description = ''
+        Users that intend to use BenchExec.
+        Provide usernames of users that are configured via {option}`${options.users.users}` as string,
+        and UIDs of "mutable users" as integers.
+        Control group delegation will be configured via systemd.
+        For more information, see <https://github.com/sosy-lab/benchexec/blob/3.18/doc/INSTALL.md#setting-up-cgroups>.
+      '';
+      default = [ ];
+      example = lib.literalExpression ''
+        [
+          "alice" # username of a user configured via ${options.users.users}
+          1007    # UID of a mutable user
+        ]
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    assertions = (map
+      (user: {
+        assertion = config.users.users ? ${user};
+        message = ''
+          The user '${user}' intends to use BenchExec (via `${opt.users}`), but is not configured via `${options.users.users}`.
+        '';
+      })
+      (builtins.filter builtins.isString cfg.users)
+    ) ++ (map
+      (id: {
+        assertion = config.users.mutableUsers;
+        message = ''
+          The user with UID '${id}' intends to use BenchExec (via `${opt.users}`), but mutable users are disabled via `${options.users.mutableUsers}`.
+        '';
+      })
+      (builtins.filter builtins.isInt cfg.users)
+    ) ++ [
+      {
+        assertion = config.systemd.enableUnifiedCgroupHierarchy == true;
+        message = ''
+          The BenchExec module `${opt.enable}` only supports control groups 2 (`${options.systemd.enableUnifiedCgroupHierarchy} = true`).
+        '';
+      }
+    ];
+
+    environment.systemPackages = [ cfg.package ];
+
+    # See <https://github.com/sosy-lab/benchexec/blob/3.18/doc/INSTALL.md#setting-up-cgroups>.
+    systemd.services = builtins.listToAttrs (map
+      (user: {
+        name = "user@${builtins.toString (uid user)}";
+        value = {
+          serviceConfig.Delegate = "yes";
+          overrideStrategy = "asDropin";
+        };
+      })
+      (builtins.filter filterUsers cfg.users));
+
+    # See <https://github.com/sosy-lab/benchexec/blob/3.18/doc/INSTALL.md#requirements>.
+    virtualisation.lxc.lxcfs.enable = lib.mkDefault true;
+
+    # See <https://github.com/sosy-lab/benchexec/blob/3.18/doc/INSTALL.md#requirements>.
+    programs = {
+      cpu-energy-meter.enable = lib.mkDefault true;
+      pqos-wrapper.enable = lib.mkDefault true;
+    };
+
+    # See <https://github.com/sosy-lab/benchexec/blob/3.18/doc/INSTALL.md#kernel-requirements>.
+    security.unprivilegedUsernsClone = true;
+  };
+
+  meta.maintainers = with lib.maintainers; [ lorenzleutgeb ];
+}
diff --git a/nixos/modules/programs/cpu-energy-meter.nix b/nixos/modules/programs/cpu-energy-meter.nix
new file mode 100644
index 0000000000000..653ec067492d7
--- /dev/null
+++ b/nixos/modules/programs/cpu-energy-meter.nix
@@ -0,0 +1,27 @@
+{ config
+, lib
+, pkgs
+, ...
+}: {
+  options.programs.cpu-energy-meter = {
+    enable = lib.mkEnableOption "CPU Energy Meter";
+    package = lib.mkPackageOption pkgs "cpu-energy-meter" { };
+  };
+
+  config =
+    let
+      cfg = config.programs.cpu-energy-meter;
+    in
+    lib.mkIf cfg.enable {
+      hardware.cpu.x86.msr.enable = true;
+
+      security.wrappers.${cfg.package.meta.mainProgram} = {
+        owner = "nobody";
+        group = config.hardware.cpu.x86.msr.group;
+        source = lib.getExe cfg.package;
+        capabilities = "cap_sys_rawio=ep";
+      };
+    };
+
+  meta.maintainers = with lib.maintainers; [ lorenzleutgeb ];
+}
diff --git a/nixos/modules/programs/firefox.nix b/nixos/modules/programs/firefox.nix
index f698c3999dc1f..7e0dec57d2dac 100644
--- a/nixos/modules/programs/firefox.nix
+++ b/nixos/modules/programs/firefox.nix
@@ -287,7 +287,7 @@ in
         (_: value: { Value = value; Status = cfg.preferencesStatus; })
         cfg.preferences);
       ExtensionSettings = builtins.listToAttrs (builtins.map
-        (lang: builtins.nameValuePair
+        (lang: lib.attrsets.nameValuePair
           "langpack-${lang}@firefox.mozilla.org"
           {
             installation_mode = "normal_installed";
diff --git a/nixos/modules/programs/gnupg.nix b/nixos/modules/programs/gnupg.nix
index c755d110170c6..eb983d9ce78a9 100644
--- a/nixos/modules/programs/gnupg.nix
+++ b/nixos/modules/programs/gnupg.nix
@@ -8,22 +8,6 @@ let
   agentSettingsFormat = pkgs.formats.keyValue {
     mkKeyValue = lib.generators.mkKeyValueDefault { } " ";
   };
-
-  xserverCfg = config.services.xserver;
-
-  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
-      "gtk2"
-    else if xserverCfg.enable || config.programs.sway.enable then
-      "gnome3"
-    else
-      "curses";
-
 in
 {
   imports = [
diff --git a/nixos/modules/programs/kdeconnect.nix b/nixos/modules/programs/kdeconnect.nix
index dbdff1e447c5c..76bba40103084 100644
--- a/nixos/modules/programs/kdeconnect.nix
+++ b/nixos/modules/programs/kdeconnect.nix
@@ -21,7 +21,6 @@
       lib.mkIf cfg.enable {
         environment.systemPackages = [
           cfg.package
-          pkgs.sshfs
         ];
         networking.firewall = rec {
           allowedTCPPortRanges = [ { from = 1714; to = 1764; } ];
diff --git a/nixos/modules/programs/pantheon-tweaks.nix b/nixos/modules/programs/pantheon-tweaks.nix
deleted file mode 100644
index b7258e2eb4bfe..0000000000000
--- a/nixos/modules/programs/pantheon-tweaks.nix
+++ /dev/null
@@ -1,17 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-{
-  meta = {
-    maintainers = lib.teams.pantheon.members;
-  };
-
-  ###### interface
-  options = {
-    programs.pantheon-tweaks.enable = lib.mkEnableOption "Pantheon Tweaks, an unofficial system settings panel for Pantheon";
-  };
-
-  ###### implementation
-  config = lib.mkIf config.programs.pantheon-tweaks.enable {
-    services.xserver.desktopManager.pantheon.extraSwitchboardPlugs = [ pkgs.pantheon-tweaks ];
-  };
-}
diff --git a/nixos/modules/programs/pqos-wrapper.nix b/nixos/modules/programs/pqos-wrapper.nix
new file mode 100644
index 0000000000000..82023e67a2ae2
--- /dev/null
+++ b/nixos/modules/programs/pqos-wrapper.nix
@@ -0,0 +1,27 @@
+{ config
+, lib
+, pkgs
+, ...
+}:
+let
+  cfg = config.programs.pqos-wrapper;
+in
+{
+  options.programs.pqos-wrapper = {
+    enable = lib.mkEnableOption "PQoS Wrapper for BenchExec";
+    package = lib.mkPackageOption pkgs "pqos-wrapper" { };
+  };
+
+  config = lib.mkIf cfg.enable {
+    hardware.cpu.x86.msr.enable = true;
+
+    security.wrappers.${cfg.package.meta.mainProgram} = {
+      owner = "nobody";
+      group = config.hardware.cpu.x86.msr.group;
+      source = lib.getExe cfg.package;
+      capabilities = "cap_sys_rawio=eip";
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ lorenzleutgeb ];
+}
diff --git a/nixos/modules/programs/screen.nix b/nixos/modules/programs/screen.nix
index 01af5b4c9597a..4f3cd9fcf9a56 100644
--- a/nixos/modules/programs/screen.nix
+++ b/nixos/modules/programs/screen.nix
@@ -12,7 +12,8 @@ in
       package = lib.mkPackageOptionMD pkgs "screen" { };
 
       screenrc = lib.mkOption {
-        type = with lib.types; nullOr lines;
+        type = lib.types.lines;
+        default = "";
         example = ''
           defscrollback 10000
           startup_message off
@@ -22,20 +23,22 @@ in
     };
   };
 
-  config = {
-    # TODO: Added in 24.05, remove before 24.11
-    assertions = [
-      {
-        assertion = cfg.screenrc != null -> cfg.enable;
-        message = "`programs.screen.screenrc` has been configured, but `programs.screen.enable` is not true";
-      }
-    ];
-  } // lib.mkIf cfg.enable {
-    environment.etc.screenrc = {
-      enable = cfg.screenrc != null;
-      text = cfg.screenrc;
-    };
-    environment.systemPackages = [ cfg.package ];
-    security.pam.services.screen = {};
-  };
+  config = lib.mkMerge [
+    {
+      # TODO: Added in 24.05, remove before 24.11
+      assertions = [
+        {
+          assertion = cfg.screenrc != "" -> cfg.enable;
+          message = "`programs.screen.screenrc` has been configured, but `programs.screen.enable` is not true";
+        }
+      ];
+    }
+    (lib.mkIf cfg.enable {
+      environment.etc.screenrc = {
+        text = cfg.screenrc;
+      };
+      environment.systemPackages = [ cfg.package ];
+      security.pam.services.screen = {};
+    })
+  ];
 }
diff --git a/nixos/modules/programs/steam.nix b/nixos/modules/programs/steam.nix
index d317398495f54..6af8d3aea903b 100644
--- a/nixos/modules/programs/steam.nix
+++ b/nixos/modules/programs/steam.nix
@@ -4,6 +4,8 @@ let
   cfg = config.programs.steam;
   gamescopeCfg = config.programs.gamescope;
 
+  extraCompatPaths = lib.makeSearchPathOutput "steamcompattool" "" cfg.extraCompatPackages;
+
   steam-gamescope = let
     exports = builtins.attrValues (builtins.mapAttrs (n: v: "export ${n}=${v}") cfg.gamescopeSession.env);
   in
@@ -42,7 +44,7 @@ in {
       '';
       apply = steam: steam.override (prev: {
         extraEnv = (lib.optionalAttrs (cfg.extraCompatPackages != [ ]) {
-          STEAM_EXTRA_COMPAT_TOOLS_PATHS = lib.makeSearchPathOutput "steamcompattool" "" cfg.extraCompatPackages;
+          STEAM_EXTRA_COMPAT_TOOLS_PATHS = extraCompatPaths;
         }) // (lib.optionalAttrs cfg.extest.enable {
           LD_PRELOAD = "${pkgs.pkgsi686Linux.extest}/lib/libextest.so";
         }) // (prev.extraEnv or {});
@@ -53,6 +55,7 @@ in {
             then [ package ] ++ extraPackages
             else [ package32 ] ++ extraPackages32;
         in prevLibs ++ additionalLibs;
+        extraPkgs = p: (cfg.extraPackages ++ lib.optionals (prev ? extraPkgs) (prev.extraPkgs p));
       } // lib.optionalAttrs (cfg.gamescopeSession.enable && gamescopeCfg.capSysNice)
       {
         buildFHSEnv = pkgs.buildFHSEnv.override {
@@ -69,6 +72,19 @@ in {
       '';
     };
 
+    extraPackages = lib.mkOption {
+      type = lib.types.listOf lib.types.package;
+      default = [ ];
+      example = lib.literalExpression ''
+        with pkgs; [
+          gamescope
+        ]
+      '';
+      description = ''
+        Additional packages to add to the Steam environment.
+      '';
+    };
+
     extraCompatPackages = lib.mkOption {
       type = lib.types.listOf lib.types.package;
       default = [ ];
@@ -86,6 +102,18 @@ in {
       '';
     };
 
+    fontPackages = lib.mkOption {
+      type = lib.types.listOf lib.types.package;
+      default = config.fonts.packages;
+      defaultText = lib.literalExpression "fonts.packages";
+      example = lib.literalExpression "with pkgs; [ source-han-sans ]";
+      description = ''
+        Font packages to use in Steam.
+
+        Defaults to system fonts, but could be overridden to use other fonts — useful for users who would like to customize CJK fonts used in Steam. According to the [upstream issue](https://github.com/ValveSoftware/steam-for-linux/issues/10422#issuecomment-1944396010), Steam only follows the per-user fontconfig configuration.
+      '';
+    };
+
     remotePlay.openFirewall = lib.mkOption {
       type = lib.types.bool;
       default = false;
@@ -139,6 +167,11 @@ in {
       Load the extest library into Steam, to translate X11 input events to
       uinput events (e.g. for using Steam Input on Wayland)
     '';
+
+    protontricks = {
+      enable = lib.mkEnableOption "protontricks, a simple wrapper for running Winetricks commands for Proton-enabled games";
+      package = lib.mkPackageOption pkgs "protontricks" { };
+    };
   };
 
   config = lib.mkIf cfg.enable {
@@ -158,6 +191,8 @@ in {
       };
     };
 
+    programs.steam.extraPackages = cfg.fontPackages;
+
     programs.gamescope.enable = lib.mkDefault cfg.gamescopeSession.enable;
     services.displayManager.sessionPackages = lib.mkIf cfg.gamescopeSession.enable [ gamescopeSessionFile ];
 
@@ -169,7 +204,8 @@ in {
     environment.systemPackages = [
       cfg.package
       cfg.package.run
-    ] ++ lib.optional cfg.gamescopeSession.enable steam-gamescope;
+    ] ++ lib.optional cfg.gamescopeSession.enable steam-gamescope
+    ++ lib.optional cfg.protontricks.enable (cfg.protontricks.package.override { inherit extraCompatPaths; });
 
     networking.firewall = lib.mkMerge [
       (lib.mkIf (cfg.remotePlay.openFirewall || cfg.localNetworkGameTransfers.openFirewall) {
@@ -192,5 +228,5 @@ in {
     ];
   };
 
-  meta.maintainers = lib.teams.steam;
+  meta.maintainers = lib.teams.steam.members;
 }
diff --git a/nixos/modules/programs/virt-manager.nix b/nixos/modules/programs/virt-manager.nix
index 095db7586a034..9b5fa22268ae9 100644
--- a/nixos/modules/programs/virt-manager.nix
+++ b/nixos/modules/programs/virt-manager.nix
@@ -2,15 +2,27 @@
 
 let
   cfg = config.programs.virt-manager;
-in {
+in
+{
   options.programs.virt-manager = {
     enable = lib.mkEnableOption "virt-manager, an UI for managing virtual machines in libvirt";
 
-    package = lib.mkPackageOption pkgs "virt-manager" {};
+    package = lib.mkPackageOption pkgs "virt-manager" { };
   };
 
   config = lib.mkIf cfg.enable {
     environment.systemPackages = [ cfg.package ];
-    programs.dconf.enable = true;
+    programs.dconf = {
+      profiles.user.databases = [
+        {
+          settings = {
+            "org/virt-manager/virt-manager/connections" = {
+              autoconnect = [ "qemu:///system" ];
+              uris = [ "qemu:///system" ];
+            };
+          };
+        }
+      ];
+    };
   };
 }
diff --git a/nixos/modules/programs/wayland/hyprland.nix b/nixos/modules/programs/wayland/hyprland.nix
index c963429f2e2a9..f5ca741f94328 100644
--- a/nixos/modules/programs/wayland/hyprland.nix
+++ b/nixos/modules/programs/wayland/hyprland.nix
@@ -1,46 +1,41 @@
-{ config
-, lib
-, pkgs
-, ...
-}:
+{ config, lib, pkgs, ... }:
+
 let
   cfg = config.programs.hyprland;
 
-  finalPortalPackage = cfg.portalPackage.override {
-    hyprland = cfg.finalPackage;
-  };
+  wayland-lib = import ./lib.nix { inherit lib; };
 in
 {
   options.programs.hyprland = {
-    enable = lib.mkEnableOption null // {
-      description = ''
-        Whether to enable Hyprland, the dynamic tiling Wayland compositor that doesn't sacrifice on its looks.
-
-        You can manually launch Hyprland by executing {command}`Hyprland` on a TTY.
-
-        A configuration file will be generated in {file}`~/.config/hypr/hyprland.conf`.
-        See <https://wiki.hyprland.org> for more information.
+    enable = lib.mkEnableOption ''
+      Hyprland, the dynamic tiling Wayland compositor that doesn't sacrifice on its looks.
+      You can manually launch Hyprland by executing {command}`Hyprland` on a TTY.
+      A configuration file will be generated in {file}`~/.config/hypr/hyprland.conf`.
+      See <https://wiki.hyprland.org> for more information'';
+
+    package = lib.mkPackageOption pkgs "hyprland" {
+      extraDescription = ''
+        If the package is not overridable with `enableXWayland`, then the module option
+        {option}`xwayland` will have no effect.
       '';
-    };
-
-    package = lib.mkPackageOption pkgs "hyprland" { };
-
-    finalPackage = lib.mkOption {
-      type = lib.types.package;
-      readOnly = true;
-      default = cfg.package.override {
+    } // {
+      apply = p: wayland-lib.genFinalPackage p {
         enableXWayland = cfg.xwayland.enable;
       };
-      defaultText = lib.literalExpression
-        "`programs.hyprland.package` with applied configuration";
-      description = ''
-        The Hyprland package after applying configuration.
-      '';
     };
 
-    portalPackage = lib.mkPackageOption pkgs "xdg-desktop-portal-hyprland" { };
+    portalPackage = lib.mkPackageOption pkgs "xdg-desktop-portal-hyprland" {
+      extraDescription = ''
+        If the package is not overridable with `hyprland`, then the Hyprland package
+        used by the portal may differ from the one set in the module option {option}`package`.
+      '';
+    } // {
+      apply = p: wayland-lib.genFinalPackage p {
+        hyprland = cfg.package;
+      };
+    };
 
-    xwayland.enable = lib.mkEnableOption ("XWayland") // { default = true; };
+    xwayland.enable = lib.mkEnableOption "XWayland" // { default = true; };
 
     systemd.setPath.enable = lib.mkEnableOption null // {
       default = true;
@@ -53,33 +48,30 @@ in
     };
   };
 
-  config = lib.mkIf cfg.enable {
-    environment.systemPackages = [ cfg.finalPackage ];
-
-    fonts.enableDefaultPackages = lib.mkDefault true;
-    hardware.opengl.enable = lib.mkDefault true;
+  config = lib.mkIf cfg.enable (lib.mkMerge [
+    {
+      environment.systemPackages = [ cfg.package ];
 
-    programs = {
-      dconf.enable = lib.mkDefault true;
-      xwayland.enable = lib.mkDefault cfg.xwayland.enable;
-    };
-
-    security.polkit.enable = true;
+      # To make a Hyprland session available if a display manager like SDDM is enabled:
+      services.displayManager.sessionPackages = [ cfg.package ];
 
-    services.displayManager.sessionPackages = [ cfg.finalPackage ];
+      xdg.portal = {
+        extraPortals = [ cfg.portalPackage ];
+        configPackages = lib.mkDefault [ cfg.package ];
+      };
 
-    xdg.portal = {
-      enable = lib.mkDefault true;
-      extraPortals = [ finalPortalPackage ];
-      configPackages = lib.mkDefault [ cfg.finalPackage ];
-    };
+      systemd = lib.mkIf cfg.systemd.setPath.enable {
+        user.extraConfig = ''
+          DefaultEnvironment="PATH=$PATH:/run/current-system/sw/bin:/etc/profiles/per-user/%u/bin:/run/wrappers/bin"
+        '';
+      };
+    }
 
-    systemd = lib.mkIf cfg.systemd.setPath.enable {
-      user.extraConfig = ''
-        DefaultEnvironment="PATH=$PATH:/run/current-system/sw/bin:/etc/profiles/per-user/%u/bin:/run/wrappers/bin"
-      '';
-    };
-  };
+    (import ./wayland-session.nix {
+      inherit lib pkgs;
+      xwayland = cfg.xwayland.enable;
+    })
+  ]);
 
   imports = [
     (lib.mkRemovedOptionModule
diff --git a/nixos/modules/programs/wayland/lib.nix b/nixos/modules/programs/wayland/lib.nix
new file mode 100644
index 0000000000000..0f275d3f18c56
--- /dev/null
+++ b/nixos/modules/programs/wayland/lib.nix
@@ -0,0 +1,12 @@
+{ lib }:
+
+{
+  genFinalPackage = pkg: args:
+    let
+      expectedArgs = with lib;
+        lib.naturalSort (lib.attrNames args);
+      existingArgs = with lib;
+        naturalSort (intersectLists expectedArgs (attrNames (functionArgs pkg.override)));
+    in
+      if existingArgs != expectedArgs then pkg else pkg.override args;
+}
diff --git a/nixos/modules/programs/wayland/river.nix b/nixos/modules/programs/wayland/river.nix
index 6f8bafb155064..0980bd28cf828 100644
--- a/nixos/modules/programs/wayland/river.nix
+++ b/nixos/modules/programs/wayland/river.nix
@@ -1,37 +1,40 @@
-{
-  config,
-  pkgs,
-  lib,
-  ...
-}:
+{ config, lib, pkgs, ... }:
+
 let
   cfg = config.programs.river;
-in {
+
+  wayland-lib = import ./lib.nix { inherit lib; };
+in
+{
   options.programs.river = {
     enable = lib.mkEnableOption "river, a dynamic tiling Wayland compositor";
 
     package = lib.mkPackageOption pkgs "river" {
       nullable = true;
       extraDescription = ''
+        If the package is not overridable with `xwaylandSupport`, then the module option
+        {option}`xwayland` will have no effect.
+
         Set to `null` to not add any River package to your path.
         This should be done if you want to use the Home Manager River module to install River.
       '';
+    } // {
+      apply = p: if p == null then null else
+        wayland-lib.genFinalPackage p {
+          xwaylandSupport = cfg.xwayland.enable;
+        };
     };
 
+    xwayland.enable = lib.mkEnableOption "XWayland" // { default = true; };
+
     extraPackages = lib.mkOption {
       type = with lib.types; listOf package;
-      default = with pkgs; [
-        swaylock
-        foot
-        dmenu
-      ];
+      default = with pkgs; [ swaylock foot dmenu ];
       defaultText = lib.literalExpression ''
         with pkgs; [ swaylock foot dmenu ];
       '';
       example = lib.literalExpression ''
-        with pkgs; [
-          termite rofi light
-        ]
+        with pkgs; [ termite rofi light ]
       '';
       description = ''
         Extra packages to be installed system wide. See
@@ -41,19 +44,22 @@ in {
     };
   };
 
-  config =
-    lib.mkIf cfg.enable (lib.mkMerge [
-      {
-        environment.systemPackages = lib.optional (cfg.package != null) cfg.package ++ cfg.extraPackages;
+  config = lib.mkIf cfg.enable (lib.mkMerge [
+    {
+      environment.systemPackages = lib.optional (cfg.package != null) cfg.package ++ cfg.extraPackages;
+
+      # To make a river session available if a display manager like SDDM is enabled:
+      services.displayManager.sessionPackages = lib.optional (cfg.package != null) cfg.package;
 
-        # To make a river session available if a display manager like SDDM is enabled:
-        services.displayManager.sessionPackages = lib.optionals (cfg.package != null) [ cfg.package ];
+      # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1050913
+      xdg.portal.config.river.default = lib.mkDefault [ "wlr" "gtk" ];
+    }
 
-        # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1050913
-        xdg.portal.config.river.default = lib.mkDefault [ "wlr" "gtk" ];
-      }
-      (import ./wayland-session.nix { inherit lib pkgs; })
-    ]);
+    (import ./wayland-session.nix {
+      inherit lib pkgs;
+      xwayland = cfg.xwayland.enable;
+    })
+  ]);
 
   meta.maintainers = with lib.maintainers; [ GaetanLepage ];
 }
diff --git a/nixos/modules/programs/wayland/sway.nix b/nixos/modules/programs/wayland/sway.nix
index cec634b6b0338..31821a84a5bd7 100644
--- a/nixos/modules/programs/wayland/sway.nix
+++ b/nixos/modules/programs/wayland/sway.nix
@@ -1,52 +1,11 @@
-{ config, pkgs, lib, ... }:
+{ config, lib, pkgs, ... }:
 
 let
   cfg = config.programs.sway;
 
-  wrapperOptions = lib.types.submodule {
-    options =
-      let
-        mkWrapperFeature  = default: description: lib.mkOption {
-          type = lib.types.bool;
-          inherit default;
-          example = !default;
-          description = "Whether to make use of the ${description}";
-        };
-      in {
-        base = mkWrapperFeature true ''
-          base wrapper to execute extra session commands and prepend a
-          dbus-run-session to the sway command.
-        '';
-        gtk = mkWrapperFeature false ''
-          wrapGAppsHook wrapper to execute sway with required environment
-          variables for GTK applications.
-        '';
-    };
-  };
-
-  genFinalPackage = pkg:
-    let
-      expectedArgs = lib.naturalSort [
-        "extraSessionCommands"
-        "extraOptions"
-        "withBaseWrapper"
-        "withGtkWrapper"
-        "isNixOS"
-      ];
-      existedArgs = with lib;
-        naturalSort
-        (intersectLists expectedArgs (attrNames (functionArgs pkg.override)));
-    in if existedArgs != expectedArgs then
-      pkg
-    else
-      pkg.override {
-        extraSessionCommands = cfg.extraSessionCommands;
-        extraOptions = cfg.extraOptions;
-        withBaseWrapper = cfg.wrapperFeatures.base;
-        withGtkWrapper = cfg.wrapperFeatures.gtk;
-        isNixOS = true;
-      };
-in {
+  wayland-lib = import ./lib.nix { inherit lib; };
+in
+{
   options.programs.sway = {
     enable = lib.mkEnableOption ''
       Sway, the i3-compatible tiling Wayland compositor. You can manually launch
@@ -55,28 +14,36 @@ in {
       <https://github.com/swaywm/sway/wiki> and
       "man 5 sway" for more information'';
 
-    package = lib.mkOption {
-      type = with lib.types; nullOr package;
-      default = pkgs.sway;
-      apply = p: if p == null then null else genFinalPackage p;
-      defaultText = lib.literalExpression "pkgs.sway";
-      description = ''
-        Sway package to use. If the package does not contain the override arguments
-        `extraSessionCommands`, `extraOptions`, `withBaseWrapper`, `withGtkWrapper`,
-        `isNixOS`, then the module options {option}`wrapperFeatures`,
-        {option}`wrapperFeatures` and {option}`wrapperFeatures` will have no effect.
-        Set to `null` to not add any Sway package to your path. This should be done if
-        you want to use the Home Manager Sway module to install Sway.
+    package = lib.mkPackageOption pkgs "sway" {
+      nullable = true;
+      extraDescription = ''
+        If the package is not overridable with `extraSessionCommands`, `extraOptions`,
+        `withBaseWrapper`, `withGtkWrapper`, `enableXWayland` and `isNixOS`,
+        then the module options {option}`wrapperFeatures`, {option}`extraSessionCommands`,
+        {option}`extraOptions` and {option}`xwayland` will have no effect.
+
+        Set to `null` to not add any Sway package to your path.
+        This should be done if you want to use the Home Manager Sway module to install Sway.
       '';
+    } // {
+      apply = p: if p == null then null else
+        wayland-lib.genFinalPackage p {
+          extraSessionCommands = cfg.extraSessionCommands;
+          extraOptions = cfg.extraOptions;
+          withBaseWrapper = cfg.wrapperFeatures.base;
+          withGtkWrapper = cfg.wrapperFeatures.gtk;
+          enableXWayland = cfg.xwayland.enable;
+          isNixOS = true;
+        };
     };
 
-    wrapperFeatures = lib.mkOption {
-      type = wrapperOptions;
-      default = { };
-      example = { gtk = true; };
-      description = ''
-        Attribute set of features to enable in the wrapper.
-      '';
+    wrapperFeatures = {
+      base = lib.mkEnableOption ''
+        the base wrapper to execute extra session commands and prepend a
+        dbus-run-session to the sway command'' // { default = true; };
+      gtk = lib.mkEnableOption ''
+        the wrapGAppsHook wrapper to execute sway with required environment
+        variables for GTK applications'';
     };
 
     extraSessionCommands = lib.mkOption {
@@ -114,19 +81,16 @@ in {
       '';
     };
 
+    xwayland.enable = lib.mkEnableOption "XWayland" // { default = true; };
+
     extraPackages = lib.mkOption {
       type = with lib.types; listOf package;
-      default = with pkgs; [
-        swaylock swayidle foot dmenu wmenu
-      ];
+      default = with pkgs; [ swaylock swayidle foot dmenu wmenu ];
       defaultText = lib.literalExpression ''
         with pkgs; [ swaylock swayidle foot dmenu wmenu ];
       '';
       example = lib.literalExpression ''
-        with pkgs; [
-          i3status i3status-rust
-          termite rofi light
-        ]
+        with pkgs; [ i3status i3status-rust termite rofi light ]
       '';
       description = ''
         Extra packages to be installed system wide. See
@@ -135,46 +99,50 @@ in {
         for a list of useful software.
       '';
     };
-
   };
 
-  config = lib.mkIf cfg.enable
-    (lib.mkMerge [
-      {
-        assertions = [
-          {
-            assertion = cfg.extraSessionCommands != "" -> cfg.wrapperFeatures.base;
-            message = ''
-              The extraSessionCommands for Sway will not be run if
-              wrapperFeatures.base is disabled.
-            '';
-          }
-        ];
-
-        environment = {
-          systemPackages = lib.optional (cfg.package != null) cfg.package ++ cfg.extraPackages;
-          # Needed for the default wallpaper:
-          pathsToLink = lib.optionals (cfg.package != null) [ "/share/backgrounds/sway" ];
-          etc = {
-            "sway/config.d/nixos.conf".source = pkgs.writeText "nixos.conf" ''
-              # Import the most important environment variables into the D-Bus and systemd
-              # user environments (e.g. required for screen sharing and Pinentry prompts):
-              exec dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY SWAYSOCK XDG_CURRENT_DESKTOP
-            '';
-          } // lib.optionalAttrs (cfg.package != null) {
-            "sway/config".source = lib.mkOptionDefault "${cfg.package}/etc/sway/config";
-          };
+  config = lib.mkIf cfg.enable (lib.mkMerge [
+    {
+      assertions = [
+        {
+          assertion = cfg.extraSessionCommands != "" -> cfg.wrapperFeatures.base;
+          message = ''
+            The extraSessionCommands for Sway will not be run if wrapperFeatures.base is disabled.
+          '';
+        }
+      ];
+
+      environment = {
+        systemPackages = lib.optional (cfg.package != null) cfg.package ++ cfg.extraPackages;
+
+        # Needed for the default wallpaper:
+        pathsToLink = lib.optional (cfg.package != null) "/share/backgrounds/sway";
+
+        etc = {
+          "sway/config.d/nixos.conf".source = pkgs.writeText "nixos.conf" ''
+            # Import the most important environment variables into the D-Bus and systemd
+            # user environments (e.g. required for screen sharing and Pinentry prompts):
+            exec dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY SWAYSOCK XDG_CURRENT_DESKTOP
+          '';
+        } // lib.optionalAttrs (cfg.package != null) {
+          "sway/config".source = lib.mkOptionDefault "${cfg.package}/etc/sway/config";
         };
+      };
+
+      programs.gnupg.agent.pinentryPackage = lib.mkDefault pkgs.pinentry-gnome3;
 
-        programs.gnupg.agent.pinentryPackage = lib.mkDefault pkgs.pinentry-gnome3;
+      # To make a Sway session available if a display manager like SDDM is enabled:
+      services.displayManager.sessionPackages = lib.optional (cfg.package != null) cfg.package;
 
-        # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1050913
-        xdg.portal.config.sway.default = lib.mkDefault [ "wlr" "gtk" ];
+      # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1050913
+      xdg.portal.config.sway.default = lib.mkDefault [ "wlr" "gtk" ];
+    }
 
-        # To make a Sway session available if a display manager like SDDM is enabled:
-        services.displayManager.sessionPackages = lib.optionals (cfg.package != null) [ cfg.package ]; }
-      (import ./wayland-session.nix { inherit lib pkgs; })
-    ]);
+    (import ./wayland-session.nix {
+      inherit lib pkgs;
+      xwayland = cfg.xwayland.enable;
+    })
+  ]);
 
   meta.maintainers = with lib.maintainers; [ primeos colemickens ];
 }
diff --git a/nixos/modules/programs/wayland/wayland-session.nix b/nixos/modules/programs/wayland/wayland-session.nix
index 47ee0788e0f38..e9c12da156abc 100644
--- a/nixos/modules/programs/wayland/wayland-session.nix
+++ b/nixos/modules/programs/wayland/wayland-session.nix
@@ -1,23 +1,18 @@
-{ lib, pkgs, ... }: {
-    security = {
-      polkit.enable = true;
-      pam.services.swaylock = {};
-    };
+{ lib, pkgs, xwayland ? true }:
 
-    hardware.opengl.enable = lib.mkDefault true;
-    fonts.enableDefaultPackages = lib.mkDefault true;
+{
+  security = {
+    polkit.enable = true;
+    pam.services.swaylock = {};
+  };
 
-    programs = {
-      dconf.enable = lib.mkDefault true;
-      xwayland.enable = lib.mkDefault true;
-    };
+  hardware.opengl.enable = lib.mkDefault true;
+  fonts.enableDefaultPackages = lib.mkDefault true;
 
-    xdg.portal = {
-      enable = lib.mkDefault true;
+  programs = {
+    dconf.enable = lib.mkDefault true;
+    xwayland.enable = lib.mkDefault xwayland;
+  };
 
-      extraPortals = [
-        # For screen sharing
-        pkgs.xdg-desktop-portal-wlr
-      ];
-    };
+  xdg.portal.wlr.enable = lib.mkDefault true;
 }
diff --git a/nixos/modules/programs/yazi.nix b/nixos/modules/programs/yazi.nix
index 5905f2afb946d..d9f38d8d81185 100644
--- a/nixos/modules/programs/yazi.nix
+++ b/nixos/modules/programs/yazi.nix
@@ -5,7 +5,7 @@ let
 
   settingsFormat = pkgs.formats.toml { };
 
-  names = [ "yazi" "theme" "keymap" ];
+  files = [ "yazi" "theme" "keymap" ];
 in
 {
   options.programs.yazi = {
@@ -15,7 +15,7 @@ in
 
     settings = lib.mkOption {
       type = with lib.types; submodule {
-        options = lib.listToAttrs (map
+        options = (lib.listToAttrs (map
           (name: lib.nameValuePair name (lib.mkOption {
             inherit (settingsFormat) type;
             default = { };
@@ -25,26 +25,65 @@ in
               See https://yazi-rs.github.io/docs/configuration/${name}/ for documentation.
             '';
           }))
-          names);
+          files));
       };
       default = { };
       description = ''
         Configuration included in `$YAZI_CONFIG_HOME`.
       '';
     };
+
+    initLua = lib.mkOption {
+      type = with lib.types; nullOr path;
+      default = null;
+      description = ''
+        The init.lua for Yazi itself.
+      '';
+      example = lib.literalExpression "./init.lua";
+    };
+
+    plugins = lib.mkOption {
+      type = with lib.types; attrsOf (oneOf [ path package ]);
+      default = { };
+      description = ''
+        Lua plugins.
+
+        See https://yazi-rs.github.io/docs/plugins/overview/ for documentation.
+      '';
+      example = lib.literalExpression ''
+        {
+          foo = ./foo;
+          bar = pkgs.bar;
+        }
+      '';
+    };
+
+    flavors = lib.mkOption {
+      type = with lib.types; attrsOf (oneOf [ path package ]);
+      default = { };
+      description = ''
+        Pre-made themes.
+
+        See https://yazi-rs.github.io/docs/flavors/overview/ for documentation.
+      '';
+      example = lib.literalExpression ''
+        {
+          foo = ./foo;
+          bar = pkgs.bar;
+        }
+      '';
+    };
+
   };
 
   config = lib.mkIf cfg.enable {
-    environment = {
-      systemPackages = [ cfg.package ];
-      variables.YAZI_CONFIG_HOME = "/etc/yazi/";
-      etc = lib.attrsets.mergeAttrsList (map
-        (name: lib.optionalAttrs (cfg.settings.${name} != { }) {
-          "yazi/${name}.toml".source = settingsFormat.generate "${name}.toml" cfg.settings.${name};
-        })
-        names);
-    };
+    environment.systemPackages = [
+      (cfg.package.override {
+        inherit (cfg) settings initLua plugins flavors;
+      })
+    ];
   };
+
   meta = {
     maintainers = with lib.maintainers; [ linsui ];
   };
diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix
index 01985995a651d..d4661a19188c8 100644
--- a/nixos/modules/rename.nix
+++ b/nixos/modules/rename.nix
@@ -40,12 +40,16 @@ in
     (mkRemovedOptionModule [ "networking" "vpnc" ] "Use environment.etc.\"vpnc/service.conf\" instead.")
     (mkRemovedOptionModule [ "networking" "wicd" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "programs" "gnome-documents" ] "The corresponding package was removed from nixpkgs.")
+    (mkRemovedOptionModule [ "programs" "pantheon-tweaks" ] ''
+      pantheon-tweaks is no longer a switchboard plugin but an independent app,
+      adding the package to environment.systemPackages is sufficient.
+    '')
     (mkRemovedOptionModule [ "programs" "tilp2" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "programs" "way-cooler" ] ("way-cooler is abandoned by its author: " +
       "https://way-cooler.org/blog/2020/01/09/way-cooler-post-mortem.html"))
     (mkRemovedOptionModule [ "security" "hideProcessInformation" ] ''
-        The hidepid module was removed, since the underlying machinery
-        is broken when using cgroups-v2.
+      The hidepid module was removed, since the underlying machinery
+      is broken when using cgroups-v2.
     '')
     (mkRemovedOptionModule [ "services" "baget" "enable" ] "The baget module was removed due to the upstream package being unmaintained.")
     (mkRemovedOptionModule [ "services" "beegfs" ] "The BeeGFS module has been removed")
diff --git a/nixos/modules/services/admin/pgadmin.nix b/nixos/modules/services/admin/pgadmin.nix
index ead0c3c6c9a34..b3dd3c78874c2 100644
--- a/nixos/modules/services/admin/pgadmin.nix
+++ b/nixos/modules/services/admin/pgadmin.nix
@@ -152,7 +152,8 @@ in
         # Check here for password length to prevent pgadmin from starting
         # and presenting a hard to find error message
         # see https://github.com/NixOS/nixpkgs/issues/270624
-        PW_LENGTH=$(wc -m < ${escapeShellArg cfg.initialPasswordFile})
+        PW_FILE="$CREDENTIALS_DIRECTORY/initial_password"
+        PW_LENGTH=$(wc -m < "$PW_FILE")
         if [ $PW_LENGTH -lt ${toString cfg.minimumPasswordLength} ]; then
             echo "Password must be at least ${toString cfg.minimumPasswordLength} characters long"
             exit 1
@@ -162,7 +163,7 @@ in
           echo ${escapeShellArg cfg.initialEmail}
 
           # file might not contain newline. echo hack fixes that.
-          PW=$(cat ${escapeShellArg cfg.initialPasswordFile})
+          PW=$(cat "$PW_FILE")
 
           # Password:
           echo "$PW"
@@ -181,6 +182,8 @@ in
         LogsDirectory = "pgadmin";
         StateDirectory = "pgadmin";
         ExecStart = "${cfg.package}/bin/pgadmin4";
+        LoadCredential = [ "initial_password:${cfg.initialPasswordFile}" ]
+          ++ optional cfg.emailServer.enable "email_password:${cfg.emailServer.passwordFile}";
       };
     };
 
@@ -193,7 +196,8 @@ in
 
     environment.etc."pgadmin/config_system.py" = {
       text = lib.optionalString cfg.emailServer.enable ''
-        with open("${cfg.emailServer.passwordFile}") as f:
+        import os
+        with open(os.path.join(os.environ['CREDENTIALS_DIRECTORY'], 'email_password')) as f:
           pw = f.read()
         MAIL_PASSWORD = pw
       '' + formatPy cfg.settings;
diff --git a/nixos/modules/services/audio/navidrome.nix b/nixos/modules/services/audio/navidrome.nix
index ca1cd6ca43af0..a9db9228827a2 100644
--- a/nixos/modules/services/audio/navidrome.nix
+++ b/nixos/modules/services/audio/navidrome.nix
@@ -6,8 +6,18 @@
 }:
 
 let
-  inherit (lib) mkEnableOption mkPackageOption mkOption maintainers;
-  inherit (lib.types) bool str;
+  inherit (lib)
+    mkEnableOption
+    mkPackageOption
+    mkOption
+    maintainers
+    ;
+  inherit (lib.types)
+    bool
+    port
+    str
+    submodule
+    ;
   cfg = config.services.navidrome;
   settingsFormat = pkgs.formats.json { };
 in
@@ -20,11 +30,24 @@ in
       package = mkPackageOption pkgs "navidrome" { };
 
       settings = mkOption {
-        type = settingsFormat.type;
-        default = {
-          Address = "127.0.0.1";
-          Port = 4533;
+        type = submodule {
+          freeformType = settingsFormat.type;
+
+          options = {
+            Address = mkOption {
+              default = "127.0.0.1";
+              description = "Address to run Navidrome on.";
+              type = str;
+            };
+
+            Port = mkOption {
+              default = 4533;
+              description = "Port to run Navidrome on.";
+              type = port;
+            };
+          };
         };
+        default = { };
         example = {
           MusicFolder = "/mnt/music";
         };
@@ -134,5 +157,5 @@ in
 
       networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.settings.Port ];
     };
-    meta.maintainers = with maintainers; [ nu-nu-ko ];
+  meta.maintainers = with maintainers; [ nu-nu-ko ];
 }
diff --git a/nixos/modules/services/backup/borgbackup.nix b/nixos/modules/services/backup/borgbackup.nix
index 04f971008073e..a3c0715c9e607 100644
--- a/nixos/modules/services/backup/borgbackup.nix
+++ b/nixos/modules/services/backup/borgbackup.nix
@@ -361,7 +361,7 @@ in {
             type = types.bool;
             example = true;
             description = ''
-              Set the `persistentTimer` option for the
+              Set the `Persistent` option for the
               {manpage}`systemd.timer(5)`
               which triggers the backup immediately if the last trigger
               was missed (e.g. if the system was powered down).
diff --git a/nixos/modules/services/continuous-integration/hydra/default.nix b/nixos/modules/services/continuous-integration/hydra/default.nix
index 23f07eb64b92d..b516c3d6192cb 100644
--- a/nixos/modules/services/continuous-integration/hydra/default.nix
+++ b/nixos/modules/services/continuous-integration/hydra/default.nix
@@ -335,7 +335,7 @@ in
           mkdir -m 0700 -p ${baseDir}/queue-runner
           mkdir -m 0750 -p ${baseDir}/build-logs
           mkdir -m 0750 -p ${baseDir}/runcommand-logs
-          chown hydra-queue-runner.hydra \
+          chown hydra-queue-runner:hydra \
             ${baseDir}/queue-runner \
             ${baseDir}/build-logs \
             ${baseDir}/runcommand-logs
diff --git a/nixos/modules/services/desktop-managers/lomiri.nix b/nixos/modules/services/desktop-managers/lomiri.nix
index e11867b691071..06930b15a0084 100644
--- a/nixos/modules/services/desktop-managers/lomiri.nix
+++ b/nixos/modules/services/desktop-managers/lomiri.nix
@@ -38,6 +38,7 @@ in {
       ]);
     };
 
+    hardware.pulseaudio.enable = lib.mkDefault true;
     networking.networkmanager.enable = lib.mkDefault true;
 
     systemd.packages = with pkgs.lomiri; [
@@ -71,8 +72,12 @@ in {
       enable = true;
       packages = (with pkgs; [
         ayatana-indicator-datetime
+        ayatana-indicator-display
         ayatana-indicator-messages
+        ayatana-indicator-power
         ayatana-indicator-session
+      ] ++ lib.optionals (config.hardware.pulseaudio.enable || config.services.pipewire.pulse.enable) [
+        ayatana-indicator-sound
       ]) ++ (with pkgs.lomiri; [
         telephony-service
       ] ++ lib.optionals config.networking.networkmanager.enable [
diff --git a/nixos/modules/services/desktop-managers/plasma6.nix b/nixos/modules/services/desktop-managers/plasma6.nix
index 842b0716b928d..83f6b5bc0ea10 100644
--- a/nixos/modules/services/desktop-managers/plasma6.nix
+++ b/nixos/modules/services/desktop-managers/plasma6.nix
@@ -60,10 +60,8 @@ in {
     qt.enable = true;
     environment.systemPackages = with kdePackages; let
       requiredPackages = [
-        # Hack? To make everything run on Wayland
-        qtwayland
-        # Needed to render SVG icons
-        qtsvg
+        qtwayland # Hack? To make everything run on Wayland
+        qtsvg # Needed to render SVG icons
 
         # Frameworks with globally loadable bits
         frameworkintegration # provides Qt plugin
@@ -75,6 +73,9 @@ in {
         kiconthemes # provides Qt plugins
         kimageformats # provides Qt plugins
         kio # provides helper service + a bunch of other stuff
+        kio-admin # managing files as admin
+        kio-extras # stuff for MTP, AFC, etc
+        kio-fuse # fuse interface for KIO
         kpackage # provides kpackagetool tool
         kservice # provides kbuildsycoca6 tool
         kwallet # provides helper service
@@ -87,30 +88,26 @@ in {
         # Core Plasma parts
         kwin
         pkgs.xwayland
-
         kscreen
         libkscreen
-
         kscreenlocker
-
         kactivitymanagerd
         kde-cli-tools
-        kglobalacceld
+        kglobalacceld # keyboard shortcut daemon
         kwrited # wall message proxy, not to be confused with kwrite
-
-        milou
-        polkit-kde-agent-1
-
+        baloo # system indexer
+        milou # search engine atop baloo
+        kdegraphics-thumbnailers # pdf etc thumbnailer
+        polkit-kde-agent-1 # polkit auth ui
         plasma-desktop
         plasma-workspace
-
-        # Crash handler
-        drkonqi
+        drkonqi # crash handler
+        kde-inotify-survey # warns the user on low inotifywatch limits
 
         # Application integration
         libplasma # provides Kirigami platform theme
         plasma-integration # provides Qt platform theme
-        kde-gtk-config
+        kde-gtk-config # syncs KDE settings to GTK
 
         # Artwork + themes
         breeze
@@ -124,37 +121,21 @@ in {
 
         # 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
@@ -162,6 +143,10 @@ in {
         kate
         khelpcenter
         print-manager
+        dolphin
+        dolphin-plugins
+        spectacle
+        ffmpegthumbs
       ];
     in
       requiredPackages
diff --git a/nixos/modules/services/desktops/espanso.nix b/nixos/modules/services/desktops/espanso.nix
index 4ef6724dda0a0..a9b15b2659459 100644
--- a/nixos/modules/services/desktops/espanso.nix
+++ b/nixos/modules/services/desktops/espanso.nix
@@ -6,19 +6,25 @@ in {
   meta = { maintainers = with lib.maintainers; [ numkem ]; };
 
   options = {
-    services.espanso = { enable = options.mkEnableOption "Espanso"; };
+    services.espanso = {
+      enable = mkEnableOption "Espanso";
+      package = mkPackageOption pkgs "espanso" {
+        example = "pkgs.espanso-wayland";
+      };
+    };
   };
 
   config = mkIf cfg.enable {
+    services.espanso.package = mkIf cfg.wayland pkgs.espanso-wayland;
     systemd.user.services.espanso = {
       description = "Espanso daemon";
       serviceConfig = {
-        ExecStart = "${pkgs.espanso}/bin/espanso daemon";
+        ExecStart = "${lib.getExe cfg.package} daemon";
         Restart = "on-failure";
       };
       wantedBy = [ "default.target" ];
     };
 
-    environment.systemPackages = [ pkgs.espanso ];
+    environment.systemPackages = [ cfg.package ];
   };
 }
diff --git a/nixos/modules/services/display-managers/default.nix b/nixos/modules/services/display-managers/default.nix
index 6fa8556e39bee..feba4b163ccd2 100644
--- a/nixos/modules/services/display-managers/default.nix
+++ b/nixos/modules/services/display-managers/default.nix
@@ -113,7 +113,7 @@ in
         type = lib.types.nullOr lib.types.str // {
           description = "session name";
           check = d:
-            lib.assertMsg (d != null -> (lib.types.str.check d && lib.elem d config.services.displayManager.sessionData.sessionNames)) ''
+            lib.assertMsg (d != null -> (lib.types.str.check d && lib.elem d cfg.sessionData.sessionNames)) ''
                 Default graphical session, '${d}', not found.
                 Valid names for 'services.displayManager.defaultSession' are:
                   ${lib.concatStringsSep "\n  " cfg.sessionData.sessionNames}
@@ -187,7 +187,7 @@ in
 
     services.displayManager.sessionData = {
       desktops = installedSessions;
-      sessionNames = lib.concatMap (p: p.providedSessions) config.services.displayManager.sessionPackages;
+      sessionNames = lib.concatMap (p: p.providedSessions) cfg.sessionPackages;
       # We do not want to force users to set defaultSession when they have only single DE.
       autologinSession =
         if cfg.defaultSession != null then
diff --git a/nixos/modules/services/display-managers/sddm.nix b/nixos/modules/services/display-managers/sddm.nix
index a6bfa213fe380..54356f7bb6171 100644
--- a/nixos/modules/services/display-managers/sddm.nix
+++ b/nixos/modules/services/display-managers/sddm.nix
@@ -66,7 +66,14 @@ let
       HideShells = "/run/current-system/sw/bin/nologin";
     };
 
-    X11 = optionalAttrs xcfg.enable {
+    Wayland = {
+      EnableHiDPI = cfg.enableHidpi;
+      SessionDir = "${dmcfg.sessionData.desktops}/share/wayland-sessions";
+      CompositorCommand = lib.optionalString cfg.wayland.enable cfg.wayland.compositorCommand;
+    };
+
+  } // optionalAttrs xcfg.enable {
+    X11 = {
       MinimumVT = if xcfg.tty != null then xcfg.tty else 7;
       ServerPath = toString xserverWrapper;
       XephyrPath = "${pkgs.xorg.xorgserver.out}/bin/Xephyr";
@@ -77,12 +84,6 @@ let
       DisplayStopCommand = toString Xstop;
       EnableHiDPI = cfg.enableHidpi;
     };
-
-    Wayland = {
-      EnableHiDPI = cfg.enableHidpi;
-      SessionDir = "${dmcfg.sessionData.desktops}/share/wayland-sessions";
-      CompositorCommand = lib.optionalString cfg.wayland.enable cfg.wayland.compositorCommand;
-    };
   } // optionalAttrs dmcfg.autoLogin.enable {
     Autologin = {
       User = dmcfg.autoLogin.user;
diff --git a/nixos/modules/services/games/archisteamfarm.nix b/nixos/modules/services/games/archisteamfarm.nix
index 33898f8387e99..c9c41d6f4eb5e 100644
--- a/nixos/modules/services/games/archisteamfarm.nix
+++ b/nixos/modules/services/games/archisteamfarm.nix
@@ -164,8 +164,11 @@ in
   };
 
   config = lib.mkIf cfg.enable {
-    # TODO: drop with 24.11
-    services.archisteamfarm.dataDir = lib.mkIf (lib.versionAtLeast config.system.stateVersion "24.05") (lib.mkDefault "/var/lib/asf");
+    services.archisteamfarm = {
+      # TODO: drop with 24.11
+      dataDir = lib.mkIf (lib.versionAtLeast config.system.stateVersion "24.05") (lib.mkDefault "/var/lib/asf");
+      settings.IPC = lib.mkIf (!cfg.web-ui.enable) false;
+    };
 
     users = {
       users.archisteamfarm = {
diff --git a/nixos/modules/services/hardware/kanata.nix b/nixos/modules/services/hardware/kanata.nix
index 46af3e36b9859..60fb33881f256 100644
--- a/nixos/modules/services/hardware/kanata.nix
+++ b/nixos/modules/services/hardware/kanata.nix
@@ -7,7 +7,7 @@ let
 
   upstreamDoc = "See [the upstream documentation](https://github.com/jtroo/kanata/blob/main/docs/config.adoc) and [example config files](https://github.com/jtroo/kanata/tree/main/cfg_samples) for more information.";
 
-  keyboard = {
+  keyboard = { name, config, ... }: {
     options = {
       devices = mkOption {
         type = types.listOf types.str;
@@ -48,6 +48,21 @@ let
           ${upstreamDoc}
         '';
       };
+      configFile = mkOption {
+        type = types.path;
+        default = mkConfig name config;
+        defaultText =
+          "A config file generated by values from other kanata module options.";
+        description = ''
+          The config file.
+
+          By default, it is generated by values from other kanata
+          module options.
+
+          You can also set it to your own full config file which
+          overrides all other kanata module options.  ${upstreamDoc}
+        '';
+      };
       extraArgs = mkOption {
         type = types.listOf types.str;
         default = [ ];
@@ -85,6 +100,10 @@ let
 
       ${keyboard.config}
     '';
+    # Only the config file generated by this module is checked.  A
+    # user-provided one is not checked because it may not be available
+    # at build time.  I think this is a good balance between module
+    # complexity and functionality.
     checkPhase = ''
       ${getExe cfg.package} --cfg "$target" --check --debug
     '';
@@ -96,7 +115,7 @@ let
       Type = "notify";
       ExecStart = ''
         ${getExe cfg.package} \
-          --cfg ${mkConfig name keyboard} \
+          --cfg ${keyboard.configFile} \
           --symlink-path ''${RUNTIME_DIRECTORY}/${name} \
           ${optionalString (keyboard.port != null) "--port ${toString keyboard.port}"} \
           ${utils.escapeSystemdExecArgs keyboard.extraArgs}
diff --git a/nixos/modules/services/hardware/thermald.nix b/nixos/modules/services/hardware/thermald.nix
index 25cfd97016288..fb7cf3735a7ea 100644
--- a/nixos/modules/services/hardware/thermald.nix
+++ b/nixos/modules/services/hardware/thermald.nix
@@ -28,7 +28,13 @@ in
       configFile = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = "the thermald manual configuration file.";
+        description = ''
+          The thermald manual configuration file.
+
+          Leave unspecified to run with the `--adaptive` flag instead which will have thermald use your computer's DPTF adaptive tables.
+
+          See `man thermald` for more information.
+        '';
       };
 
       package = mkPackageOption pkgs "thermald" { };
@@ -49,8 +55,7 @@ in
             --no-daemon \
             ${optionalString cfg.debug "--loglevel=debug"} \
             ${optionalString cfg.ignoreCpuidCheck "--ignore-cpuid-check"} \
-            ${optionalString (cfg.configFile != null) "--config-file ${cfg.configFile}"} \
-            ${optionalString (cfg.configFile == null) "--adaptive"} \
+            ${if cfg.configFile != null then "--config-file ${cfg.configFile}" else "--adaptive"} \
             --dbus-enable
         '';
       };
diff --git a/nixos/modules/services/home-automation/ebusd.nix b/nixos/modules/services/home-automation/ebusd.nix
index ac9ec06639c13..f5c5479e8eaff 100644
--- a/nixos/modules/services/home-automation/ebusd.nix
+++ b/nixos/modules/services/home-automation/ebusd.nix
@@ -138,7 +138,7 @@ in
       after = [ "network.target" ];
       serviceConfig = {
         ExecStart = let
-          args = cli.toGNUCommandLineShell { } (foldr (a: b: a // b) { } [
+          args = cli.toGNUCommandLineShell { optionValueSeparator = "="; } (foldr (a: b: a // b) { } [
             {
               inherit (cfg) device port configpath scanconfig readonly;
               foreground = true;
diff --git a/nixos/modules/services/home-automation/wyoming/faster-whisper.nix b/nixos/modules/services/home-automation/wyoming/faster-whisper.nix
index d0fca6a41c7b6..45664103665f7 100644
--- a/nixos/modules/services/home-automation/wyoming/faster-whisper.nix
+++ b/nixos/modules/services/home-automation/wyoming/faster-whisper.nix
@@ -113,6 +113,9 @@ in
       nameValuePair "wyoming-faster-whisper-${server}" {
         inherit (options) enable;
         description = "Wyoming faster-whisper server instance ${server}";
+        wants = [
+          "network-online.target"
+        ];
         after = [
           "network-online.target"
         ];
diff --git a/nixos/modules/services/home-automation/wyoming/openwakeword.nix b/nixos/modules/services/home-automation/wyoming/openwakeword.nix
index 856a4ef7366d0..f9848970bf734 100644
--- a/nixos/modules/services/home-automation/wyoming/openwakeword.nix
+++ b/nixos/modules/services/home-automation/wyoming/openwakeword.nix
@@ -108,6 +108,9 @@ in
   config = mkIf cfg.enable {
     systemd.services."wyoming-openwakeword" = {
       description = "Wyoming openWakeWord server";
+      wants = [
+        "network-online.target"
+      ];
       after = [
         "network-online.target"
       ];
diff --git a/nixos/modules/services/home-automation/wyoming/piper.nix b/nixos/modules/services/home-automation/wyoming/piper.nix
index 5b5f898d7ca35..a26fe8e84f609 100644
--- a/nixos/modules/services/home-automation/wyoming/piper.nix
+++ b/nixos/modules/services/home-automation/wyoming/piper.nix
@@ -117,6 +117,9 @@ in
       nameValuePair "wyoming-piper-${server}" {
         inherit (options) enable;
         description = "Wyoming Piper server instance ${server}";
+        wants = [
+          "network-online.target"
+        ];
         after = [
           "network-online.target"
         ];
diff --git a/nixos/modules/services/mail/stalwart-mail.nix b/nixos/modules/services/mail/stalwart-mail.nix
index 9cc919fd117d6..68606698e59f1 100644
--- a/nixos/modules/services/mail/stalwart-mail.nix
+++ b/nixos/modules/services/mail/stalwart-mail.nix
@@ -11,6 +11,7 @@ let
 in {
   options.services.stalwart-mail = {
     enable = mkEnableOption "the Stalwart all-in-one email server";
+
     package = mkPackageOption pkgs "stalwart-mail" { };
 
     settings = mkOption {
@@ -26,6 +27,7 @@ in {
   };
 
   config = mkIf cfg.enable {
+
     # Default config: all local
     services.stalwart-mail.settings = {
       global.tracing.method = mkDefault "stdout";
@@ -38,9 +40,12 @@ in {
       store.blob.path = mkDefault "${dataDir}/data/blobs";
       storage.data = mkDefault "db";
       storage.fts = mkDefault "db";
+      storage.lookup = mkDefault "db";
       storage.blob = mkDefault "blob";
       resolver.type = mkDefault "system";
-      resolver.public-suffix = mkDefault ["https://publicsuffix.org/list/public_suffix_list.dat"];
+      resolver.public-suffix = lib.mkDefault [
+        "file://${pkgs.publicsuffix-list}/share/publicsuffix/public_suffix_list.dat"
+      ];
     };
 
     systemd.services.stalwart-mail = {
@@ -106,6 +111,6 @@ in {
   };
 
   meta = {
-    maintainers = with maintainers; [ happysalada pacien ];
+    maintainers = with maintainers; [ happysalada pacien onny ];
   };
 }
diff --git a/nixos/modules/services/misc/bcg.nix b/nixos/modules/services/misc/bcg.nix
index 626a67f66d08b..63c441833d958 100644
--- a/nixos/modules/services/misc/bcg.nix
+++ b/nixos/modules/services/misc/bcg.nix
@@ -149,20 +149,20 @@ in
     systemd.services.bcg = let
       envConfig = cfg.environmentFiles != [];
       finalConfig = if envConfig
-                    then "$RUNTIME_DIRECTORY/bcg.config.yaml"
+                    then "\${RUNTIME_DIRECTORY}/bcg.config.yaml"
                     else configFile;
     in {
       description = "BigClown Gateway";
       wantedBy = [ "multi-user.target" ];
       wants = [ "network-online.target" ] ++ lib.optional config.services.mosquitto.enable "mosquitto.service";
       after = [ "network-online.target" ];
-      preStart = ''
+      preStart = mkIf envConfig ''
         umask 077
         ${pkgs.envsubst}/bin/envsubst -i "${configFile}" -o "${finalConfig}"
         '';
       serviceConfig = {
         EnvironmentFile = cfg.environmentFiles;
-        ExecStart="${cfg.package}/bin/bcg -c ${finalConfig} -v ${cfg.verbose}";
+        ExecStart = "${cfg.package}/bin/bcg -c ${finalConfig} -v ${cfg.verbose}";
         RuntimeDirectory = "bcg";
       };
     };
diff --git a/nixos/modules/services/misc/invidious-router.nix b/nixos/modules/services/misc/invidious-router.nix
index 33da7e96b5235..4a08f0bcb8dcb 100644
--- a/nixos/modules/services/misc/invidious-router.nix
+++ b/nixos/modules/services/misc/invidious-router.nix
@@ -8,7 +8,7 @@
   settingsFormat = pkgs.formats.yaml {};
   configFile = settingsFormat.generate "config.yaml" cfg.settings;
 in {
-  meta.maintainers = [lib.maintainers.s1ls];
+  meta.maintainers = [lib.maintainers.sils];
 
   options.services.invidious-router = {
     enable = lib.mkEnableOption "Enables the invidious-router service";
diff --git a/nixos/modules/services/misc/llama-cpp.nix b/nixos/modules/services/misc/llama-cpp.nix
index c73cff027e224..2fa1a7b28e3b1 100644
--- a/nixos/modules/services/misc/llama-cpp.nix
+++ b/nixos/modules/services/misc/llama-cpp.nix
@@ -92,7 +92,6 @@ in {
         SystemCallFilter = [
           "@system-service"
           "~@privileged"
-          "~@resources"
         ];
         SystemCallErrorNumber = "EPERM";
         ProtectProc = "invisible";
diff --git a/nixos/modules/services/misc/plex.nix b/nixos/modules/services/misc/plex.nix
index fcd8ebbac6edd..212abda5d1e0b 100644
--- a/nixos/modules/services/misc/plex.nix
+++ b/nixos/modules/services/misc/plex.nix
@@ -93,6 +93,17 @@ in
         '';
       };
 
+      accelerationDevices = mkOption {
+        type = types.listOf types.str;
+        default = ["*"];
+        example = [ "/dev/dri/renderD128" ];
+        description = ''
+          A list of device paths to hardware acceleration devices that Plex should
+          have access to. This is useful when transcoding media files.
+          The special value `"*"` will allow all devices.
+        '';
+      };
+
       package = mkPackageOption pkgs "plex" {
         extraDescription = ''
           Plex subscribers may wish to use their own package here,
@@ -133,6 +144,24 @@ in
         KillSignal = "SIGQUIT";
         PIDFile = "${cfg.dataDir}/Plex Media Server/plexmediaserver.pid";
         Restart = "on-failure";
+
+        # Hardening
+        NoNewPrivileges = true;
+        PrivateTmp = true;
+        PrivateDevices = cfg.accelerationDevices == [];
+        DeviceAllow = mkIf (cfg.accelerationDevices != [] && !lib.elem "*" cfg.accelerationDevices) cfg.accelerationDevices;
+        ProtectSystem = true;
+        ProtectHome = true;
+        ProtectControlGroups = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        RestrictAddressFamilies = ["AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK"];
+        # This could be made to work if the namespaces needed were known
+        # RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        MemoryDenyWriteExecute = true;
+        LockPersonality = true;
       };
 
       environment = {
diff --git a/nixos/modules/services/misc/portunus.nix b/nixos/modules/services/misc/portunus.nix
index bdb35da788e3a..335806b261a23 100644
--- a/nixos/modules/services/misc/portunus.nix
+++ b/nixos/modules/services/misc/portunus.nix
@@ -98,6 +98,10 @@ in
 
           The OIDC secret must be set as the `DEX_CLIENT_''${id}` environment variable
           in the [](#opt-services.dex.environmentFile) setting.
+
+          ::: {.note}
+          Make sure the id only contains characters that are allowed in an environment variable name, e.g. no -.
+          :::
         '';
       };
 
diff --git a/nixos/modules/services/misc/snapper.nix b/nixos/modules/services/misc/snapper.nix
index 3a3ed1b5c0f56..a42fca5b60289 100644
--- a/nixos/modules/services/misc/snapper.nix
+++ b/nixos/modules/services/misc/snapper.nix
@@ -103,6 +103,18 @@ in
       '';
     };
 
+    persistentTimer = mkOption {
+      default = false;
+      type = types.bool;
+      example = true;
+      description = ''
+        Set the `Persistent` option for the
+        {manpage}`systemd.timer(5)`
+        which triggers the snapshot immediately if the last trigger
+        was missed (e.g. if the system was powered down).
+      '';
+    };
+
     cleanupInterval = mkOption {
       type = types.str;
       default = "1d";
@@ -198,7 +210,14 @@ in
       inherit documentation;
       requires = [ "local-fs.target" ];
       serviceConfig.ExecStart = "${pkgs.snapper}/lib/snapper/systemd-helper --timeline";
-      startAt = cfg.snapshotInterval;
+    };
+
+    systemd.timers.snapper-timeline = {
+      wantedBy = [ "timers.target" ];
+      timerConfig = {
+        Persistent = cfg.persistentTimer;
+        OnCalendar = cfg.snapshotInterval;
+      };
     };
 
     systemd.services.snapper-cleanup = {
diff --git a/nixos/modules/services/misc/tzupdate.nix b/nixos/modules/services/misc/tzupdate.nix
index eac1e1112a5ab..be63bb179e423 100644
--- a/nixos/modules/services/misc/tzupdate.nix
+++ b/nixos/modules/services/misc/tzupdate.nix
@@ -41,5 +41,5 @@ in {
     };
   };
 
-  meta.maintainers = [ maintainers.michaelpj ];
+  meta.maintainers = [ ];
 }
diff --git a/nixos/modules/services/monitoring/arbtt.nix b/nixos/modules/services/monitoring/arbtt.nix
index 6dad6bdec3284..cf9a236c079c0 100644
--- a/nixos/modules/services/monitoring/arbtt.nix
+++ b/nixos/modules/services/monitoring/arbtt.nix
@@ -45,5 +45,5 @@ in {
     };
   };
 
-  meta.maintainers = [ maintainers.michaelpj ];
+  meta.maintainers = [ ];
 }
diff --git a/nixos/modules/services/monitoring/loki.nix b/nixos/modules/services/monitoring/loki.nix
index ba63f95e7f1a8..de4f1bc7aa23e 100644
--- a/nixos/modules/services/monitoring/loki.nix
+++ b/nixos/modules/services/monitoring/loki.nix
@@ -97,8 +97,20 @@ in {
 
       serviceConfig = let
         conf = if cfg.configFile == null
-               then prettyJSON cfg.configuration
+               then
+                 # Config validation may fail when using extraFlags = [ "-config.expand-env=true" ].
+                 # To work around this, we simply skip it when extraFlags is not empty.
+                 if cfg.extraFlags == []
+                 then validateConfig (prettyJSON cfg.configuration)
+                 else prettyJSON cfg.configuration
                else cfg.configFile;
+        validateConfig = file:
+        pkgs.runCommand "validate-loki-conf" {
+          nativeBuildInputs = [ cfg.package ];
+        } ''
+            loki -verify-config -config.file "${file}"
+            ln -s "${file}" "$out"
+          '';
       in
       {
         ExecStart = "${cfg.package}/bin/loki --config.file=${conf} ${escapeShellArgs cfg.extraFlags}";
diff --git a/nixos/modules/services/networking/kea.nix b/nixos/modules/services/networking/kea.nix
index 66173c145d16a..11add600b66fb 100644
--- a/nixos/modules/services/networking/kea.nix
+++ b/nixos/modules/services/networking/kea.nix
@@ -278,6 +278,9 @@ in
         "https://kea.readthedocs.io/en/kea-${package.version}/arm/agent.html"
       ];
 
+      wants = [
+        "network-online.target"
+      ];
       after = [
         "network-online.target"
         "time-sync.target"
diff --git a/nixos/modules/services/networking/oink.nix b/nixos/modules/services/networking/oink.nix
new file mode 100644
index 0000000000000..cd0fdf172331d
--- /dev/null
+++ b/nixos/modules/services/networking/oink.nix
@@ -0,0 +1,84 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.oink;
+  makeOinkConfig = attrs: (pkgs.formats.json { }).generate
+    "oink.json" (mapAttrs' (k: v: nameValuePair (toLower k) v) attrs);
+  oinkConfig = makeOinkConfig {
+    global = cfg.settings;
+    domains = cfg.domains;
+  };
+in
+{
+  options.services.oink = {
+    enable = mkEnableOption "Oink, a dynamic DNS client for Porkbun";
+    package = mkPackageOption pkgs "oink" { };
+    settings = {
+      apiKey = mkOption {
+        type = types.str;
+        description = "API key to use when modifying DNS records.";
+      };
+      secretApiKey = mkOption {
+        type = types.str;
+        description = "Secret API key to use when modifying DNS records.";
+      };
+      interval = mkOption {
+        # https://github.com/rlado/oink/blob/v1.1.1/src/main.go#L364
+        type = types.ints.between 60 172800; # 48 hours
+        default = 900;
+        description = "Seconds to wait before sending another request.";
+      };
+      ttl = mkOption {
+        type = types.ints.between 600 172800;
+        default = 600;
+        description = ''
+          The TTL ("Time to Live") value to set for your DNS records.
+
+          The TTL controls how long in seconds your records will be cached
+          for. A smaller value will allow the record to update quicker.
+        '';
+      };
+    };
+    domains = mkOption {
+      type = with types; listOf (attrsOf anything);
+      default = [];
+      example = [
+        {
+          domain = "nixos.org";
+          subdomain = "";
+          ttl = 1200;
+        }
+        {
+          domain = "nixos.org";
+          subdomain = "hydra";
+        }
+      ];
+      description = ''
+        List of attribute sets containing configuration for each domain.
+
+        Each attribute set must have two attributes, one named *domain*
+        and another named *subdomain*. The domain attribute must specify
+        the root domain that you want to configure, and the subdomain
+        attribute must specify its subdomain if any. If you want to
+        configure the root domain rather than a subdomain, leave the
+        subdomain attribute as an empty string.
+
+        Additionally, you can use attributes from *services.oink.settings*
+        to override settings per-domain.
+
+        Every domain listed here *must* have API access enabled in
+        Porkbun's control panel.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.oink = {
+      description = "Dynamic DNS client for Porkbun";
+      wantedBy = [ "multi-user.target" ];
+      script = "${cfg.package}/bin/oink -c ${oinkConfig}";
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/rosenpass.nix b/nixos/modules/services/networking/rosenpass.nix
index 373a6c7690799..66b6f960a81ab 100644
--- a/nixos/modules/services/networking/rosenpass.nix
+++ b/nixos/modules/services/networking/rosenpass.nix
@@ -225,8 +225,10 @@ in
         # See <https://www.freedesktop.org/software/systemd/man/systemd.unit.html#Specifiers>
         environment.CONFIG = "%t/${serviceConfig.RuntimeDirectory}/config.toml";
 
-        preStart = "${getExe pkgs.envsubst} -i ${config} -o \"$CONFIG\"";
-        script = "rosenpass exchange-config \"$CONFIG\"";
+        script = ''
+          ${getExe pkgs.envsubst} -i ${config} -o "$CONFIG"
+          rosenpass exchange-config "$CONFIG"
+        '';
       };
   };
 }
diff --git a/nixos/modules/services/networking/vsftpd.nix b/nixos/modules/services/networking/vsftpd.nix
index 25f950600b91c..07b93e92a7509 100644
--- a/nixos/modules/services/networking/vsftpd.nix
+++ b/nixos/modules/services/networking/vsftpd.nix
@@ -278,7 +278,7 @@ in
       }
       {
         assertion = (cfg.enableVirtualUsers -> cfg.userDbPath != null)
-                 && (cfg.enableVirtualUsers -> cfg.localUsers != null);
+                 && (cfg.enableVirtualUsers -> cfg.localUsers);
         message = "vsftpd: If enableVirtualUsers is true, you need to setup both the userDbPath and localUsers options.";
       }];
 
diff --git a/nixos/modules/services/networking/wireguard.nix b/nixos/modules/services/networking/wireguard.nix
index 3f68af3a86c96..81abae2c9303d 100644
--- a/nixos/modules/services/networking/wireguard.nix
+++ b/nixos/modules/services/networking/wireguard.nix
@@ -80,6 +80,15 @@ let
         description = "Commands called at the end of the interface setup.";
       };
 
+      preShutdown = mkOption {
+        example = literalExpression ''"''${pkgs.iproute2}/bin/ip netns del foo"'';
+        default = "";
+        type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
+        description = ''
+          Commands called before shutting down the interface.
+        '';
+      };
+
       postShutdown = mkOption {
         example = literalExpression ''"''${pkgs.openresolv}/bin/resolvconf -d wg0"'';
         default = "";
@@ -497,6 +506,7 @@ let
         '';
 
         postStop = ''
+          ${values.preShutdown}
           ${ipPostMove} link del dev "${name}"
           ${values.postShutdown}
         '';
diff --git a/nixos/modules/services/security/bitwarden-directory-connector-cli.nix b/nixos/modules/services/security/bitwarden-directory-connector-cli.nix
index d21322caf4c33..fef4a88648979 100644
--- a/nixos/modules/services/security/bitwarden-directory-connector-cli.nix
+++ b/nixos/modules/services/security/bitwarden-directory-connector-cli.nix
@@ -260,6 +260,7 @@ in {
         description = "Sync timer for Bitwarden Directory Connector";
         wantedBy = ["timers.target"];
         after = ["network-online.target"];
+        wants = ["network-online.target"];
         timerConfig = {
           OnCalendar = cfg.interval;
           Unit = "bitwarden-directory-connector-cli.service";
diff --git a/nixos/modules/services/security/oauth2-proxy-nginx.nix b/nixos/modules/services/security/oauth2-proxy-nginx.nix
index 07192e7287b05..44bf56233e95e 100644
--- a/nixos/modules/services/security/oauth2-proxy-nginx.nix
+++ b/nixos/modules/services/security/oauth2-proxy-nginx.nix
@@ -83,6 +83,15 @@ in
   } ++ (lib.mapAttrsToList (vhost: conf: {
     virtualHosts.${vhost} = {
       locations = {
+        "/".extraConfig = ''
+          # pass information via X-User and X-Email headers to backend, requires running with --set-xauthrequest flag
+          proxy_set_header X-User  $user;
+          proxy_set_header X-Email $email;
+
+          # if you enabled --cookie-refresh, this is needed for it to work with auth_request
+          add_header Set-Cookie $auth_cookie;
+        '';
+
         "/oauth2/auth" = let
           maybeQueryArg = name: value:
             if value == null then null
@@ -102,6 +111,7 @@ in
             proxy_pass_request_body           off;
           '';
         };
+
         "@redirectToAuth2ProxyLogin" = {
           return = "307 https://${cfg.domain}/oauth2/start?rd=$scheme://$host$request_uri";
           extraConfig = ''
@@ -114,16 +124,10 @@ in
         auth_request /oauth2/auth;
         error_page 401 = @redirectToAuth2ProxyLogin;
 
-        # pass information via X-User and X-Email headers to backend,
-        # requires running with --set-xauthrequest flag
+        # set variables being used in locations."/".extraConfig
         auth_request_set $user   $upstream_http_x_auth_request_user;
         auth_request_set $email  $upstream_http_x_auth_request_email;
-        proxy_set_header X-User  $user;
-        proxy_set_header X-Email $email;
-
-        # if you enabled --cookie-refresh, this is needed for it to work with auth_request
         auth_request_set $auth_cookie $upstream_http_set_cookie;
-        add_header Set-Cookie $auth_cookie;
       '';
     };
   }) cfg.virtualHosts)));
diff --git a/nixos/modules/services/security/oauth2-proxy.nix b/nixos/modules/services/security/oauth2-proxy.nix
index 78a772845a352..3079a1d030c52 100644
--- a/nixos/modules/services/security/oauth2-proxy.nix
+++ b/nixos/modules/services/security/oauth2-proxy.nix
@@ -577,20 +577,22 @@ in
 
     users.groups.oauth2-proxy = {};
 
-    systemd.services.oauth2-proxy = {
-      description = "OAuth2 Proxy";
-      path = [ cfg.package ];
-      wantedBy = [ "multi-user.target" ];
-      wants = [ "network-online.target" ];
-      after = [ "network-online.target" ];
-
-      serviceConfig = {
-        User = "oauth2-proxy";
-        Restart = "always";
-        ExecStart = "${cfg.package}/bin/oauth2-proxy ${configString}";
-        EnvironmentFile = lib.mkIf (cfg.keyFile != null) cfg.keyFile;
+    systemd.services.oauth2-proxy =
+      let needsKeycloak = lib.elem cfg.provider ["keycloak" "keycloak-oidc"]
+                          && config.services.keycloak.enable;
+      in {
+        description = "OAuth2 Proxy";
+        path = [ cfg.package ];
+        wantedBy = [ "multi-user.target" ];
+        wants = [ "network-online.target" ] ++ lib.optionals needsKeycloak [ "keycloak.service" ];
+        after = [ "network-online.target" ] ++ lib.optionals needsKeycloak [ "keycloak.service" ];
+
+        serviceConfig = {
+          User = "oauth2-proxy";
+          Restart = "always";
+          ExecStart = "${cfg.package}/bin/oauth2-proxy ${configString}";
+          EnvironmentFile = lib.mkIf (cfg.keyFile != null) cfg.keyFile;
+        };
       };
-    };
-
   };
 }
diff --git a/nixos/modules/services/system/dbus.nix b/nixos/modules/services/system/dbus.nix
index 26f4eba707f92..d84136125f934 100644
--- a/nixos/modules/services/system/dbus.nix
+++ b/nixos/modules/services/system/dbus.nix
@@ -128,10 +128,14 @@ in
         contents."/etc/dbus-1".source = pkgs.makeDBusConf {
           inherit (cfg) apparmor;
           suidHelper = "/bin/false";
-          serviceDirectories = [ pkgs.dbus ];
+          serviceDirectories = [ pkgs.dbus config.boot.initrd.systemd.package ];
         };
         packages = [ pkgs.dbus ];
-        storePaths = [ "${pkgs.dbus}/bin/dbus-daemon" ];
+        storePaths = [
+          "${pkgs.dbus}/bin/dbus-daemon"
+          "${config.boot.initrd.systemd.package}/share/dbus-1/system-services"
+          "${config.boot.initrd.systemd.package}/share/dbus-1/system.d"
+        ];
         targets.sockets.wants = [ "dbus.socket" ];
       };
     })
diff --git a/nixos/modules/services/video/frigate.nix b/nixos/modules/services/video/frigate.nix
index 0e6bde447c033..c3ec4a3c76c34 100644
--- a/nixos/modules/services/video/frigate.nix
+++ b/nixos/modules/services/video/frigate.nix
@@ -427,10 +427,6 @@ in
         PrivateTmp = true;
         CacheDirectory = "frigate";
         CacheDirectoryMode = "0750";
-
-        BindPaths = [
-          "/migrations:${cfg.package}/share/frigate/migrations:ro"
-        ];
       };
     };
   };
diff --git a/nixos/modules/services/web-apps/artalk.nix b/nixos/modules/services/web-apps/artalk.nix
new file mode 100644
index 0000000000000..d3d06f1521b6a
--- /dev/null
+++ b/nixos/modules/services/web-apps/artalk.nix
@@ -0,0 +1,131 @@
+{
+  config,
+  lib,
+  pkgs,
+  utils,
+  ...
+}:
+let
+  cfg = config.services.artalk;
+  settingsFormat = pkgs.formats.json { };
+in
+{
+
+  meta = {
+    maintainers = with lib.maintainers; [ moraxyc ];
+  };
+
+  options = {
+    services.artalk = {
+      enable = lib.mkEnableOption "artalk, a comment system";
+      configFile = lib.mkOption {
+        type = lib.types.str;
+        default = "/etc/artalk/config.yml";
+        description = "Artalk config file path. If it is not exist, Artalk will generate one.";
+      };
+      allowModify = lib.mkOption {
+        type = lib.types.bool;
+        default = true;
+        description = "allow Artalk store the settings to config file persistently";
+      };
+      workdir = lib.mkOption {
+        type = lib.types.str;
+        default = "/var/lib/artalk";
+        description = "Artalk working directory";
+      };
+      user = lib.mkOption {
+        type = lib.types.str;
+        default = "artalk";
+        description = "Artalk user name.";
+      };
+
+      group = lib.mkOption {
+        type = lib.types.str;
+        default = "artalk";
+        description = "Artalk group name.";
+      };
+
+      package = lib.mkPackageOption pkgs "artalk" { };
+      settings = lib.mkOption {
+        type = lib.types.submodule {
+          freeformType = settingsFormat.type;
+          options = {
+            host = lib.mkOption {
+              type = lib.types.str;
+              default = "0.0.0.0";
+              description = ''
+                Artalk server listen host
+              '';
+            };
+            port = lib.mkOption {
+              type = lib.types.port;
+              default = 23366;
+              description = ''
+                Artalk server listen port
+              '';
+            };
+          };
+        };
+        default = { };
+        description = ''
+          The artalk configuration.
+
+          If you set allowModify to true, Artalk will be able to store the settings in the config file persistently. This section's content will update in the config file after the service restarts.
+
+          Options containing secret data should be set to an attribute set
+          containing the attribute `_secret` - a string pointing to a file
+          containing the value the option should be set to.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    users.users.artalk = lib.optionalAttrs (cfg.user == "artalk") {
+      description = "artalk user";
+      isSystemUser = true;
+      group = cfg.group;
+    };
+    users.groups.artalk = lib.optionalAttrs (cfg.group == "artalk") { };
+
+    environment.systemPackages = [ cfg.package ];
+
+    systemd.services.artalk = {
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      preStart =
+        ''
+          umask 0077
+          ${utils.genJqSecretsReplacementSnippet cfg.settings "/run/artalk/new"}
+        ''
+        + (
+          if cfg.allowModify then
+            ''
+              [ -e "${cfg.configFile}" ] || ${lib.getExe cfg.package} gen config "${cfg.configFile}"
+              cat "${cfg.configFile}" | ${lib.getExe pkgs.yj} > "/run/artalk/old"
+              ${lib.getExe pkgs.jq} -s '.[0] * .[1]' "/run/artalk/old" "/run/artalk/new" > "/run/artalk/result"
+              cat "/run/artalk/result" | ${lib.getExe pkgs.yj} -r > "${cfg.configFile}"
+              rm /run/artalk/{old,new,result}
+            ''
+          else
+            ''
+              cat /run/artalk/new | ${lib.getExe pkgs.yj} -r > "${cfg.configFile}"
+              rm /run/artalk/new
+            ''
+        );
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        Type = "simple";
+        ExecStart = "${lib.getExe cfg.package} server --config ${cfg.configFile} --workdir ${cfg.workdir} --host ${cfg.settings.host} --port ${builtins.toString cfg.settings.port}";
+        Restart = "on-failure";
+        RestartSec = "5s";
+        ConfigurationDirectory = [ "artalk" ];
+        StateDirectory = [ "artalk" ];
+        RuntimeDirectory = [ "artalk" ];
+        AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
+        ProtectHome = "yes";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/web-apps/commafeed.nix b/nixos/modules/services/web-apps/commafeed.nix
new file mode 100644
index 0000000000000..354e3625bb999
--- /dev/null
+++ b/nixos/modules/services/web-apps/commafeed.nix
@@ -0,0 +1,114 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+let
+  cfg = config.services.commafeed;
+in
+{
+  options.services.commafeed = {
+    enable = lib.mkEnableOption "CommaFeed";
+
+    package = lib.mkPackageOption pkgs "commafeed" { };
+
+    user = lib.mkOption {
+      type = lib.types.str;
+      description = "User under which CommaFeed runs.";
+      default = "commafeed";
+    };
+
+    group = lib.mkOption {
+      type = lib.types.str;
+      description = "Group under which CommaFeed runs.";
+      default = "commafeed";
+    };
+
+    stateDir = lib.mkOption {
+      type = lib.types.path;
+      description = "Directory holding all state for CommaFeed to run.";
+      default = "/var/lib/commafeed";
+    };
+
+    environment = lib.mkOption {
+      type = lib.types.attrsOf (
+        lib.types.oneOf [
+          lib.types.bool
+          lib.types.int
+          lib.types.str
+        ]
+      );
+      description = ''
+        Extra environment variables passed to CommaFeed, refer to
+        <https://github.com/Athou/commafeed/blob/master/commafeed-server/config.yml.example>
+        for supported values. The default user is `admin` and the default password is `admin`.
+        Correct configuration for H2 database is already provided.
+      '';
+      default = { };
+      example = {
+        CF_SERVER_APPLICATIONCONNECTORS_0_TYPE = "http";
+        CF_SERVER_APPLICATIONCONNECTORS_0_PORT = 9090;
+      };
+    };
+
+    environmentFile = lib.mkOption {
+      type = lib.types.nullOr lib.types.path;
+      description = ''
+        Environment file as defined in {manpage}`systemd.exec(5)`.
+      '';
+      default = null;
+      example = "/var/lib/commafeed/commafeed.env";
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.commafeed = {
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      environment = lib.mapAttrs (
+        _: v: if lib.isBool v then lib.boolToString v else toString v
+      ) cfg.environment;
+      serviceConfig = {
+        ExecStart = "${lib.getExe cfg.package} server ${cfg.package}/share/config.yml";
+        User = cfg.user;
+        Group = cfg.group;
+        StateDirectory = baseNameOf cfg.stateDir;
+        WorkingDirectory = cfg.stateDir;
+        # Hardening
+        CapabilityBoundingSet = [ "" ];
+        DevicePolicy = "closed";
+        DynamicUser = true;
+        LockPersonality = true;
+        NoNewPrivileges = true;
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProcSubset = "pid";
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        ProtectSystem = true;
+        RestrictAddressFamilies = [
+          "AF_INET"
+          "AF_INET6"
+        ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [
+          "@system-service"
+          "~@privileged"
+        ];
+        UMask = "0077";
+      } // lib.optionalAttrs (cfg.environmentFile != null) { EnvironmentFile = cfg.environmentFile; };
+    };
+  };
+
+  meta.maintainers = [ lib.maintainers.raroh73 ];
+}
diff --git a/nixos/modules/services/web-apps/filesender.md b/nixos/modules/services/web-apps/filesender.md
new file mode 100644
index 0000000000000..44d066761b9a4
--- /dev/null
+++ b/nixos/modules/services/web-apps/filesender.md
@@ -0,0 +1,49 @@
+# FileSender {#module-services-filesender}
+
+[FileSender](https://filesender.org/software/) is a software that makes it easy to send and receive big files.
+
+## Quickstart {#module-services-filesender-quickstart}
+
+FileSender uses [SimpleSAMLphp](https://simplesamlphp.org/) for authentication, which needs to be configured separately.
+
+Minimal working instance of FileSender that uses password-authentication would look like this:
+
+```nix
+{
+  networking.firewall.allowedTCPPorts = [ 80 443 ];
+  services.filesender = {
+    enable = true;
+    localDomain = "filesender.example.com";
+    configureNginx = true;
+    database.createLocally = true;
+
+    settings = {
+      auth_sp_saml_authentication_source = "default";
+      auth_sp_saml_uid_attribute = "uid";
+      storage_filesystem_path = "<STORAGE PATH FOR UPLOADED FILES>";
+      admin = "admin";
+      admin_email = "admin@example.com";
+      email_reply_to = "noreply@example.com";
+    };
+  };
+  services.simplesamlphp.filesender = {
+    settings = {
+      "module.enable".exampleauth = true;
+    };
+    authSources = {
+      admin = [ "core:AdminPassword" ];
+      default = format.lib.mkMixedArray [ "exampleauth:UserPass" ] {
+        "admin:admin123" = {
+          uid = [ "admin" ];
+          cn = [ "admin" ];
+          mail = [ "admin@example.com" ];
+        };
+      };
+    };
+  };
+}
+```
+
+::: {.warning}
+Example above uses hardcoded clear-text password, in production you should use other authentication method like LDAP. You can check supported authentication methods [in SimpleSAMLphp documentation](https://simplesamlphp.org/docs/stable/simplesamlphp-idp.html).
+:::
diff --git a/nixos/modules/services/web-apps/filesender.nix b/nixos/modules/services/web-apps/filesender.nix
new file mode 100644
index 0000000000000..bc8d465643f2f
--- /dev/null
+++ b/nixos/modules/services/web-apps/filesender.nix
@@ -0,0 +1,253 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+let
+  format = pkgs.formats.php { finalVariable = "config"; };
+
+  cfg = config.services.filesender;
+  simpleSamlCfg = config.services.simplesamlphp.filesender;
+  fpm = config.services.phpfpm.pools.filesender;
+
+  filesenderConfigDirectory = pkgs.runCommand "filesender-config" { } ''
+    mkdir $out
+    cp ${format.generate "config.php" cfg.settings} $out/config.php
+  '';
+in
+{
+  meta = {
+    maintainers = with lib.maintainers; [ nhnn ];
+    doc = ./filesender.md;
+  };
+
+  options.services.filesender = with lib; {
+    enable = mkEnableOption "FileSender";
+    package = mkPackageOption pkgs "filesender" { };
+    user = mkOption {
+      description = "User under which filesender runs.";
+      type = types.str;
+      default = "filesender";
+    };
+    database = {
+      createLocally = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Create the PostgreSQL database and database user locally.
+        '';
+      };
+      hostname = mkOption {
+        type = types.str;
+        default = "/run/postgresql";
+        description = "Database hostname.";
+      };
+      port = mkOption {
+        type = types.port;
+        default = 5432;
+        description = "Database port.";
+      };
+      name = mkOption {
+        type = types.str;
+        default = "filesender";
+        description = "Database name.";
+      };
+      user = mkOption {
+        type = types.str;
+        default = "filesender";
+        description = "Database user.";
+      };
+      passwordFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/run/keys/filesender-dbpassword";
+        description = ''
+          A file containing the password corresponding to
+          [](#opt-services.filesender.database.user).
+        '';
+      };
+    };
+    settings = mkOption {
+      type = types.submodule {
+        freeformType = format.type;
+        options = {
+          site_url = mkOption {
+            type = types.str;
+            description = "Site URL. Used in emails, to build URLs for logging in, logging out, build URL for upload endpoint for web workers, to include scripts etc.";
+          };
+          admin = mkOption {
+            type = types.commas;
+            description = ''
+              UIDs (as per the configured saml_uid_attribute) of FileSender administrators.
+              Accounts with these UIDs can access the Admin page through the web UI.
+            '';
+          };
+          admin_email = mkOption {
+            type = types.commas;
+            description = ''
+              Email address of FileSender administrator(s).
+              Emails regarding disk full etc. are sent here.
+              You should use a role-address here.
+            '';
+          };
+          storage_filesystem_path = mkOption {
+            type = types.nullOr types.str;
+            description = "When using storage type filesystem this is the absolute path to the file system where uploaded files are stored until they expire. Your FileSender storage root.";
+          };
+          log_facilities = mkOption {
+            type = format.type;
+            default = [ { type = "error_log"; } ];
+            description = "Defines where FileSender logging is sent. You can sent logging to a file, to syslog or to the default PHP log facility (as configured through your webserver's PHP module). The directive takes an array of one or more logging targets. Logging can be sent to multiple targets simultaneously. Each logging target is a list containing the name of the logging target and a number of attributes which vary per log target. See below for the exact definiation of each log target.";
+          };
+        };
+      };
+      default = { };
+      description = ''
+        Configuration options used by FileSender.
+        See [](https://docs.filesender.org/filesender/v2.0/admin/configuration/)
+        for available options.
+      '';
+    };
+    configureNginx = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Configure nginx as a reverse proxy for FileSender.";
+    };
+    localDomain = mkOption {
+      type = types.str;
+      example = "filesender.example.org";
+      description = "The domain serving your FileSender instance.";
+    };
+    poolSettings = mkOption {
+      type =
+        with types;
+        attrsOf (oneOf [
+          str
+          int
+          bool
+        ]);
+      default = {
+        "pm" = "dynamic";
+        "pm.max_children" = "32";
+        "pm.start_servers" = "2";
+        "pm.min_spare_servers" = "2";
+        "pm.max_spare_servers" = "4";
+        "pm.max_requests" = "500";
+      };
+      description = ''
+        Options for FileSender's PHP pool. See the documentation on `php-fpm.conf` for details on configuration directives.
+      '';
+    };
+  };
+  config = lib.mkIf cfg.enable {
+    services.simplesamlphp.filesender = {
+      phpfpmPool = "filesender";
+      localDomain = cfg.localDomain;
+      settings.baseurlpath = lib.mkDefault "https://${cfg.localDomain}/saml";
+    };
+
+    services.phpfpm = {
+      pools.filesender = {
+        user = cfg.user;
+        group = config.services.nginx.group;
+        phpEnv = {
+          FILESENDER_CONFIG_DIR = toString filesenderConfigDirectory;
+          SIMPLESAMLPHP_CONFIG_DIR = toString simpleSamlCfg.configDir;
+        };
+        settings = {
+          "listen.owner" = config.services.nginx.user;
+          "listen.group" = config.services.nginx.group;
+        } // cfg.poolSettings;
+      };
+    };
+
+    services.nginx = lib.mkIf cfg.configureNginx {
+      enable = true;
+      virtualHosts.${cfg.localDomain} = {
+        root = "${cfg.package}/www";
+        extraConfig = ''
+          index index.php;
+        '';
+        locations = {
+          "/".extraConfig = ''
+            try_files $uri $uri/ /index.php?args;
+          '';
+          "~ [^/]\\.php(/|$)" = {
+            extraConfig = ''
+              fastcgi_split_path_info  ^(.+\.php)(/.+)$;
+              fastcgi_pass  unix:${fpm.socket};
+              include ${pkgs.nginx}/conf/fastcgi.conf;
+              fastcgi_intercept_errors on;
+              fastcgi_param PATH_INFO       $fastcgi_path_info;
+              fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+            '';
+          };
+          "~ /\\.".extraConfig = "deny all;";
+        };
+      };
+    };
+
+    services.postgresql = lib.mkIf cfg.database.createLocally {
+      enable = true;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        {
+          name = cfg.database.user;
+          ensureDBOwnership = true;
+        }
+      ];
+    };
+
+    services.filesender.settings = lib.mkMerge [
+      (lib.mkIf cfg.database.createLocally {
+        db_host = "/run/postgresql";
+        db_port = "5432";
+        db_password = "."; # FileSender requires it even when on UNIX socket auth.
+      })
+      (lib.mkIf (!cfg.database.createLocally) {
+        db_host = cfg.database.hostname;
+        db_port = toString cfg.database.port;
+        db_password = format.lib.mkRaw "file_get_contents('${cfg.database.passwordFile}')";
+      })
+      {
+        site_url = lib.mkDefault "https://${cfg.localDomain}";
+        db_type = "pgsql";
+        db_username = cfg.database.user;
+        db_database = cfg.database.name;
+        "auth_sp_saml_simplesamlphp_url" = "/saml";
+        "auth_sp_saml_simplesamlphp_location" = "${simpleSamlCfg.libDir}";
+      }
+    ];
+
+    systemd.services.filesender-initdb = {
+      description = "Init filesender DB";
+
+      wantedBy = [
+        "multi-user.target"
+        "phpfpm-filesender.service"
+      ];
+      after = [ "postgresql.service" ];
+
+      restartIfChanged = true;
+
+      serviceConfig = {
+        Environment = [
+          "FILESENDER_CONFIG_DIR=${toString filesenderConfigDirectory}"
+          "SIMPLESAMLPHP_CONFIG_DIR=${toString simpleSamlCfg.configDir}"
+        ];
+        Type = "oneshot";
+        Group = config.services.nginx.group;
+        User = "filesender";
+        ExecStart = "${fpm.phpPackage}/bin/php ${cfg.package}/scripts/upgrade/database.php";
+      };
+    };
+
+    users.extraUsers.filesender = lib.mkIf (cfg.user == "filesender") {
+      home = "/var/lib/filesender";
+      group = config.services.nginx.group;
+      createHome = true;
+      isSystemUser = true;
+    };
+  };
+}
diff --git a/nixos/modules/services/web-apps/firefly-iii.nix b/nixos/modules/services/web-apps/firefly-iii.nix
index b0024ce09c38e..6b383139c8911 100644
--- a/nixos/modules/services/web-apps/firefly-iii.nix
+++ b/nixos/modules/services/web-apps/firefly-iii.nix
@@ -3,8 +3,8 @@
 let
   inherit (lib) optionalString mkDefault mkIf mkOption mkEnableOption literalExpression;
   inherit (lib.types) nullOr attrsOf oneOf str int bool path package enum submodule;
-  inherit (lib.strings) concatMapStringsSep removePrefix toShellVars removeSuffix hasSuffix;
-  inherit (lib.attrsets) attrValues genAttrs filterAttrs mapAttrs' nameValuePair;
+  inherit (lib.strings) concatLines removePrefix toShellVars removeSuffix hasSuffix;
+  inherit (lib.attrsets) mapAttrsToList attrValues genAttrs filterAttrs mapAttrs' nameValuePair;
   inherit (builtins) isInt isString toString typeOf;
 
   cfg = config.services.firefly-iii;
@@ -21,18 +21,10 @@ let
     (filterAttrs (n: v: hasSuffix "_FILE" n) cfg.settings);
   env-nonfile-values = filterAttrs (n: v: ! hasSuffix "_FILE" n) cfg.settings;
 
-  envfile = pkgs.writeText "firefly-iii-env" ''
-    ${toShellVars env-file-values}
-    ${toShellVars env-nonfile-values}
-  '';
-
   fileenv-func = ''
-    cp --no-preserve=mode ${envfile} /tmp/firefly-iii-env
-    ${concatMapStringsSep "\n"
-      (n: "${pkgs.replace-secret}/bin/replace-secret ${n} ${n} /tmp/firefly-iii-env")
-      (attrValues env-file-values)}
     set -a
-    . /tmp/firefly-iii-env
+    ${toShellVars env-nonfile-values}
+    ${concatLines (mapAttrsToList (n: v: "${n}=\"$(< ${v})\"") env-file-values)}
     set +a
   '';
 
@@ -41,15 +33,13 @@ let
 
     ${optionalString (cfg.settings.DB_CONNECTION == "sqlite")
       "touch ${cfg.dataDir}/storage/database/database.sqlite"}
-    ${artisan} migrate --seed --no-interaction --force
-    ${artisan} firefly-iii:decrypt-all
+    ${artisan} package:discover
     ${artisan} firefly-iii:upgrade-database
-    ${artisan} firefly-iii:correct-database
-    ${artisan} firefly-iii:report-integrity
     ${artisan} firefly-iii:laravel-passport-keys
     ${artisan} cache:clear
-
-    mv /tmp/firefly-iii-env /run/phpfpm/firefly-iii-env
+    ${artisan} view:cache
+    ${artisan} route:cache
+    ${artisan} config:cache
   '';
 
   commonServiceConfig = {
@@ -146,6 +136,7 @@ in {
 
     virtualHost = mkOption {
       type = str;
+      default = "localhost";
       description = ''
         The hostname at which you wish firefly-iii to be served. If you have
         enabled nginx using `services.firefly-iii.enableNginx` then this will
@@ -170,14 +161,15 @@ in {
     };
 
     settings = mkOption {
+      default = {};
       description = ''
         Options for firefly-iii configuration. Refer to
         <https://github.com/firefly-iii/firefly-iii/blob/main/.env.example> for
         details on supported values. All <option>_FILE values supported by
         upstream are supported here.
 
-        APP_URL will be set by `services.firefly-iii.virtualHost`, do not
-        redefine it here.
+        APP_URL will be the same as `services.firefly-iii.virtualHost` if the
+        former is unset in `services.firefly-iii.settings`.
       '';
       example = literalExpression ''
         {
@@ -192,7 +184,6 @@ in {
           DB_PASSWORD_FILE = "/var/secrets/firefly-iii-mysql-password.txt;
         }
       '';
-      default = {};
       type = submodule {
         freeformType = attrsOf (oneOf [str int bool]);
         options = {
@@ -216,9 +207,9 @@ in {
           };
           DB_PORT = mkOption {
             type = nullOr int;
-            default = if cfg.settings.DB_CONNECTION == "sqlite" then null
+            default = if cfg.settings.DB_CONNECTION == "pgsql" then 5432
                       else if cfg.settings.DB_CONNECTION == "mysql" then 3306
-                      else 5432;
+                      else null;
             defaultText = ''
               `null` if DB_CONNECTION is "sqlite", `3306` if "mysql", `5432` if "pgsql"
             '';
@@ -227,6 +218,21 @@ in {
               this value to be filled.
             '';
           };
+          DB_HOST = mkOption {
+            type = str;
+            default = if cfg.settings.DB_CONNECTION == "pgsql" then "/run/postgresql"
+                      else "localhost";
+            defaultText = ''
+              "localhost" if DB_CONNECTION is "sqlite" or "mysql", "/run/postgresql" if "pgsql".
+            '';
+            description = ''
+              The machine which hosts your database. This is left at the
+              default value for "mysql" because we use the "DB_SOCKET" option
+              to connect to a unix socket instead. "pgsql" requires that the
+              unix socket location be specified here instead of at "DB_SOCKET".
+              This option does not affect "sqlite".
+            '';
+          };
           APP_KEY_FILE = mkOption {
             type = path;
             description = ''
@@ -235,6 +241,20 @@ in {
               /dev/urandom | base64)" > /path/to/key-file`.
             '';
           };
+          APP_URL = mkOption {
+            type = str;
+            default = if cfg.virtualHost == "localhost" then "http://${cfg.virtualHost}"
+                      else "https://${cfg.virtualHost}";
+            defaultText = ''
+              http(s)://''${config.services.firefly-iii.virtualHost}
+            '';
+            description = ''
+              The APP_URL used by firefly-iii internally. Please make sure this
+              URL matches the external URL of your Firefly III installation. It
+              is used to validate specific requests and to generate URLs in
+              emails.
+            '';
+          };
         };
       };
     };
@@ -242,12 +262,6 @@ in {
 
   config = mkIf cfg.enable {
 
-    services.firefly-iii = {
-      settings = {
-        APP_URL = cfg.virtualHost;
-      };
-    };
-
     services.phpfpm.pools.firefly-iii = {
       inherit user group;
       phpPackage = cfg.package.phpPackage;
@@ -262,29 +276,27 @@ in {
       } // cfg.poolConfig;
     };
 
-    systemd.services.phpfpm-firefly-iii.serviceConfig = {
-      EnvironmentFile = "/run/phpfpm/firefly-iii-env";
-      ExecStartPost = "${pkgs.coreutils}/bin/rm /run/phpfpm/firefly-iii-env";
-    };
-
     systemd.services.firefly-iii-setup = {
+      after = [ "postgresql.service" "mysql.service" ];
       requiredBy = [ "phpfpm-firefly-iii.service" ];
       before = [ "phpfpm-firefly-iii.service" ];
       serviceConfig = {
         ExecStart = firefly-iii-maintenance;
         RuntimeDirectory = "phpfpm";
         RuntimeDirectoryPreserve = true;
+        RemainAfterExit = true;
       } // commonServiceConfig;
       unitConfig.JoinsNamespaceOf = "phpfpm-firefly-iii.service";
+      restartTriggers = [ cfg.package ];
     };
 
     systemd.services.firefly-iii-cron = {
+      after = [ "firefly-iii-setup.service" "postgresql.service" "mysql.service" ];
+      wants = [ "firefly-iii-setup.service" ];
       description = "Daily Firefly III cron job";
-      script = ''
-        ${fileenv-func}
-        ${artisan} firefly-iii:cron
-      '';
-      serviceConfig = commonServiceConfig;
+      serviceConfig = {
+        ExecStart = "${artisan} firefly-iii:cron";
+      } // commonServiceConfig;
     };
 
     systemd.timers.firefly-iii-cron = {
@@ -295,6 +307,7 @@ in {
         Persistent = true;
       };
       wantedBy = [ "timers.target" ];
+      restartTriggers = [ cfg.package ];
     };
 
     services.nginx = mkIf cfg.enableNginx {
diff --git a/nixos/modules/services/web-apps/flarum.nix b/nixos/modules/services/web-apps/flarum.nix
new file mode 100644
index 0000000000000..a967c3b121bd6
--- /dev/null
+++ b/nixos/modules/services/web-apps/flarum.nix
@@ -0,0 +1,210 @@
+{ pkgs, lib, config, ... }:
+
+with lib;
+
+let
+  cfg = config.services.flarum;
+
+  flarumInstallConfig = pkgs.writeText "config.json" (builtins.toJSON {
+    debug = false;
+    offline = false;
+
+    baseUrl = cfg.baseUrl;
+    databaseConfiguration = cfg.database;
+    adminUser = {
+      username = cfg.adminUser;
+      password = cfg.initialAdminPassword;
+      email = cfg.adminEmail;
+    };
+    settings = {
+      forum_title = cfg.forumTitle;
+    };
+  });
+in {
+  options.services.flarum = {
+    enable = mkEnableOption "Flarum discussion platform";
+
+    package = mkPackageOption pkgs "flarum" { };
+
+    forumTitle = mkOption {
+      type = types.str;
+      default = "A Flarum Forum on NixOS";
+      description = "Title of the forum.";
+    };
+
+    domain = mkOption {
+      type = types.str;
+      default = "localhost";
+      example = "forum.example.com";
+      description = "Domain to serve on.";
+    };
+
+    baseUrl = mkOption {
+      type = types.str;
+      default = "http://localhost";
+      example = "https://forum.example.com";
+      description = "Change `domain` instead.";
+    };
+
+    adminUser = mkOption {
+      type = types.str;
+      default = "flarum";
+      description = "Username for first web application administrator";
+    };
+
+    adminEmail = mkOption {
+      type = types.str;
+      default = "admin@example.com";
+      description = "Email for first web application administrator";
+    };
+
+    initialAdminPassword = mkOption {
+      type = types.str;
+      default = "flarum";
+      description = "Initial password for the adminUser";
+    };
+
+    user = mkOption {
+      type = types.str;
+      default = "flarum";
+      description = "System user to run Flarum";
+    };
+
+    group = mkOption {
+      type = types.str;
+      default = "flarum";
+      description = "System group to run Flarum";
+    };
+
+    stateDir = mkOption {
+      type = types.path;
+      default = "/var/lib/flarum";
+      description = "Home directory for writable storage";
+    };
+
+    database = mkOption rec {
+      type = with types; attrsOf (oneOf [str bool int]);
+      description = "MySQL database parameters";
+      default = {
+        # the database driver; i.e. MySQL; MariaDB...
+        driver = "mysql";
+        # the host of the connection; localhost in most cases unless using an external service
+        host = "localhost";
+        # the name of the database in the instance
+        database = "flarum";
+        # database username
+        username = "flarum";
+        # database password
+        password = "";
+        # the prefix for the tables; useful if you are sharing the same database with another service
+        prefix = "";
+        # the port of the connection; defaults to 3306 with MySQL
+        port = 3306;
+        strict = false;
+      };
+    };
+
+    createDatabaseLocally = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Create the database and database user locally, and run installation.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.users.${cfg.user} = {
+      isSystemUser = true;
+      home = cfg.stateDir;
+      createHome = true;
+      group = cfg.group;
+    };
+    users.groups.${cfg.group} = {};
+
+    services.phpfpm.pools.flarum = {
+      user = cfg.user;
+      settings = {
+        "listen.owner" = config.services.nginx.user;
+        "listen.group" = config.services.nginx.group;
+        "listen.mode" = "0600";
+        "pm" = mkDefault "dynamic";
+        "pm.max_children" = mkDefault 10;
+        "pm.max_requests" = mkDefault 500;
+        "pm.start_servers" = mkDefault 2;
+        "pm.min_spare_servers" = mkDefault 1;
+        "pm.max_spare_servers" = mkDefault 3;
+      };
+      phpOptions = ''
+        error_log = syslog
+        log_errors = on
+      '';
+    };
+
+    services.nginx = {
+      enable = true;
+      virtualHosts."${cfg.domain}" = {
+        root = "${cfg.stateDir}/public";
+        locations."~ \.php$".extraConfig = ''
+          fastcgi_pass unix:${config.services.phpfpm.pools.flarum.socket};
+          fastcgi_index site.php;
+        '';
+        extraConfig = ''
+          index index.php;
+          include ${cfg.package}/share/php/flarum/.nginx.conf;
+        '';
+      };
+    };
+
+    services.mysql = mkIf cfg.enable {
+      enable = true;
+      package = pkgs.mysql;
+      ensureDatabases = [cfg.database.database];
+      ensureUsers = [
+        {
+          name = cfg.database.username;
+          ensurePermissions = {
+            "${cfg.database.database}.*" = "ALL PRIVILEGES";
+          };
+        }
+      ];
+    };
+
+    assertions = [
+      {
+        assertion = !cfg.createDatabaseLocally || cfg.database.driver == "mysql";
+        message = "Flarum can only be automatically installed in MySQL/MariaDB.";
+      }
+    ];
+
+    systemd.services.flarum-install = {
+      description = "Flarum installation";
+      requiredBy = ["phpfpm-flarum.service"];
+      before = ["phpfpm-flarum.service"];
+      requires = ["mysql.service"];
+      after = ["mysql.service"];
+      serviceConfig = {
+        Type = "oneshot";
+        User = cfg.user;
+        Group = cfg.group;
+      };
+      path = [config.services.phpfpm.phpPackage];
+      script = ''
+        mkdir -p ${cfg.stateDir}/{extensions,public/assets/avatars}
+        mkdir -p ${cfg.stateDir}/storage/{cache,formatter,sessions,views}
+        cd ${cfg.stateDir}
+        cp -f ${cfg.package}/share/php/flarum/{extend.php,site.php,flarum} .
+        ln -sf ${cfg.package}/share/php/flarum/vendor .
+        ln -sf ${cfg.package}/share/php/flarum/public/index.php public/
+        chmod a+x . public
+        chmod +x site.php extend.php flarum
+      '' + optionalString (cfg.createDatabaseLocally && cfg.database.driver == "mysql") ''
+        if [ ! -f config.php ]; then
+            php flarum install --file=${flarumInstallConfig}
+        fi
+        php flarum migrate
+        php flarum cache:clear
+      '';
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ fsagbuya jasonodoom ];
+}
diff --git a/nixos/modules/services/web-apps/keycloak.nix b/nixos/modules/services/web-apps/keycloak.nix
index 201085daa74a8..6d472cf48cd01 100644
--- a/nixos/modules/services/web-apps/keycloak.nix
+++ b/nixos/modules/services/web-apps/keycloak.nix
@@ -466,7 +466,8 @@ in
       confFile = pkgs.writeText "keycloak.conf" (keycloakConfig filteredConfig);
       keycloakBuild = cfg.package.override {
         inherit confFile;
-        plugins = cfg.package.enabledPlugins ++ cfg.plugins;
+        plugins = cfg.package.enabledPlugins ++ cfg.plugins ++
+                  (with cfg.package.plugins; [quarkus-systemd-notify quarkus-systemd-notify-deployment]);
       };
     in
     mkIf cfg.enable
@@ -638,6 +639,8 @@ in
               RuntimeDirectory = "keycloak";
               RuntimeDirectoryMode = "0700";
               AmbientCapabilities = "CAP_NET_BIND_SERVICE";
+              Type = "notify";  # Requires quarkus-systemd-notify plugin
+              NotifyAccess = "all";
             };
             script = ''
               set -o errexit -o pipefail -o nounset -o errtrace
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
index 36c8d2ed6dbd4..91d03dc160779 100644
--- a/nixos/modules/services/web-apps/nextcloud.nix
+++ b/nixos/modules/services/web-apps/nextcloud.nix
@@ -797,7 +797,7 @@ in {
 
   config = mkIf cfg.enable (mkMerge [
     { warnings = let
-        latest = 28;
+        latest = 29;
         upgradeWarning = major: nixos:
           ''
             A legacy Nextcloud install (from before NixOS ${nixos}) may be installed.
diff --git a/nixos/modules/services/web-apps/nextjs-ollama-llm-ui.nix b/nixos/modules/services/web-apps/nextjs-ollama-llm-ui.nix
new file mode 100644
index 0000000000000..d58210c8d9610
--- /dev/null
+++ b/nixos/modules/services/web-apps/nextjs-ollama-llm-ui.nix
@@ -0,0 +1,87 @@
+{
+  config,
+  pkgs,
+  lib,
+  ...
+}:
+let
+  cfg = config.services.nextjs-ollama-llm-ui;
+  # we have to override the URL to a Ollama service here, because it gets baked into the web app.
+  nextjs-ollama-llm-ui = cfg.package.override { ollamaUrl = "https://ollama.lambdablob.com"; };
+in
+{
+  options = {
+    services.nextjs-ollama-llm-ui = {
+      enable = lib.mkEnableOption ''
+        Simple Ollama web UI service; an easy to use web frontend for a Ollama backend service.
+        Run state-of-the-art AI large language models (LLM) similar to ChatGPT locally with privacy
+        on your personal computer.
+        This service is stateless and doesn't store any data on the server; all data is kept
+        locally in your web browser.
+        See https://github.com/jakobhoeg/nextjs-ollama-llm-ui.
+
+        Required: You need the Ollama backend service running by having
+        "services.nextjs-ollama-llm-ui.ollamaUrl" point to the correct url.
+        You can host such a backend service with NixOS through "services.ollama".
+      '';
+      package = lib.mkPackageOption pkgs "nextjs-ollama-llm-ui" { };
+
+      hostname = lib.mkOption {
+        type = lib.types.str;
+        default = "127.0.0.1";
+        example = "ui.example.org";
+        description = ''
+          The hostname under which the Ollama UI interface should be accessible.
+          By default it uses localhost/127.0.0.1 to be accessible only from the local machine.
+          Change to "0.0.0.0" to make it directly accessible from the local network.
+
+          Note: You should keep it at 127.0.0.1 and only serve to the local
+          network or internet from a (home) server behind a reverse-proxy and secured encryption.
+          See https://wiki.nixos.org/wiki/Nginx for instructions on how to set up a reverse-proxy.
+        '';
+      };
+
+      port = lib.mkOption {
+        type = lib.types.port;
+        default = 3000;
+        example = 3000;
+        description = ''
+          The port under which the Ollama UI interface should be accessible.
+        '';
+      };
+
+      ollamaUrl = lib.mkOption {
+        type = lib.types.str;
+        default = "127.0.0.1:11434";
+        example = "https://ollama.example.org";
+        description = ''
+          The address (including host and port) under which we can access the Ollama backend server.
+          !Note that if the the UI service is running under a domain "https://ui.example.org",
+          the Ollama backend service must allow "CORS" requests from this domain, e.g. by adding
+          "services.ollama.environment.OLLAMA_ORIGINS = [ ... "https://ui.example.org" ];"!
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services = {
+
+      nextjs-ollama-llm-ui = {
+        wantedBy = [ "multi-user.target" ];
+        description = "Nextjs Ollama LLM Ui.";
+        after = [ "network.target" ];
+        environment = {
+          HOSTNAME = cfg.hostname;
+          PORT = toString cfg.port;
+          NEXT_PUBLIC_OLLAMA_URL = cfg.ollamaUrl;
+        };
+        serviceConfig = {
+          ExecStart = "${lib.getExe nextjs-ollama-llm-ui}";
+          DynamicUser = true;
+        };
+      };
+    };
+  };
+  meta.maintainers = with lib.maintainers; [ malteneuss ];
+}
diff --git a/nixos/modules/services/web-apps/pretalx.nix b/nixos/modules/services/web-apps/pretalx.nix
index d0b1512f77c59..1411d11982c87 100644
--- a/nixos/modules/services/web-apps/pretalx.nix
+++ b/nixos/modules/services/web-apps/pretalx.nix
@@ -11,14 +11,19 @@ let
 
   configFile = format.generate "pretalx.cfg" cfg.settings;
 
-  extras = cfg.package.optional-dependencies.redis
-    ++ lib.optionals (cfg.settings.database.backend == "mysql") cfg.package.optional-dependencies.mysql
-    ++ lib.optionals (cfg.settings.database.backend == "postgresql") cfg.package.optional-dependencies.postgres;
-
-  pythonEnv = cfg.package.python.buildEnv.override {
-    extraLibs = [ (cfg.package.python.pkgs.toPythonModule cfg.package) ]
-      ++ (with cfg.package.python.pkgs; [ gunicorn ]
-      ++ lib.optional cfg.celery.enable celery) ++ extras;
+  finalPackage = cfg.package.override {
+    inherit (cfg) plugins;
+  };
+
+  pythonEnv = finalPackage.python.buildEnv.override {
+    extraLibs = with finalPackage.python.pkgs; [
+      (toPythonModule finalPackage)
+      gunicorn
+    ]
+    ++ finalPackage.optional-dependencies.redis
+    ++ lib.optionals cfg.celery.enable [ celery ]
+    ++ lib.optionals (cfg.settings.database.backend == "mysql") finalPackage.optional-dependencies.mysql
+    ++ lib.optionals (cfg.settings.database.backend == "postgresql") finalPackage.optional-dependencies.postgres;
   };
 in
 
@@ -44,6 +49,20 @@ in
       description = "User under which pretalx should run.";
     };
 
+    plugins = lib.mkOption {
+      type = with lib.types; listOf package;
+      default = [];
+      example = lib.literalExpression ''
+        with config.services.pretalx.package.plugins; [
+          pages
+          youtube
+        ];
+      '';
+      description = ''
+        Pretalx plugins to install into the Python environment.
+      '';
+    };
+
     gunicorn.extraArgs = lib.mkOption {
       type = with lib.types; listOf str;
       default = [
diff --git a/nixos/modules/services/web-apps/simplesamlphp.nix b/nixos/modules/services/web-apps/simplesamlphp.nix
new file mode 100644
index 0000000000000..e970266fc17dd
--- /dev/null
+++ b/nixos/modules/services/web-apps/simplesamlphp.nix
@@ -0,0 +1,128 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+let
+  cfg = config.services.simplesamlphp;
+
+  format = pkgs.formats.php { finalVariable = "config"; };
+
+  generateConfig =
+    opts:
+    pkgs.runCommand "simplesamlphp-config" { } ''
+      mkdir $out
+      cp ${format.generate "config.php" opts.settings} $out/config.php
+      cp ${format.generate "authsources.php" opts.authSources} $out/authsources.php
+    '';
+in
+{
+  meta = {
+    maintainers = with lib.maintainers; [ nhnn ];
+  };
+
+  options.services.simplesamlphp =
+    with lib;
+    mkOption {
+      type = types.attrsOf (
+        types.submodule (
+          { config, ... }:
+          {
+            options = {
+              package = mkPackageOption pkgs "simplesamlphp" { };
+              configureNginx = mkOption {
+                type = types.bool;
+                default = true;
+                description = "Configure nginx as a reverse proxy for SimpleSAMLphp.";
+              };
+              phpfpmPool = mkOption {
+                type = types.str;
+                description = "The PHP-FPM pool that serves SimpleSAMLphp instance.";
+              };
+              localDomain = mkOption {
+                type = types.str;
+                description = "The domain serving your SimpleSAMLphp instance. This option modifies only /saml route.";
+              };
+              settings = mkOption {
+                type = types.submodule {
+                  freeformType = format.type;
+                  options = {
+                    baseurlpath = mkOption {
+                      type = types.str;
+                      example = "https://filesender.example.com/saml/";
+                      description = "URL where SimpleSAMLphp can be reached.";
+                    };
+                  };
+                };
+                default = { };
+                description = ''
+                  Configuration options used by SimpleSAMLphp.
+                  See [](https://simplesamlphp.org/docs/stable/simplesamlphp-install)
+                  for available options.
+                '';
+              };
+
+              authSources = mkOption {
+                type = format.type;
+                default = { };
+                description = ''
+                  Auth sources options used by SimpleSAMLphp.
+                '';
+              };
+
+              libDir = mkOption {
+                type = types.str;
+                readOnly = true;
+                description = ''
+                  Path to the SimpleSAMLphp library directory.
+                '';
+              };
+              configDir = mkOption {
+                type = types.str;
+                readOnly = true;
+                description = ''
+                  Path to the SimpleSAMLphp config directory.
+                '';
+              };
+            };
+            config = {
+              libDir = "${config.package}/share/php/simplesamlphp/";
+              configDir = "${generateConfig config}";
+            };
+          }
+        )
+      );
+      default = { };
+      description = "Instances of SimpleSAMLphp. This module is designed to work with already existing PHP-FPM pool and NGINX virtualHost.";
+    };
+
+  config = {
+    services.phpfpm.pools = lib.mapAttrs' (
+      phpfpmName: opts:
+      lib.nameValuePair opts.phpfpmPool { phpEnv.SIMPLESAMLPHP_CONFIG_DIR = "${generateConfig opts}"; }
+    ) cfg;
+
+    services.nginx.virtualHosts = lib.mapAttrs' (
+      phpfpmName: opts:
+      lib.nameValuePair opts.localDomain (
+        lib.mkIf opts.configureNginx {
+          locations."^~ /saml/" = {
+            alias = "${opts.package}/share/php/simplesamlphp/www/";
+            extraConfig = ''
+                location ~ ^(?<prefix>/saml)(?<phpfile>.+?\.php)(?<pathinfo>/.*)?$ {
+                  include ${pkgs.nginx}/conf/fastcgi.conf;
+                  fastcgi_split_path_info  ^(.+\.php)(/.+)$;
+                  fastcgi_pass  unix:${config.services.phpfpm.pools.${phpfpmName}.socket};
+                  fastcgi_intercept_errors on;
+                  fastcgi_param SCRIPT_FILENAME $document_root$phpfile;
+                  fastcgi_param SCRIPT_NAME /saml$phpfile;
+                  fastcgi_param PATH_INFO $pathinfo if_not_empty;
+              }
+            '';
+          };
+        }
+      )
+    ) cfg;
+  };
+}
diff --git a/nixos/modules/services/web-apps/your_spotify.nix b/nixos/modules/services/web-apps/your_spotify.nix
new file mode 100644
index 0000000000000..3eb2ffef4f933
--- /dev/null
+++ b/nixos/modules/services/web-apps/your_spotify.nix
@@ -0,0 +1,191 @@
+{
+  pkgs,
+  config,
+  lib,
+  ...
+}: let
+  inherit
+    (lib)
+    boolToString
+    concatMapAttrs
+    concatStrings
+    isBool
+    mapAttrsToList
+    mkEnableOption
+    mkIf
+    mkOption
+    mkPackageOption
+    optionalAttrs
+    types
+    mkDefault
+    ;
+  cfg = config.services.your_spotify;
+
+  configEnv = concatMapAttrs (name: value:
+    optionalAttrs (value != null) {
+      ${name} =
+        if isBool value
+        then boolToString value
+        else toString value;
+    })
+  cfg.settings;
+
+  configFile = pkgs.writeText "your_spotify.env" (concatStrings (mapAttrsToList (name: value: "${name}=${value}\n") configEnv));
+in {
+  options.services.your_spotify = let
+    inherit (types) nullOr port str path package;
+  in {
+    enable = mkEnableOption "your_spotify";
+
+    enableLocalDB = mkEnableOption "a local mongodb instance";
+    nginxVirtualHost = mkOption {
+      type = nullOr str;
+      default = null;
+      description = ''
+        If set creates an nginx virtual host for the client.
+        In most cases this should be the CLIENT_ENDPOINT without
+        protocol prefix.
+      '';
+    };
+
+    package = mkPackageOption pkgs "your_spotify" {};
+
+    clientPackage = mkOption {
+      type = package;
+      description = "Client package to use.";
+    };
+
+    spotifySecretFile = mkOption {
+      type = path;
+      description = ''
+        A file containing the secret key of your Spotify application.
+        Refer to: [Creating the Spotify Application](https://github.com/Yooooomi/your_spotify#creating-the-spotify-application).
+      '';
+    };
+
+    settings = mkOption {
+      description = ''
+        Your Spotify Configuration. Refer to [Your Spotify](https://github.com/Yooooomi/your_spotify) for definitions and values.
+      '';
+      example = lib.literalExpression ''
+        {
+          CLIENT_ENDPOINT = "https://example.com";
+          API_ENDPOINT = "https://api.example.com";
+          SPOTIFY_PUBLIC = "spotify_client_id";
+        }
+      '';
+      type = types.submodule {
+        freeformType = types.attrsOf types.str;
+        options = {
+          CLIENT_ENDPOINT = mkOption {
+            type = str;
+            description = ''
+              The endpoint of your web application.
+              Has to include a protocol Prefix (e.g. `http://`)
+            '';
+            example = "https://your_spotify.example.org";
+          };
+          API_ENDPOINT = mkOption {
+            type = str;
+            description = ''
+              The endpoint of your server
+              This api has to be reachable from the device you use the website from not from the server.
+              This means that for example you may need two nginx virtual hosts if you want to expose this on the
+              internet.
+              Has to include a protocol Prefix (e.g. `http://`)
+            '';
+            example = "https://localhost:3000";
+          };
+          SPOTIFY_PUBLIC = mkOption {
+            type = str;
+            description = ''
+              The public client ID of your Spotify application.
+              Refer to: [Creating the Spotify Application](https://github.com/Yooooomi/your_spotify#creating-the-spotify-application)
+            '';
+          };
+          MONGO_ENDPOINT = mkOption {
+            type = str;
+            description = ''The endpoint of the Mongo database.'';
+            default = "mongodb://localhost:27017/your_spotify";
+          };
+          PORT = mkOption {
+            type = port;
+            description = "The port of the api server";
+            default = 3000;
+          };
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.your_spotify.clientPackage = mkDefault (cfg.package.client.override {apiEndpoint = cfg.settings.API_ENDPOINT;});
+    systemd.services.your_spotify = {
+      after = ["network.target"];
+      script = ''
+        export SPOTIFY_SECRET=$(< "$CREDENTIALS_DIRECTORY/SPOTIFY_SECRET")
+        ${lib.getExe' cfg.package "your_spotify_migrate"}
+        exec ${lib.getExe cfg.package}
+      '';
+      serviceConfig = {
+        User = "your_spotify";
+        Group = "your_spotify";
+        DynamicUser = true;
+        EnvironmentFile = [configFile];
+        StateDirectory = "your_spotify";
+        LimitNOFILE = "1048576";
+        PrivateTmp = true;
+        PrivateDevices = true;
+        StateDirectoryMode = "0700";
+        Restart = "always";
+
+        LoadCredential = ["SPOTIFY_SECRET:${cfg.spotifySecretFile}"];
+
+        # Hardening
+        CapabilityBoundingSet = "";
+        LockPersonality = true;
+        #MemoryDenyWriteExecute = true; # Leads to coredump because V8 does JIT
+        PrivateUsers = true;
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        ProcSubset = "pid";
+        ProtectSystem = "strict";
+        RestrictAddressFamilies = [
+          "AF_INET"
+          "AF_INET6"
+          "AF_NETLINK"
+        ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [
+          "@system-service"
+          "@pkey"
+        ];
+        UMask = "0077";
+      };
+      wantedBy = ["multi-user.target"];
+    };
+    services.nginx = mkIf (cfg.nginxVirtualHost != null) {
+      enable = true;
+      virtualHosts.${cfg.nginxVirtualHost} = {
+        root = cfg.clientPackage;
+        locations."/".extraConfig = ''
+          add_header Content-Security-Policy "frame-ancestors 'none';" ;
+          add_header X-Content-Type-Options "nosniff" ;
+          try_files = $uri $uri/ /index.html ;
+        '';
+      };
+    };
+    services.mongodb = mkIf cfg.enableLocalDB {
+      enable = true;
+    };
+  };
+  meta.maintainers = with lib.maintainers; [patrickdag];
+}
diff --git a/nixos/modules/services/web-servers/bluemap.nix b/nixos/modules/services/web-servers/bluemap.nix
new file mode 100644
index 0000000000000..28eaad3db313e
--- /dev/null
+++ b/nixos/modules/services/web-servers/bluemap.nix
@@ -0,0 +1,311 @@
+{ config, lib, pkgs, ... }:
+let
+  cfg = config.services.bluemap;
+  format = pkgs.formats.hocon { };
+
+  coreConfig = format.generate "core.conf" cfg.coreSettings;
+  webappConfig = format.generate "webapp.conf" cfg.webappSettings;
+  webserverConfig = format.generate "webserver.conf" cfg.webserverSettings;
+
+  mapsFolder = pkgs.linkFarm "maps"
+    (lib.attrsets.mapAttrs' (name: value:
+      lib.nameValuePair "${name}.conf"
+        (format.generate "${name}.conf" value))
+      cfg.maps);
+
+  storageFolder = pkgs.linkFarm "storage"
+    (lib.attrsets.mapAttrs' (name: value:
+      lib.nameValuePair "${name}.conf"
+        (format.generate "${name}.conf" value))
+      cfg.storage);
+
+  configFolder = pkgs.linkFarm "bluemap-config" {
+    "maps" = mapsFolder;
+    "storages" = storageFolder;
+    "core.conf" = coreConfig;
+    "webapp.conf" = webappConfig;
+    "webserver.conf" = webserverConfig;
+    "resourcepacks" = pkgs.linkFarm "resourcepacks" cfg.resourcepacks;
+  };
+
+  inherit (lib) mkOption;
+in {
+  options.services.bluemap = {
+    enable = lib.mkEnableOption "bluemap";
+
+    eula = mkOption {
+      type = lib.types.bool;
+      description = ''
+        By changing this option to true you confirm that you own a copy of minecraft Java Edition,
+        and that you agree to minecrafts EULA.
+      '';
+      default = false;
+    };
+
+    defaultWorld = mkOption {
+      type = lib.types.path;
+      description = ''
+        The world used by the default map ruleset.
+        If you configure your own maps you do not need to set this.
+      '';
+      example = lib.literalExpression "\${config.services.minecraft.dataDir}/world";
+    };
+
+    enableRender = mkOption {
+      type = lib.types.bool;
+      description = "Enable rendering";
+      default = true;
+    };
+
+    webRoot = mkOption {
+      type = lib.types.path;
+      default = "/var/lib/bluemap/web";
+      description = "The directory for saving and serving the webapp and the maps";
+    };
+
+    enableNginx = mkOption {
+      type = lib.types.bool;
+      default = true;
+      description = "Enable configuring a virtualHost for serving the bluemap webapp";
+    };
+
+    host = mkOption {
+      type = lib.types.str;
+      default = "bluemap.${config.networking.domain}";
+      defaultText = lib.literalExpression "bluemap.\${config.networking.domain}";
+      description = "Domain to configure nginx for";
+    };
+
+    onCalendar = mkOption {
+      type = lib.types.str;
+      description = ''
+        How often to trigger rendering the map,
+        in the format of a systemd timer onCalendar configuration.
+        See {manpage}`systemd.timer(5)`.
+      '';
+      default = "*-*-* 03:10:00";
+    };
+
+    coreSettings = mkOption {
+      type = lib.types.submodule {
+        freeformType = format.type;
+        options = {
+          data = mkOption {
+            type = lib.types.path;
+            description = "Folder for where bluemap stores its data";
+            default = "/var/lib/bluemap";
+          };
+          metrics = lib.mkEnableOption "Sending usage metrics containing the version of bluemap in use";
+        };
+      };
+      description = "Settings for the core.conf file, [see upstream docs](https://github.com/BlueMap-Minecraft/BlueMap/blob/master/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/core.conf).";
+    };
+
+    webappSettings = mkOption {
+      type = lib.types.submodule {
+        freeformType = format.type;
+      };
+      default = {
+        enabled = true;
+        webroot = cfg.webRoot;
+      };
+      defaultText = lib.literalExpression ''
+        {
+          enabled = true;
+          webroot = config.services.bluemap.webRoot;
+        }
+      '';
+      description = "Settings for the webapp.conf file, see [upstream docs](https://github.com/BlueMap-Minecraft/BlueMap/blob/master/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/webapp.conf).";
+    };
+
+    webserverSettings = mkOption {
+      type = lib.types.submodule {
+        freeformType = format.type;
+        options = {
+          enabled = mkOption {
+            type = lib.types.bool;
+            description = ''
+              Enable bluemap's built-in webserver.
+              Disabled by default in nixos for use of nginx directly.
+            '';
+            default = false;
+          };
+        };
+      };
+      default = { };
+      description = ''
+        Settings for the webserver.conf file, usually not required.
+        [See upstream docs](https://github.com/BlueMap-Minecraft/BlueMap/blob/master/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/webserver.conf).
+      '';
+    };
+
+    maps = mkOption {
+      type = lib.types.attrsOf (lib.types.submodule {
+        freeformType = format.type;
+        options = {
+          world = lib.mkOption {
+            type = lib.types.path;
+            description = "Path to world folder containing the dimension to render";
+          };
+        };
+      });
+      default = {
+        "overworld" = {
+          world = "${cfg.defaultWorld}";
+          ambient-light = 0.1;
+          cave-detection-ocean-floor = -5;
+        };
+
+        "nether" = {
+          world = "${cfg.defaultWorld}/DIM-1";
+          sorting = 100;
+          sky-color = "#290000";
+          void-color = "#150000";
+          ambient-light = 0.6;
+          world-sky-light = 0;
+          remove-caves-below-y = -10000;
+          cave-detection-ocean-floor = -5;
+          cave-detection-uses-block-light = true;
+          max-y = 90;
+        };
+
+        "end" = {
+          world = "${cfg.defaultWorld}/DIM1";
+          sorting = 200;
+          sky-color = "#080010";
+          void-color = "#080010";
+          ambient-light = 0.6;
+          world-sky-light = 0;
+          remove-caves-below-y = -10000;
+          cave-detection-ocean-floor = -5;
+        };
+      };
+      defaultText = lib.literalExpression ''
+        {
+          "overworld" = {
+            world = "''${cfg.defaultWorld}";
+            ambient-light = 0.1;
+            cave-detection-ocean-floor = -5;
+          };
+
+          "nether" = {
+            world = "''${cfg.defaultWorld}/DIM-1";
+            sorting = 100;
+            sky-color = "#290000";
+            void-color = "#150000";
+            ambient-light = 0.6;
+            world-sky-light = 0;
+            remove-caves-below-y = -10000;
+            cave-detection-ocean-floor = -5;
+            cave-detection-uses-block-light = true;
+            max-y = 90;
+          };
+
+          "end" = {
+            world = "''${cfg.defaultWorld}/DIM1";
+            sorting = 200;
+            sky-color = "#080010";
+            void-color = "#080010";
+            ambient-light = 0.6;
+            world-sky-light = 0;
+            remove-caves-below-y = -10000;
+            cave-detection-ocean-floor = -5;
+          };
+        };
+      '';
+      description = ''
+        Settings for files in `maps/`.
+        If you define anything here you must define everything yourself.
+        See the default for an example with good options for the different world types.
+        For valid values [consult upstream docs](https://github.com/BlueMap-Minecraft/BlueMap/blob/master/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/maps/map.conf).
+      '';
+    };
+
+    storage = mkOption {
+      type = lib.types.attrsOf (lib.types.submodule {
+        freeformType = format.type;
+        options = {
+          storage-type = mkOption {
+            type = lib.types.enum [ "FILE" "SQL" ];
+            description = "Type of storage config";
+            default = "FILE";
+          };
+        };
+      });
+      description = ''
+        Where the rendered map will be stored.
+        Unless you are doing something advanced you should probably leave this alone and configure webRoot instead.
+        [See upstream docs](https://github.com/BlueMap-Minecraft/BlueMap/tree/master/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/storages)
+      '';
+      default = {
+        "file" = {
+          root = "${cfg.webRoot}/maps";
+        };
+      };
+      defaultText = lib.literalExpression ''
+        {
+          "file" = {
+            root = "''${config.services.bluemap.webRoot}/maps";
+          };
+        }
+      '';
+    };
+
+    resourcepacks = mkOption {
+      type = lib.types.attrsOf lib.types.pathInStore;
+      default = { };
+      description = "A set of resourcepacks to use, loaded in alphabetical order";
+    };
+  };
+
+
+  config = lib.mkIf cfg.enable {
+    assertions =
+      [ { assertion = config.services.bluemap.eula;
+          message = ''
+            You have enabled bluemap but have not accepted minecraft's EULA.
+            You can achieve this through setting `services.bluemap.eula = true`
+          '';
+        }
+      ];
+
+    services.bluemap.coreSettings.accept-download = cfg.eula;
+
+    systemd.services."render-bluemap-maps" = lib.mkIf cfg.enableRender {
+      serviceConfig = {
+        Type = "oneshot";
+        Group = "nginx";
+        UMask = "026";
+      };
+      script = ''
+        ${lib.getExe pkgs.bluemap} -c ${configFolder} -gs -r
+      '';
+    };
+
+    systemd.timers."render-bluemap-maps" = lib.mkIf cfg.enableRender {
+      wantedBy = [ "timers.target" ];
+      timerConfig = {
+        OnCalendar = cfg.onCalendar;
+        Persistent = true;
+        Unit = "render-bluemap-maps.service";
+      };
+    };
+
+    services.nginx.virtualHosts = lib.mkIf cfg.enableNginx {
+      "${cfg.host}" = {
+        root = config.services.bluemap.webRoot;
+        locations = {
+          "~* ^/maps/[^/]*/tiles/[^/]*.json$".extraConfig = ''
+            error_page 404 =200 /assets/emptyTile.json;
+            gzip_static always;
+          '';
+          "~* ^/maps/[^/]*/tiles/[^/]*.png$".tryFiles = "$uri =204";
+        };
+      };
+    };
+  };
+
+  meta = {
+    maintainers = with lib.maintainers; [ dandellion h7x4 ];
+  };
+}
diff --git a/nixos/modules/services/web-servers/garage.nix b/nixos/modules/services/web-servers/garage.nix
index f75829d64d67c..8d1966aee091b 100644
--- a/nixos/modules/services/web-servers/garage.nix
+++ b/nixos/modules/services/web-servers/garage.nix
@@ -10,7 +10,7 @@ in
 {
   meta = {
     doc = ./garage.md;
-    maintainers = with pkgs.lib.maintainers; [ raitobezarius ];
+    maintainers = [ ];
   };
 
   options.services.garage = {
@@ -49,8 +49,15 @@ in
 
           data_dir = mkOption {
             default = "/var/lib/garage/data";
-            type = types.path;
-            description = "The main data storage, put this on your large storage (e.g. high capacity HDD)";
+            example = [ {
+              path = "/var/lib/garage/data";
+              capacity = "2T";
+            } ];
+            type = with types; either path (listOf attrs);
+            description = ''
+              The directory in which Garage will store the data blocks of objects. This folder can be placed on an HDD.
+              Since v0.9.0, Garage supports multiple data directories, refer to https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#data_dir for the exact format.
+            '';
           };
         };
       };
@@ -70,7 +77,11 @@ in
       # to garage 1.0.0 while relying on the module-level default, they would be left
       # with a config which evaluates and builds, but then garage refuses to start
       # because either replication_factor or replication_mode is required.
-      # This assertion can be removed in NixOS 24.11, when all users have been warned once.
+      # The replication_factor option also was `toString`'ed before, which is
+      # now not possible anymore, so we prompt the user to change it to a string
+      # if present.
+      # These assertions can be removed in NixOS 24.11, when all users have been
+      # warned once.
       {
         assertion = (cfg.settings ? replication_factor || cfg.settings ? replication_mode) || lib.versionOlder cfg.package "1.0.0";
         message = ''
@@ -80,6 +91,22 @@ in
 
         '';
       }
+      {
+        assertion = lib.isString (cfg.settings.replication_mode or "");
+        message = ''
+          The explicit `replication_mode` option in `services.garage.settings`
+          has been removed and is now handled by the freeform settings in order
+          to allow it being completely absent (for Garage 1.x).
+          That module option previously `toString`'ed the value it's configured
+          with, which is now no longer possible.
+
+          You're still using a non-string here, please manually set it to
+          a string, or migrate to the separate setting keys introduced in 1.x.
+
+          Refer to https://garagehq.deuxfleurs.fr/documentation/working-documents/migration-1/
+          for the migration guide.
+        '';
+      }
     ];
 
     environment.etc."garage.toml" = {
diff --git a/nixos/modules/services/x11/window-managers/qtile.nix b/nixos/modules/services/x11/window-managers/qtile.nix
index 78152283a0a58..700ead8366008 100644
--- a/nixos/modules/services/x11/window-managers/qtile.nix
+++ b/nixos/modules/services/x11/window-managers/qtile.nix
@@ -4,7 +4,6 @@ with lib;
 
 let
   cfg = config.services.xserver.windowManager.qtile;
-  pyEnv = pkgs.python3.withPackages (p: [ (cfg.package.unwrapped or cfg.package) ] ++ (cfg.extraPackages p));
 in
 
 {
@@ -48,13 +47,24 @@ in
           ];
         '';
       };
+
+    finalPackage = mkOption {
+      type = types.package;
+      visible = false;
+      readOnly = true;
+      description = "The resulting Qtile package, bundled with extra packages";
+    };
   };
 
   config = mkIf cfg.enable {
+    services.xserver.windowManager.qtile.finalPackage = pkgs.python3.withPackages (p:
+      [ (cfg.package.unwrapped or cfg.package) ] ++ (cfg.extraPackages p)
+    );
+
     services.xserver.windowManager.session = [{
       name = "qtile";
       start = ''
-        ${pyEnv}/bin/qtile start -b ${cfg.backend} \
+        ${cfg.finalPackage}/bin/qtile start -b ${cfg.backend} \
         ${optionalString (cfg.configFile != null)
         "--config \"${cfg.configFile}\""} &
         waitPID=$!
diff --git a/nixos/modules/system/boot/binfmt.nix b/nixos/modules/system/boot/binfmt.nix
index 3605ce56910ed..1d702442f7f66 100644
--- a/nixos/modules/system/boot/binfmt.nix
+++ b/nixos/modules/system/boot/binfmt.nix
@@ -280,7 +280,7 @@ in {
   };
 
   config = {
-    boot.binfmt.registrations = builtins.listToAttrs (map (system: {
+    boot.binfmt.registrations = builtins.listToAttrs (map (system: assert system != pkgs.stdenv.hostPlatform.system; {
       name = system;
       value = { config, ... }: let
         interpreter = getEmulator system;
diff --git a/nixos/modules/system/boot/resolved.nix b/nixos/modules/system/boot/resolved.nix
index 64a15179438fe..b658a7a2dc05e 100644
--- a/nixos/modules/system/boot/resolved.nix
+++ b/nixos/modules/system/boot/resolved.nix
@@ -7,6 +7,20 @@ let
   dnsmasqResolve = config.services.dnsmasq.enable &&
                    config.services.dnsmasq.resolveLocalQueries;
 
+  resolvedConf = ''
+    [Resolve]
+    ${optionalString (config.networking.nameservers != [])
+      "DNS=${concatStringsSep " " config.networking.nameservers}"}
+    ${optionalString (cfg.fallbackDns != null)
+      "FallbackDNS=${concatStringsSep " " cfg.fallbackDns}"}
+    ${optionalString (cfg.domains != [])
+      "Domains=${concatStringsSep " " cfg.domains}"}
+    LLMNR=${cfg.llmnr}
+    DNSSEC=${cfg.dnssec}
+    DNSOverTLS=${cfg.dnsovertls}
+    ${config.services.resolved.extraConfig}
+  '';
+
 in
 {
 
@@ -126,60 +140,87 @@ in
       '';
     };
 
-  };
-
-  config = mkIf cfg.enable {
-
-    assertions = [
-      { assertion = !config.networking.useHostResolvConf;
-        message = "Using host resolv.conf is not supported with systemd-resolved";
-      }
-    ];
-
-    users.users.systemd-resolve.group = "systemd-resolve";
-
-    # add resolve to nss hosts database if enabled and nscd enabled
-    # system.nssModules is configured in nixos/modules/system/boot/systemd.nix
-    # added with order 501 to allow modules to go before with mkBefore
-    system.nssDatabases.hosts = (mkOrder 501 ["resolve [!UNAVAIL=return]"]);
-
-    systemd.additionalUpstreamSystemUnits = [
-      "systemd-resolved.service"
-    ];
-
-    systemd.services.systemd-resolved = {
-      wantedBy = [ "multi-user.target" ];
-      aliases = [ "dbus-org.freedesktop.resolve1.service" ];
-      restartTriggers = [ config.environment.etc."systemd/resolved.conf".source ];
-    };
-
-    environment.etc = {
-      "systemd/resolved.conf".text = ''
-        [Resolve]
-        ${optionalString (config.networking.nameservers != [])
-          "DNS=${concatStringsSep " " config.networking.nameservers}"}
-        ${optionalString (cfg.fallbackDns != null)
-          "FallbackDNS=${concatStringsSep " " cfg.fallbackDns}"}
-        ${optionalString (cfg.domains != [])
-          "Domains=${concatStringsSep " " cfg.domains}"}
-        LLMNR=${cfg.llmnr}
-        DNSSEC=${cfg.dnssec}
-        DNSOverTLS=${cfg.dnsovertls}
-        ${config.services.resolved.extraConfig}
+    boot.initrd.services.resolved.enable = mkOption {
+      default = config.boot.initrd.systemd.network.enable;
+      defaultText = "config.boot.initrd.systemd.network.enable";
+      description = ''
+        Whether to enable resolved for stage 1 networking.
+        Uses the toplevel 'services.resolved' options for 'resolved.conf'
       '';
-
-      # symlink the dynamic stub resolver of resolv.conf as recommended by upstream:
-      # https://www.freedesktop.org/software/systemd/man/systemd-resolved.html#/etc/resolv.conf
-      "resolv.conf".source = "/run/systemd/resolve/stub-resolv.conf";
-    } // optionalAttrs dnsmasqResolve {
-      "dnsmasq-resolv.conf".source = "/run/systemd/resolve/resolv.conf";
     };
 
-    # If networkmanager is enabled, ask it to interface with resolved.
-    networking.networkmanager.dns = "systemd-resolved";
-
-    networking.resolvconf.package = pkgs.systemd;
-
   };
 
+  config = mkMerge [
+    (mkIf cfg.enable {
+
+      assertions = [
+        { assertion = !config.networking.useHostResolvConf;
+          message = "Using host resolv.conf is not supported with systemd-resolved";
+        }
+      ];
+
+      users.users.systemd-resolve.group = "systemd-resolve";
+
+      # add resolve to nss hosts database if enabled and nscd enabled
+      # system.nssModules is configured in nixos/modules/system/boot/systemd.nix
+      # added with order 501 to allow modules to go before with mkBefore
+      system.nssDatabases.hosts = (mkOrder 501 ["resolve [!UNAVAIL=return]"]);
+
+      systemd.additionalUpstreamSystemUnits = [
+        "systemd-resolved.service"
+      ];
+
+      systemd.services.systemd-resolved = {
+        wantedBy = [ "sysinit.target" ];
+        aliases = [ "dbus-org.freedesktop.resolve1.service" ];
+        restartTriggers = [ config.environment.etc."systemd/resolved.conf".source ];
+      };
+
+      environment.etc = {
+        "systemd/resolved.conf".text = resolvedConf;
+
+        # symlink the dynamic stub resolver of resolv.conf as recommended by upstream:
+        # https://www.freedesktop.org/software/systemd/man/systemd-resolved.html#/etc/resolv.conf
+        "resolv.conf".source = "/run/systemd/resolve/stub-resolv.conf";
+      } // optionalAttrs dnsmasqResolve {
+        "dnsmasq-resolv.conf".source = "/run/systemd/resolve/resolv.conf";
+      };
+
+      # If networkmanager is enabled, ask it to interface with resolved.
+      networking.networkmanager.dns = "systemd-resolved";
+
+      networking.resolvconf.package = pkgs.systemd;
+
+    })
+
+    (mkIf config.boot.initrd.services.resolved.enable {
+
+      assertions = [
+        {
+          assertion = config.boot.initrd.systemd.enable;
+          message = "'boot.initrd.services.resolved.enable' can only be enabled with systemd stage 1.";
+        }
+      ];
+
+      boot.initrd.systemd = {
+        contents = {
+          "/etc/tmpfiles.d/resolv.conf".text =
+            "L /etc/resolv.conf - - - - /run/systemd/resolve/stub-resolv.conf";
+          "/etc/systemd/resolved.conf".text = resolvedConf;
+        };
+
+        additionalUpstreamUnits = ["systemd-resolved.service"];
+        users.systemd-resolve = {};
+        groups.systemd-resolve = {};
+        storePaths = ["${config.boot.initrd.systemd.package}/lib/systemd/systemd-resolved"];
+        services.systemd-resolved = {
+          wantedBy = ["sysinit.target"];
+          aliases = [ "dbus-org.freedesktop.resolve1.service" ];
+        };
+      };
+
+    })
+  ];
+
 }
diff --git a/nixos/modules/system/boot/systemd/initrd.nix b/nixos/modules/system/boot/systemd/initrd.nix
index cc32b2a15e7ce..6107a2594baf8 100644
--- a/nixos/modules/system/boot/systemd/initrd.nix
+++ b/nixos/modules/system/boot/systemd/initrd.nix
@@ -473,8 +473,8 @@ in {
         "${cfg.package.util-linux}/bin/umount"
         "${cfg.package.util-linux}/bin/sulogin"
 
-        # required for script services
-        "${pkgs.runtimeShell}"
+        # required for script services, and some tools like xfs still want the sh symlink
+        "${pkgs.bash}/bin"
 
         # so NSS can look up usernames
         "${pkgs.glibc}/lib/libnss_files.so.2"
diff --git a/nixos/modules/virtualisation/lxc-image-metadata.nix b/nixos/modules/virtualisation/lxc-image-metadata.nix
index 2c0568b4c4682..38d955798f3e0 100644
--- a/nixos/modules/virtualisation/lxc-image-metadata.nix
+++ b/nixos/modules/virtualisation/lxc-image-metadata.nix
@@ -87,10 +87,10 @@ in {
       contents = [
         {
           source = toYAML "metadata.yaml" {
-            architecture = builtins.elemAt (builtins.match "^([a-z0-9_]+).+" (toString pkgs.system)) 0;
+            architecture = builtins.elemAt (builtins.match "^([a-z0-9_]+).+" (toString pkgs.stdenv.hostPlatform.system)) 0;
             creation_date = 1;
             properties = {
-              description = "${config.system.nixos.distroName} ${config.system.nixos.codeName} ${config.system.nixos.label} ${pkgs.system}";
+              description = "${config.system.nixos.distroName} ${config.system.nixos.codeName} ${config.system.nixos.label} ${pkgs.stdenv.hostPlatform.system}";
               os = "${config.system.nixos.distroId}";
               release = "${config.system.nixos.codeName}";
             };
diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix
index c30f4577fdd86..3b81cfaf2b8ca 100644
--- a/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixos/modules/virtualisation/qemu-vm.nix
@@ -912,7 +912,7 @@ in
           "ppc64-linux" = "tpm-spapr";
           "armv7-linux" = "tpm-tis-device";
           "aarch64-linux" = "tpm-tis-device";
-        }.${pkgs.hostPlatform.system} or (throw "Unsupported system for TPM2 emulation in QEMU"));
+        }.${pkgs.stdenv.hostPlatform.system} or (throw "Unsupported system for TPM2 emulation in QEMU"));
         defaultText = ''
           Based on the guest platform Linux system:
 
diff --git a/nixos/release-combined.nix b/nixos/release-combined.nix
index 19223d46f6b9e..10834da35207e 100644
--- a/nixos/release-combined.nix
+++ b/nixos/release-combined.nix
@@ -50,7 +50,7 @@ in rec {
         (onFullSupported "nixos.dummy")
         (onAllSupported "nixos.iso_minimal")
         (onSystems ["x86_64-linux" "aarch64-linux"] "nixos.amazonImage")
-        (onFullSupported "nixos.iso_plasma5")
+        (onFullSupported "nixos.iso_plasma6")
         (onFullSupported "nixos.iso_gnome")
         (onFullSupported "nixos.manual")
         (onSystems ["x86_64-linux"] "nixos.ova")
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 3f3a99a83fee0..035c288c22e5c 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -130,6 +130,7 @@ in {
   apparmor = handleTest ./apparmor.nix {};
   archi = handleTest ./archi.nix {};
   armagetronad = handleTest ./armagetronad.nix {};
+  artalk = handleTest ./artalk.nix {};
   atd = handleTest ./atd.nix {};
   atop = handleTest ./atop.nix {};
   atuin = handleTest ./atuin.nix {};
@@ -144,6 +145,7 @@ in {
   bcachefs = handleTestOn ["x86_64-linux" "aarch64-linux"] ./bcachefs.nix {};
   beanstalkd = handleTest ./beanstalkd.nix {};
   bees = handleTest ./bees.nix {};
+  benchexec = handleTest ./benchexec.nix {};
   binary-cache = handleTest ./binary-cache.nix {};
   bind = handleTest ./bind.nix {};
   bird = handleTest ./bird.nix {};
@@ -204,6 +206,7 @@ in {
   code-server = handleTest ./code-server.nix {};
   coder = handleTest ./coder.nix {};
   collectd = handleTest ./collectd.nix {};
+  commafeed = handleTest ./commafeed.nix {};
   connman = handleTest ./connman.nix {};
   consul = handleTest ./consul.nix {};
   consul-template = handleTest ./consul-template.nix {};
@@ -309,6 +312,7 @@ in {
   fenics = handleTest ./fenics.nix {};
   ferm = handleTest ./ferm.nix {};
   ferretdb = handleTest ./ferretdb.nix {};
+  filesender = handleTest ./filesender.nix {};
   filesystems-overlayfs = runTest ./filesystems-overlayfs.nix;
   firefly-iii = handleTest ./firefly-iii.nix {};
   firefox = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox; };
@@ -587,7 +591,7 @@ in {
   mysql-backup = handleTest ./mysql/mysql-backup.nix {};
   mysql-replication = handleTest ./mysql/mysql-replication.nix {};
   n8n = handleTest ./n8n.nix {};
-  nagios = handleTest ./nagios.nix {};
+  nagios = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./nagios.nix {};
   nar-serve = handleTest ./nar-serve.nix {};
   nat.firewall = handleTest ./nat.nix { withFirewall = true; };
   nat.standalone = handleTest ./nat.nix { withFirewall = false; };
@@ -612,6 +616,7 @@ in {
   # TODO: put in networking.nix after the test becomes more complete
   networkingProxy = handleTest ./networking-proxy.nix {};
   nextcloud = handleTest ./nextcloud {};
+  nextjs-ollama-llm-ui = runTest ./web-apps/nextjs-ollama-llm-ui.nix;
   nexus = handleTest ./nexus.nix {};
   # TODO: Test nfsv3 + Kerberos
   nfs3 = handleTest ./nfs { version = 3; };
@@ -926,6 +931,7 @@ in {
   systemd-oomd = handleTest ./systemd-oomd.nix {};
   systemd-portabled = handleTest ./systemd-portabled.nix {};
   systemd-repart = handleTest ./systemd-repart.nix {};
+  systemd-resolved = handleTest ./systemd-resolved.nix {};
   systemd-shutdown = handleTest ./systemd-shutdown.nix {};
   systemd-sysupdate = runTest ./systemd-sysupdate.nix;
   systemd-sysusers-mutable = runTest ./systemd-sysusers-mutable.nix;
@@ -1004,7 +1010,7 @@ in {
   vault-dev = handleTest ./vault-dev.nix {};
   vault-postgresql = handleTest ./vault-postgresql.nix {};
   vaultwarden = handleTest ./vaultwarden.nix {};
-  vector = handleTest ./vector.nix {};
+  vector = handleTest ./vector {};
   vengi-tools = handleTest ./vengi-tools.nix {};
   victoriametrics = handleTest ./victoriametrics.nix {};
   vikunja = handleTest ./vikunja.nix {};
@@ -1042,6 +1048,7 @@ in {
   yabar = handleTest ./yabar.nix {};
   ydotool = handleTest ./ydotool.nix {};
   yggdrasil = handleTest ./yggdrasil.nix {};
+  your_spotify = handleTest ./your_spotify.nix {};
   zammad = handleTest ./zammad.nix {};
   zeronet-conservancy = handleTest ./zeronet-conservancy.nix {};
   zfs = handleTest ./zfs.nix {};
diff --git a/nixos/tests/artalk.nix b/nixos/tests/artalk.nix
new file mode 100644
index 0000000000000..1338e5cd380c6
--- /dev/null
+++ b/nixos/tests/artalk.nix
@@ -0,0 +1,28 @@
+import ./make-test-python.nix (
+  { lib, pkgs, ... }:
+  {
+
+    name = "artalk";
+
+    meta = {
+      maintainers = with lib.maintainers; [ moraxyc ];
+    };
+
+    nodes.machine =
+      { pkgs, ... }:
+      {
+        environment.systemPackages = [ pkgs.curl ];
+        services.artalk = {
+          enable = true;
+        };
+      };
+
+    testScript = ''
+      machine.wait_for_unit("artalk.service")
+
+      machine.wait_for_open_port(23366)
+
+      machine.succeed("curl --fail --max-time 10 http://127.0.0.1:23366/")
+    '';
+  }
+)
diff --git a/nixos/tests/ayatana-indicators.nix b/nixos/tests/ayatana-indicators.nix
index 5709ad2a1af69..cfd4d8099d112 100644
--- a/nixos/tests/ayatana-indicators.nix
+++ b/nixos/tests/ayatana-indicators.nix
@@ -28,8 +28,11 @@ in {
       enable = true;
       packages = with pkgs; [
         ayatana-indicator-datetime
+        ayatana-indicator-display
         ayatana-indicator-messages
+        ayatana-indicator-power
         ayatana-indicator-session
+        ayatana-indicator-sound
       ] ++ (with pkgs.lomiri; [
         lomiri-indicator-network
         telephony-service
@@ -40,6 +43,8 @@ in {
 
     services.accounts-daemon.enable = true; # messages
 
+    hardware.pulseaudio.enable = true; # sound
+
     # Lomiri-ish setup for Lomiri indicators
     # TODO move into a Lomiri module, once the package set is far enough for the DE to start
 
@@ -91,7 +96,7 @@ in {
 
     # Now check if all indicators were brought up successfully, and kill them for later
   '' + (runCommandOverAyatanaIndicators (service: let serviceExec = builtins.replaceStrings [ "." ] [ "-" ] service; in ''
-    machine.succeed("pgrep -u ${user} -f ${serviceExec}")
+    machine.wait_until_succeeds("pgrep -u ${user} -f ${serviceExec}")
     machine.succeed("pkill -f ${serviceExec}")
   '')) + ''
 
diff --git a/nixos/tests/benchexec.nix b/nixos/tests/benchexec.nix
new file mode 100644
index 0000000000000..3fc9ebc2c35f5
--- /dev/null
+++ b/nixos/tests/benchexec.nix
@@ -0,0 +1,54 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let
+  user = "alice";
+in
+{
+  name = "benchexec";
+
+  nodes.benchexec = {
+    imports = [ ./common/user-account.nix ];
+
+    programs.benchexec = {
+      enable = true;
+      users = [ user ];
+    };
+  };
+
+  testScript = { ... }:
+    let
+      runexec = lib.getExe' pkgs.benchexec "runexec";
+      echo = builtins.toString pkgs.benchexec;
+      test = lib.getExe (pkgs.writeShellApplication rec {
+        name = "test";
+        meta.mainProgram = name;
+        text = "echo '${echo}'";
+      });
+      wd = "/tmp";
+      stdout = "${wd}/runexec.out";
+      stderr = "${wd}/runexec.err";
+    in
+    ''
+      start_all()
+      machine.wait_for_unit("multi-user.target")
+      benchexec.succeed(''''\
+          systemd-run \
+            --property='StandardOutput=file:${stdout}' \
+            --property='StandardError=file:${stderr}' \
+            --unit=runexec --wait --user --machine='${user}@' \
+            --working-directory ${wd} \
+          '${runexec}' \
+            --debug \
+            --read-only-dir / \
+            --hidden-dir /home \
+            '${test}' \
+      '''')
+      benchexec.succeed("grep -s '${echo}' ${wd}/output.log")
+      benchexec.succeed("test \"$(grep -Ec '((start|wall|cpu)time|memory)=' ${stdout})\" = 4")
+      benchexec.succeed("! grep -E '(WARNING|ERROR)' ${stderr}")
+    '';
+
+  interactive.nodes.benchexec.services.kmscon = {
+    enable = true;
+    fonts = [{ name = "Fira Code"; package = pkgs.fira-code; }];
+  };
+})
diff --git a/nixos/tests/castopod.nix b/nixos/tests/castopod.nix
index 3257cd3d363c7..57e035354d23e 100644
--- a/nixos/tests/castopod.nix
+++ b/nixos/tests/castopod.nix
@@ -98,6 +98,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
             driver = Firefox(options=options, service=service)
             driver = Firefox(options=options)
             driver.implicitly_wait(30)
+            driver.set_page_load_timeout(60)
 
             # install ##########################################################
 
@@ -207,7 +208,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
             text = ''
               out=/tmp/podcast.mp3
               sox -n -r 48000 -t wav - synth ${targetPodcastDuration} sine 440 `
-              `| lame --noreplaygain -cbr -q 9 -b 320 - $out
+              `| lame --noreplaygain --cbr -q 9 -b 320 - $out
               FILESIZE="$(stat -c%s $out)"
               [ "$FILESIZE" -gt 0 ]
               [ "$FILESIZE" -le "${toString targetPodcastSize}" ]
diff --git a/nixos/tests/commafeed.nix b/nixos/tests/commafeed.nix
new file mode 100644
index 0000000000000..7b65720818a9b
--- /dev/null
+++ b/nixos/tests/commafeed.nix
@@ -0,0 +1,21 @@
+import ./make-test-python.nix (
+  { lib, ... }:
+  {
+    name = "commafeed";
+
+    nodes.server = {
+      services.commafeed = {
+        enable = true;
+      };
+    };
+
+    testScript = ''
+      server.start()
+      server.wait_for_unit("commafeed.service")
+      server.wait_for_open_port(8082)
+      server.succeed("curl --fail --silent http://localhost:8082")
+    '';
+
+    meta.maintainers = [ lib.maintainers.raroh73 ];
+  }
+)
diff --git a/nixos/tests/filesender.nix b/nixos/tests/filesender.nix
new file mode 100644
index 0000000000000..9274ddbf7e90e
--- /dev/null
+++ b/nixos/tests/filesender.nix
@@ -0,0 +1,137 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "filesender";
+  meta = {
+    maintainers = with lib.maintainers; [ nhnn ];
+    broken = pkgs.stdenv.isAarch64; # selenium.common.exceptions.WebDriverException: Message: Unsupported platform/architecture combination: linux/aarch64
+  };
+
+  nodes.filesender = { ... }: let
+    format = pkgs.formats.php { };
+  in {
+    networking.firewall.allowedTCPPorts = [ 80 ];
+
+    services.filesender.enable = true;
+    services.filesender.localDomain = "filesender";
+    services.filesender.settings = {
+      auth_sp_saml_authentication_source = "default";
+      auth_sp_saml_uid_attribute = "uid";
+      storage_filesystem_path = "/tmp";
+      site_url = "http://filesender";
+      force_ssl = false;
+      admin = "";
+      admin_email = "admin@localhost";
+      email_reply_to = "noreply@localhost";
+    };
+    services.simplesamlphp.filesender = {
+      settings = {
+        baseurlpath = "http://filesender/saml";
+        "module.enable".exampleauth = true;
+      };
+      authSources = {
+        admin = [ "core:AdminPassword" ];
+        default = format.lib.mkMixedArray [ "exampleauth:UserPass" ] {
+          "user:password" = {
+            uid = [ "user" ];
+            cn = [ "user" ];
+            mail = [ "user@nixos.org" ];
+          };
+        };
+      };
+    };
+  };
+
+  nodes.client =
+    { pkgs
+    , nodes
+    , ...
+    }:
+    let
+      filesenderIP = (builtins.head (nodes.filesender.networking.interfaces.eth1.ipv4.addresses)).address;
+    in
+    {
+      networking.hosts.${filesenderIP} = [ "filesender" ];
+
+      environment.systemPackages =
+        let
+          username = "user";
+          password = "password";
+          browser-test =
+            pkgs.writers.writePython3Bin "browser-test"
+              {
+                libraries = [ pkgs.python3Packages.selenium ];
+                flakeIgnore = [ "E124" "E501" ];
+              } ''
+              from selenium.webdriver.common.by import By
+              from selenium.webdriver import Firefox
+              from selenium.webdriver.firefox.options import Options
+              from selenium.webdriver.firefox.firefox_profile import FirefoxProfile
+              from selenium.webdriver.firefox.service import Service
+              from selenium.webdriver.support.ui import WebDriverWait
+              from selenium.webdriver.support import expected_conditions as EC
+              from subprocess import STDOUT
+              import string
+              import random
+              import logging
+              import time
+              selenium_logger = logging.getLogger("selenium")
+              selenium_logger.setLevel(logging.DEBUG)
+              selenium_logger.addHandler(logging.StreamHandler())
+              profile = FirefoxProfile()
+              profile.set_preference("browser.download.folderList", 2)
+              profile.set_preference("browser.download.manager.showWhenStarting", False)
+              profile.set_preference("browser.download.dir", "/tmp/firefox")
+              profile.set_preference("browser.helperApps.neverAsk.saveToDisk", "text/plain;text/txt")
+              options = Options()
+              options.profile = profile
+              options.add_argument('--headless')
+              service = Service(log_output=STDOUT)
+              driver = Firefox(options=options)
+              driver.set_window_size(1024, 768)
+              driver.implicitly_wait(30)
+              driver.get('http://filesender/')
+              wait = WebDriverWait(driver, 20)
+              wait.until(EC.title_contains("FileSender"))
+              driver.find_element(By.ID, "btn_logon").click()
+              wait.until(EC.title_contains("Enter your username and password"))
+              driver.find_element(By.ID, 'username').send_keys(
+                  '${username}'
+              )
+              driver.find_element(By.ID, 'password').send_keys(
+                  '${password}'
+              )
+              driver.find_element(By.ID, "submit_button").click()
+              wait.until(EC.title_contains("FileSender"))
+              wait.until(EC.presence_of_element_located((By.ID, "topmenu_logoff")))
+              test_string = "".join(random.choices(string.ascii_uppercase + string.digits, k=20))
+              with open("/tmp/test_file.txt", "w") as file:
+                  file.write(test_string)
+              driver.find_element(By.ID, "files").send_keys("/tmp/test_file.txt")
+              time.sleep(2)
+              driver.find_element(By.CSS_SELECTOR, '.start').click()
+              wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, ".download_link")))
+              download_link = driver.find_element(By.CSS_SELECTOR, '.download_link > textarea').get_attribute('value').strip()
+              driver.get(download_link)
+              wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, ".download")))
+              driver.find_element(By.CSS_SELECTOR, '.download').click()
+              wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, ".ui-dialog-buttonset > button:nth-child(2)")))
+              driver.find_element(By.CSS_SELECTOR, ".ui-dialog-buttonset > button:nth-child(2)").click()
+              driver.close()
+              driver.quit()
+            '';
+        in
+        [
+          pkgs.firefox-unwrapped
+          pkgs.geckodriver
+          browser-test
+        ];
+    };
+
+  testScript = ''
+    start_all()
+    filesender.wait_for_file("/run/phpfpm/filesender.sock")
+    filesender.wait_for_open_port(80)
+    if "If you have received an invitation to access this site as a guest" not in client.wait_until_succeeds("curl -sS -f http://filesender"):
+      raise Exception("filesender returned invalid html")
+    client.succeed("browser-test")
+  '';
+})
diff --git a/nixos/tests/firefly-iii.nix b/nixos/tests/firefly-iii.nix
index c93d799320a48..2373ba8360264 100644
--- a/nixos/tests/firefly-iii.nix
+++ b/nixos/tests/firefly-iii.nix
@@ -1,14 +1,19 @@
-import ./make-test-python.nix ({ lib, pkgs, ... }: {
+import ./make-test-python.nix ({ lib, ... }:
+
+let
+  db-pass = "Test2Test2";
+  app-key = "TestTestTestTestTestTestTestTest";
+in
+{
   name = "firefly-iii";
   meta.maintainers = [ lib.maintainers.savyajha ];
 
-  nodes.machine = { config, ... }: {
+  nodes.fireflySqlite = { config, ... }: {
     environment.etc = {
-      "firefly-iii-appkey".text = "TestTestTestTestTestTestTestTest";
+      "firefly-iii-appkey".text = app-key;
     };
     services.firefly-iii = {
       enable = true;
-      virtualHost = "http://localhost";
       enableNginx = true;
       settings = {
         APP_KEY_FILE = "/etc/firefly-iii-appkey";
@@ -18,9 +23,87 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
     };
   };
 
+  nodes.fireflyPostgresql = { config, pkgs, ... }: {
+    environment.etc = {
+      "firefly-iii-appkey".text = app-key;
+      "postgres-pass".text = db-pass;
+    };
+    services.firefly-iii = {
+      enable = true;
+      enableNginx = true;
+      settings = {
+        APP_KEY_FILE = "/etc/firefly-iii-appkey";
+        LOG_CHANNEL = "stdout";
+        SITE_OWNER = "mail@example.com";
+        DB_CONNECTION = "pgsql";
+        DB_DATABASE = "firefly";
+        DB_USERNAME = "firefly";
+        DB_PASSWORD_FILE = "/etc/postgres-pass";
+      };
+    };
+
+    services.postgresql = {
+      enable = true;
+      package = pkgs.postgresql_15;
+      authentication = ''
+        local all postgres peer
+        local firefly firefly password
+      '';
+      initialScript = pkgs.writeText "firefly-init.sql" ''
+        CREATE USER "firefly" WITH LOGIN PASSWORD '${db-pass}';
+        CREATE DATABASE "firefly" WITH OWNER "firefly";
+        CREATE SCHEMA AUTHORIZATION firefly;
+      '';
+    };
+  };
+
+  nodes.fireflyMysql = { config, pkgs, ... }: {
+    environment.etc = {
+      "firefly-iii-appkey".text = app-key;
+      "mysql-pass".text = db-pass;
+    };
+    services.firefly-iii = {
+      enable = true;
+      enableNginx = true;
+      settings = {
+        APP_KEY_FILE = "/etc/firefly-iii-appkey";
+        LOG_CHANNEL = "stdout";
+        SITE_OWNER = "mail@example.com";
+        DB_CONNECTION = "mysql";
+        DB_DATABASE = "firefly";
+        DB_USERNAME = "firefly";
+        DB_PASSWORD_FILE = "/etc/mysql-pass";
+        DB_SOCKET = "/run/mysqld/mysqld.sock";
+      };
+    };
+
+    services.mysql = {
+      enable = true;
+      package = pkgs.mariadb;
+      initialScript = pkgs.writeText "firefly-init.sql" ''
+        create database firefly DEFAULT CHARACTER SET utf8mb4;
+        create user 'firefly'@'localhost' identified by '${db-pass}';
+        grant all on firefly.* to 'firefly'@'localhost';
+      '';
+      settings.mysqld.character-set-server = "utf8mb4";
+    };
+  };
+
   testScript = ''
-    machine.wait_for_unit("phpfpm-firefly-iii.service")
-    machine.wait_for_unit("nginx.service")
-    machine.succeed("curl -fvvv -Ls http://localhost/ | grep 'Firefly III'")
+    fireflySqlite.wait_for_unit("phpfpm-firefly-iii.service")
+    fireflySqlite.wait_for_unit("nginx.service")
+    fireflySqlite.succeed("curl -fvvv -Ls http://localhost/ | grep 'Firefly III'")
+    fireflySqlite.succeed("curl -fvvv -Ls http://localhost/v1/js/app.js")
+    fireflySqlite.succeed("systemctl start firefly-iii-cron.service")
+    fireflyPostgresql.wait_for_unit("phpfpm-firefly-iii.service")
+    fireflyPostgresql.wait_for_unit("nginx.service")
+    fireflyPostgresql.wait_for_unit("postgresql.service")
+    fireflyPostgresql.succeed("curl -fvvv -Ls http://localhost/ | grep 'Firefly III'")
+    fireflyPostgresql.succeed("systemctl start firefly-iii-cron.service")
+    fireflyMysql.wait_for_unit("phpfpm-firefly-iii.service")
+    fireflyMysql.wait_for_unit("nginx.service")
+    fireflyMysql.wait_for_unit("mysql.service")
+    fireflyMysql.succeed("curl -fvvv -Ls http://localhost/ | grep 'Firefly III'")
+    fireflyMysql.succeed("systemctl start firefly-iii-cron.service")
   '';
 })
diff --git a/nixos/tests/garage/default.nix b/nixos/tests/garage/default.nix
index a42236e9a5bbe..b7f9bb4b865bd 100644
--- a/nixos/tests/garage/default.nix
+++ b/nixos/tests/garage/default.nix
@@ -51,4 +51,5 @@ in
   [
     "0_8"
     "0_9"
+    "1_x"
   ]
diff --git a/nixos/tests/garage/with-3node-replication.nix b/nixos/tests/garage/with-3node-replication.nix
index d4387b198d976..266a1082893f7 100644
--- a/nixos/tests/garage/with-3node-replication.nix
+++ b/nixos/tests/garage/with-3node-replication.nix
@@ -7,10 +7,10 @@ args@{ mkNode, ver, ... }:
   };
 
   nodes = {
-    node1 = mkNode { replicationMode = 3; publicV6Address = "fc00:1::1"; };
-    node2 = mkNode { replicationMode = 3; publicV6Address = "fc00:1::2"; };
-    node3 = mkNode { replicationMode = 3; publicV6Address = "fc00:1::3"; };
-    node4 = mkNode { replicationMode = 3; publicV6Address = "fc00:1::4"; };
+    node1 = mkNode { replicationMode = "3"; publicV6Address = "fc00:1::1"; };
+    node2 = mkNode { replicationMode = "3"; publicV6Address = "fc00:1::2"; };
+    node3 = mkNode { replicationMode = "3"; publicV6Address = "fc00:1::3"; };
+    node4 = mkNode { replicationMode = "3"; publicV6Address = "fc00:1::4"; };
   };
 
   testScript = ''
diff --git a/nixos/tests/installer-systemd-stage-1.nix b/nixos/tests/installer-systemd-stage-1.nix
index 1dd55dada042a..00205f9417718 100644
--- a/nixos/tests/installer-systemd-stage-1.nix
+++ b/nixos/tests/installer-systemd-stage-1.nix
@@ -19,7 +19,7 @@
     luksroot
     luksroot-format1
     luksroot-format2
-    # lvm
+    lvm
     separateBoot
     separateBootFat
     separateBootZfs
diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix
index 7e835041eb39f..3f57a64333dda 100644
--- a/nixos/tests/installer.nix
+++ b/nixos/tests/installer.nix
@@ -249,12 +249,11 @@ let
       with subtest("Check whether nixos-rebuild works"):
           target.succeed("nixos-rebuild switch >&2")
 
-      # FIXME: Nix 2.4 broke nixos-option, someone has to fix it.
-      # with subtest("Test nixos-option"):
-      #     kernel_modules = target.succeed("nixos-option boot.initrd.kernelModules")
-      #     assert "virtio_console" in kernel_modules
-      #     assert "List of modules" in kernel_modules
-      #     assert "qemu-guest.nix" in kernel_modules
+      with subtest("Test nixos-option"):
+          kernel_modules = target.succeed("nixos-option boot.initrd.kernelModules")
+          assert "virtio_console" in kernel_modules
+          assert "List of modules" in kernel_modules
+          assert "qemu-guest.nix" in kernel_modules
 
       target.shutdown()
 
@@ -975,6 +974,9 @@ in {
           "mount LABEL=nixos /mnt",
       )
     '';
+    extraConfig = optionalString systemdStage1 ''
+      boot.initrd.services.lvm.enable = true;
+    '';
   };
 
   # Boot off an encrypted root partition with the default LUKS header format
diff --git a/nixos/tests/keepalived.nix b/nixos/tests/keepalived.nix
index 16564511d85dc..052b36266d037 100644
--- a/nixos/tests/keepalived.nix
+++ b/nixos/tests/keepalived.nix
@@ -4,8 +4,8 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
 
   nodes = {
     node1 = { pkgs, ... }: {
-      networking.firewall.extraCommands = "iptables -A INPUT -p vrrp -j ACCEPT";
       services.keepalived.enable = true;
+      services.keepalived.openFirewall = true;
       services.keepalived.vrrpInstances.test = {
         interface = "eth1";
         state = "MASTER";
@@ -16,8 +16,8 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
       environment.systemPackages = [ pkgs.tcpdump ];
     };
     node2 = { pkgs, ... }: {
-      networking.firewall.extraCommands = "iptables -A INPUT -p vrrp -j ACCEPT";
       services.keepalived.enable = true;
+      services.keepalived.openFirewall = true;
       services.keepalived.vrrpInstances.test = {
         interface = "eth1";
         state = "MASTER";
diff --git a/nixos/tests/kernel-generic.nix b/nixos/tests/kernel-generic.nix
index 5f0e7b3e37cd7..11a3bf80c15e4 100644
--- a/nixos/tests/kernel-generic.nix
+++ b/nixos/tests/kernel-generic.nix
@@ -31,6 +31,8 @@ let
       linux_5_15_hardened
       linux_6_1_hardened
       linux_6_6_hardened
+      linux_6_8_hardened
+      linux_6_9_hardened
       linux_rt_5_4
       linux_rt_5_10
       linux_rt_5_15
diff --git a/nixos/tests/lomiri.nix b/nixos/tests/lomiri.nix
index 9d6337e9977cb..99f04a303be31 100644
--- a/nixos/tests/lomiri.nix
+++ b/nixos/tests/lomiri.nix
@@ -19,9 +19,13 @@ in {
       inherit description password;
     };
 
+    # To control mouse via scripting
+    programs.ydotool.enable = true;
+
     services.desktopManager.lomiri.enable = lib.mkForce true;
     services.displayManager.defaultSession = lib.mkForce "lomiri";
 
+    # Help with OCR
     fonts.packages = [ pkgs.inconsolata ];
 
     environment = {
@@ -114,17 +118,9 @@ in {
   enableOCR = true;
 
   testScript = { nodes, ... }: ''
-    def open_starter():
-        """
-        Open the starter, and ensure it's opened.
-        """
-        machine.send_key("meta_l-a")
-        # Look for any of the default apps
-        machine.wait_for_text(r"(Search|System|Settings|Morph|Browser|Terminal|Alacritty)")
-
     def toggle_maximise():
         """
-        Send the keybind to maximise the current window.
+        Maximise the current window.
         """
         machine.send_key("ctrl-meta_l-up")
 
@@ -135,13 +131,43 @@ in {
         machine.send_key("esc")
         machine.sleep(5)
 
+    def mouse_click(xpos, ypos):
+        """
+        Move the mouse to a screen location and hit left-click.
+        """
+
+        # Need to reset to top-left, --absolute doesn't work?
+        machine.execute("ydotool mousemove -- -10000 -10000")
+        machine.sleep(2)
+
+        # Move
+        machine.execute(f"ydotool mousemove -- {xpos} {ypos}")
+        machine.sleep(2)
+
+        # Click (C0 - left button: down & up)
+        machine.execute("ydotool click 0xC0")
+        machine.sleep(2)
+
+    def open_starter():
+        """
+        Open the starter, and ensure it's opened.
+        """
+
+        # Using the keybind has a chance of instantly closing the menu again? Just click the button
+        mouse_click(20, 30)
+
+        # Look for Search box & GUI-less content-hub examples, highest chances of avoiding false positives
+        machine.wait_for_text(r"(Search|Export|Import|Share)")
+
     start_all()
     machine.wait_for_unit("multi-user.target")
 
     # Lomiri in greeter mode should work & be able to start a session
     with subtest("lomiri greeter works"):
         machine.wait_for_unit("display-manager.service")
-        # Start page shows current tie
+        machine.wait_until_succeeds("pgrep -u lightdm -f 'lomiri --mode=greeter'")
+
+        # Start page shows current time
         machine.wait_for_text(r"(AM|PM)")
         machine.screenshot("lomiri_greeter_launched")
 
@@ -152,7 +178,6 @@ in {
 
         # Login
         machine.send_chars("${password}\n")
-        # Best way I can think of to differenciate "Lomiri in LightDM greeter mode" from "Lomiri in user shell mode"
         machine.wait_until_succeeds("pgrep -u ${user} -f 'lomiri --mode=full-shell'")
 
     # The session should start, and not be stuck in i.e. a crash loop
@@ -196,6 +221,17 @@ in {
         machine.screenshot("alacritty_opens")
         machine.send_key("alt-f4")
 
+    # Morph is how we go online
+    with subtest("morph browser works"):
+        open_starter()
+        machine.send_chars("Morph\n")
+        machine.wait_for_text(r"(Bookmarks|address|site|visited any)")
+        machine.screenshot("morph_open")
+
+        # morph-browser has a separate VM test, there isn't anything new we could test here
+
+        # Keep it running, we're using it to check content-hub communication from LSS
+
     # LSS provides DE settings
     with subtest("system settings open"):
         open_starter()
@@ -231,64 +267,75 @@ in {
         machine.wait_for_text("Morph") # or Gallery, but Morph is already packaged
         machine.screenshot("settings_content-hub_peers")
 
-        # Sadly, it doesn't seem possible to actually select a peer and attempt a content-hub data exchange with just the keyboard
+        # Select Morph as content source
+        mouse_click(300, 100)
 
-        machine.send_key("alt-f4")
+        # Expect Morph to be brought into the foreground, with its Downloads page open
+        machine.wait_for_text("No downloads")
 
-    # Morph is how we go online
-    with subtest("morph browser works"):
-        open_starter()
-        machine.send_chars("Morph\n")
-        machine.wait_for_text(r"(Bookmarks|address|site|visited any)")
-        machine.screenshot("morph_open")
+        # If content-hub encounters a problem, it may have crashed the original application issuing the request.
+        # Check that it's still alive
+        machine.succeed("pgrep -u ${user} -f lomiri-system-settings")
 
-        # morph-browser has a separate VM test, there isn't anything new we could test here
+        machine.screenshot("content-hub_exchange")
 
-        machine.send_key("alt-f4")
+        # Testing any more would require more applications & setup, the fact that it's already being attempted is a good sign
+        machine.send_key("esc")
+
+        machine.send_key("alt-f4") # LSS
+        machine.sleep(2) # focus is slow to switch to second window, closing it *really* helps with OCR afterwards
+        machine.send_key("alt-f4") # Morph
 
     # The ayatana indicators are an important part of the experience, and they hold the only graphical way of exiting the session.
-    # Reaching them via the intended way requires wayland mouse control, but ydotool lacks a module for its daemon:
-    # https://github.com/NixOS/nixpkgs/issues/183659
-    # Luckily, there's a test app that also displays their contents, but it's abit inconsistent. Hopefully this is *good-enough*.
+    # There's a test app we could use that also displays their contents, but it's abit inconsistent.
     with subtest("ayatana indicators work"):
-        open_starter()
-        machine.send_chars("Indicators\n")
-        machine.wait_for_text(r"(Indicators|Client|List|network|datetime|session)")
+        mouse_click(735, 0) # the cog in the top-right, for the session indicator
+        machine.wait_for_text(r"(Notifications|Rotation|Battery|Sound|Time|Date|System)")
         machine.screenshot("indicators_open")
 
-        # Element tab order within the indicator menus is not fully deterministic
-        # Only check that the indicators are listed & their items load
+        # Indicator order within the menus *should* be fixed based on per-indicator order setting
+        # Session is the one we clicked, but the last we should test (logout). Go as far left as we can test.
+        machine.send_key("left")
+        machine.send_key("left")
+        machine.send_key("left")
+        machine.send_key("left")
+        machine.send_key("left")
+        # Notifications are usually empty, nothing to check there
+
+        with subtest("ayatana indicator display works"):
+            # We start on this, don't go right
+            machine.wait_for_text("Lock")
+            machine.screenshot("indicators_display")
 
         with subtest("lomiri indicator network works"):
-            # Select indicator-network
-            machine.send_key("tab")
-            # Don't go further down, first entry
-            machine.send_key("ret")
+            machine.send_key("right")
             machine.wait_for_text(r"(Flight|Wi-Fi)")
             machine.screenshot("indicators_network")
 
-        machine.send_key("shift-tab")
-        machine.send_key("ret")
-        machine.wait_for_text(r"(Indicators|Client|List|network|datetime|session)")
+        with subtest("ayatana indicator sound works"):
+            machine.send_key("right")
+            machine.wait_for_text(r"(Silent|Volume)")
+            machine.screenshot("indicators_sound")
+
+        with subtest("ayatana indicator power works"):
+            machine.send_key("right")
+            machine.wait_for_text(r"(Charge|Battery settings)")
+            machine.screenshot("indicators_power")
 
         with subtest("ayatana indicator datetime works"):
-            # Select ayatana-indicator-datetime
-            machine.send_key("tab")
-            machine.send_key("down")
-            machine.send_key("ret")
+            machine.send_key("right")
             machine.wait_for_text("Time and Date Settings")
             machine.screenshot("indicators_timedate")
 
-        machine.send_key("shift-tab")
-        machine.send_key("ret")
-        machine.wait_for_text(r"(Indicators|Client|List|network|datetime|session)")
-
         with subtest("ayatana indicator session works"):
-            # Select ayatana-indicator-session
-            machine.send_key("tab")
-            machine.send_key("down")
-            machine.send_key("ret")
+            machine.send_key("right")
             machine.wait_for_text("Log Out")
             machine.screenshot("indicators_session")
+
+            # We should be able to log out and return to the greeter
+            mouse_click(720, 280) # "Log Out"
+            mouse_click(400, 240) # confirm logout
+            machine.wait_until_fails("pgrep -u ${user} -f 'lomiri --mode=full-shell'")
+            machine.wait_until_succeeds("pgrep -u lightdm -f 'lomiri --mode=greeter'")
   '';
 })
diff --git a/nixos/tests/mediamtx.nix b/nixos/tests/mediamtx.nix
index 8cacd02631d95..f40c4a8cb5832 100644
--- a/nixos/tests/mediamtx.nix
+++ b/nixos/tests/mediamtx.nix
@@ -1,57 +1,60 @@
-import ./make-test-python.nix ({ pkgs, lib, ...} :
+import ./make-test-python.nix (
+  { pkgs, lib, ... }:
 
-{
-  name = "mediamtx";
-  meta.maintainers = with lib.maintainers; [ fpletz ];
+  {
+    name = "mediamtx";
+    meta.maintainers = with lib.maintainers; [ fpletz ];
 
-  nodes = {
-    machine = { config, ... }: {
-      services.mediamtx = {
-        enable = true;
-        settings = {
-          metrics = true;
-          paths.all.source = "publisher";
+    nodes = {
+      machine = {
+        services.mediamtx = {
+          enable = true;
+          settings = {
+            metrics = true;
+            paths.all.source = "publisher";
+          };
         };
-      };
 
-      systemd.services.rtmp-publish = {
-        description = "Publish an RTMP stream to mediamtx";
-        after = [ "mediamtx.service" ];
-        bindsTo = [ "mediamtx.service" ];
-        wantedBy = [ "multi-user.target" ];
-        serviceConfig = {
-          DynamicUser = true;
-          Restart = "on-failure";
-          RestartSec = "1s";
-          TimeoutStartSec = "10s";
-          ExecStart = "${lib.getBin pkgs.ffmpeg-headless}/bin/ffmpeg -re -f lavfi -i smptebars=size=800x600:rate=10 -c libx264 -f flv rtmp://localhost:1935/test";
+        systemd.services.rtmp-publish = {
+          description = "Publish an RTMP stream to mediamtx";
+          after = [ "mediamtx.service" ];
+          bindsTo = [ "mediamtx.service" ];
+          wantedBy = [ "multi-user.target" ];
+          serviceConfig = {
+            DynamicUser = true;
+            Restart = "on-failure";
+            RestartSec = "1s";
+            TimeoutStartSec = "10s";
+            ExecStart = "${lib.getBin pkgs.ffmpeg-headless}/bin/ffmpeg -re -f lavfi -i smptebars=size=800x600:rate=10 -c libx264 -f flv rtmp://localhost:1935/test";
+          };
         };
-      };
 
-      systemd.services.rtmp-receive = {
-        description = "Receive an RTMP stream from mediamtx";
-        after = [ "rtmp-publish.service" ];
-        bindsTo = [ "rtmp-publish.service" ];
-        wantedBy = [ "multi-user.target" ];
-        serviceConfig = {
-          DynamicUser = true;
-          Restart = "on-failure";
-          RestartSec = "1s";
-          TimeoutStartSec = "10s";
-          ExecStart = "${lib.getBin pkgs.ffmpeg-headless}/bin/ffmpeg -y -re -i rtmp://localhost:1935/test -f flv /dev/null";
+        systemd.services.rtmp-receive = {
+          description = "Receive an RTMP stream from mediamtx";
+          after = [ "rtmp-publish.service" ];
+          bindsTo = [ "rtmp-publish.service" ];
+          wantedBy = [ "multi-user.target" ];
+          serviceConfig = {
+            DynamicUser = true;
+            Restart = "on-failure";
+            RestartSec = "1s";
+            TimeoutStartSec = "10s";
+            ExecStart = "${lib.getBin pkgs.ffmpeg-headless}/bin/ffmpeg -y -re -i rtmp://localhost:1935/test -f flv /dev/null";
+          };
         };
       };
     };
-  };
 
-  testScript = ''
-    start_all()
+    testScript = ''
+      start_all()
 
-    machine.wait_for_unit("mediamtx.service")
-    machine.wait_for_unit("rtmp-publish.service")
-    machine.wait_for_unit("rtmp-receive.service")
-    machine.wait_for_open_port(9998)
-    machine.succeed("curl http://localhost:9998/metrics | grep '^rtmp_conns.*state=\"publish\".*1$'")
-    machine.succeed("curl http://localhost:9998/metrics | grep '^rtmp_conns.*state=\"read\".*1$'")
-  '';
-})
+      machine.wait_for_unit("mediamtx.service")
+      machine.wait_for_unit("rtmp-publish.service")
+      machine.sleep(10)
+      machine.wait_for_unit("rtmp-receive.service")
+      machine.wait_for_open_port(9998)
+      machine.succeed("curl http://localhost:9998/metrics | grep '^rtmp_conns.*state=\"publish\".*1$'")
+      machine.succeed("curl http://localhost:9998/metrics | grep '^rtmp_conns.*state=\"read\".*1$'")
+    '';
+  }
+)
diff --git a/nixos/tests/mysql/common.nix b/nixos/tests/mysql/common.nix
index 1cf52347f4c74..ad54b0e00c1b3 100644
--- a/nixos/tests/mysql/common.nix
+++ b/nixos/tests/mysql/common.nix
@@ -4,7 +4,7 @@
     inherit (pkgs) mysql80;
   };
   perconaPackages = {
-    inherit (pkgs) percona-server_8_0;
+    inherit (pkgs) percona-server_lts percona-server_innovation;
   };
   mkTestName = pkg: "mariadb_${builtins.replaceStrings ["."] [""] (lib.versions.majorMinor pkg.version)}";
 }
diff --git a/nixos/tests/mysql/mysql.nix b/nixos/tests/mysql/mysql.nix
index 0a61f9d38fe2e..093da4f46aa10 100644
--- a/nixos/tests/mysql/mysql.nix
+++ b/nixos/tests/mysql/mysql.nix
@@ -146,6 +146,6 @@ in
   }) mariadbPackages)
   // (lib.mapAttrs (_: package: makeMySQLTest {
     inherit package;
-    name = "percona_8_0";
+    name = builtins.replaceStrings ["-"] ["_"] package.pname;
     hasMroonga = false; useSocketAuth = false;
   }) perconaPackages)
diff --git a/nixos/tests/stalwart-mail.nix b/nixos/tests/stalwart-mail.nix
index 634c0e2e39261..173b4fce4ad5d 100644
--- a/nixos/tests/stalwart-mail.nix
+++ b/nixos/tests/stalwart-mail.nix
@@ -18,8 +18,8 @@ in import ./make-test-python.nix ({ lib, ... }: {
         server.hostname = domain;
 
         certificate."snakeoil" = {
-          cert = "file://${certs.${domain}.cert}";
-          private-key = "file://${certs.${domain}.key}";
+          cert = "%{file:${certs.${domain}.cert}}%";
+          private-key = "%{file:${certs.${domain}.key}}%";
         };
 
         server.tls = {
@@ -40,24 +40,24 @@ in import ./make-test-python.nix ({ lib, ... }: {
           };
         };
 
-        session.auth.mechanisms = [ "PLAIN" ];
-        session.auth.directory = "in-memory";
-        storage.directory = "in-memory";  # shared with imap
+        session.auth.mechanisms = "[plain]";
+        session.auth.directory = "'in-memory'";
+        storage.directory = "in-memory";
 
-        session.rcpt.directory = "in-memory";
-        queue.outbound.next-hop = [ "local" ];
+        session.rcpt.directory = "'in-memory'";
+        queue.outbound.next-hop = "'local'";
 
         directory."in-memory" = {
           type = "memory";
           principals = [
             {
-              type = "individual";
+              class = "individual";
               name = "alice";
               secret = "foobar";
               email = [ "alice@${domain}" ];
             }
             {
-              type = "individual";
+              class = "individual";
               name = "bob";
               secret = "foobar";
               email = [ "bob@${domain}" ];
@@ -115,6 +115,6 @@ in import ./make-test-python.nix ({ lib, ... }: {
   '';
 
   meta = {
-    maintainers = with lib.maintainers; [ happysalada pacien ];
+    maintainers = with lib.maintainers; [ happysalada pacien onny ];
   };
 })
diff --git a/nixos/tests/step-ca.nix b/nixos/tests/step-ca.nix
index a855b590232dd..184c35f6b85cc 100644
--- a/nixos/tests/step-ca.nix
+++ b/nixos/tests/step-ca.nix
@@ -62,6 +62,24 @@ import ./make-test-python.nix ({ pkgs, ... }:
             };
           };
 
+        caclientcaddy =
+          { config, pkgs, ... }: {
+            security.pki.certificateFiles = [ "${test-certificates}/root_ca.crt" ];
+
+            networking.firewall.allowedTCPPorts = [ 80 443 ];
+
+            services.caddy = {
+              enable = true;
+              virtualHosts."caclientcaddy".extraConfig = ''
+                respond "Welcome to Caddy!"
+
+                tls caddy@example.org {
+                  ca https://caserver:8443/acme/acme/directory
+                }
+              '';
+            };
+          };
+
         catester = { config, pkgs, ... }: {
           security.pki.certificateFiles = [ "${test-certificates}/root_ca.crt" ];
         };
@@ -71,7 +89,12 @@ import ./make-test-python.nix ({ pkgs, ... }:
       ''
         catester.start()
         caserver.wait_for_unit("step-ca.service")
+        caserver.wait_until_succeeds("journalctl -o cat -u step-ca.service | grep '${pkgs.step-ca.version}'")
+
         caclient.wait_for_unit("acme-finished-caclient.target")
         catester.succeed("curl https://caclient/ | grep \"Welcome to nginx!\"")
+
+        caclientcaddy.wait_for_unit("caddy.service")
+        catester.succeed("curl https://caclientcaddy/ | grep \"Welcome to Caddy!\"")
       '';
   })
diff --git a/nixos/tests/stub-ld.nix b/nixos/tests/stub-ld.nix
index 25161301741b7..72b0aebf3e6ce 100644
--- a/nixos/tests/stub-ld.nix
+++ b/nixos/tests/stub-ld.nix
@@ -45,10 +45,10 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
           ${if32 "machine.succeed('test -L /${libDir32}/${ldsoBasename32}')"}
 
       with subtest("Try FHS executable"):
-          machine.copy_from_host('${test-exec.${pkgs.system}}','test-exec')
+          machine.copy_from_host('${test-exec.${pkgs.stdenv.hostPlatform.system}}','test-exec')
           machine.succeed('if test-exec/${exec-name} 2>outfile; then false; else [ $? -eq 127 ];fi')
           machine.succeed('grep -qi nixos outfile')
-          ${if32 "machine.copy_from_host('${test-exec.${pkgs32.system}}','test-exec32')"}
+          ${if32 "machine.copy_from_host('${test-exec.${pkgs32.stdenv.hostPlatform.system}}','test-exec32')"}
           ${if32 "machine.succeed('if test-exec32/${exec-name} 2>outfile32; then false; else [ $? -eq 127 ];fi')"}
           ${if32 "machine.succeed('grep -qi nixos outfile32')"}
 
diff --git a/nixos/tests/systemd-initrd-luks-fido2.nix b/nixos/tests/systemd-initrd-luks-fido2.nix
index f9f75ab7f301c..207f51f4dd9b4 100644
--- a/nixos/tests/systemd-initrd-luks-fido2.nix
+++ b/nixos/tests/systemd-initrd-luks-fido2.nix
@@ -9,7 +9,6 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
       # Booting off the encrypted disk requires having a Nix store available for the init script
       mountHostNixStore = true;
       useEFIBoot = true;
-      qemu.package = lib.mkForce (pkgs.qemu_test.override { canokeySupport = true; });
       qemu.options = [ "-device canokey,file=/tmp/canokey-file" ];
     };
     boot.loader.systemd-boot.enable = true;
diff --git a/nixos/tests/systemd-resolved.nix b/nixos/tests/systemd-resolved.nix
new file mode 100644
index 0000000000000..3eedc17f4b34f
--- /dev/null
+++ b/nixos/tests/systemd-resolved.nix
@@ -0,0 +1,75 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "systemd-resolved";
+  meta.maintainers = [ lib.maintainers.elvishjerricco ];
+
+  nodes.server = { lib, config, ... }: let
+    exampleZone = pkgs.writeTextDir "example.com.zone" ''
+      @ SOA ns.example.com. noc.example.com. 2019031301 86400 7200 3600000 172800
+      @       A       ${(lib.head config.networking.interfaces.eth1.ipv4.addresses).address}
+      @       AAAA    ${(lib.head config.networking.interfaces.eth1.ipv6.addresses).address}
+    '';
+  in {
+    networking.firewall.enable = false;
+    networking.useDHCP = false;
+
+    networking.interfaces.eth1.ipv6.addresses = lib.mkForce [
+      { address = "fd00::1"; prefixLength = 64; }
+    ];
+
+    services.knot = {
+      enable = true;
+      settings = {
+        server.listen = [
+          "0.0.0.0@53"
+          "::@53"
+        ];
+        template.default.storage = exampleZone;
+        zone."example.com".file = "example.com.zone";
+      };
+    };
+  };
+
+  nodes.client = { nodes, ... }: let
+    inherit (lib.head nodes.server.networking.interfaces.eth1.ipv4.addresses) address;
+  in {
+    networking.nameservers = [ address ];
+    networking.interfaces.eth1.ipv6.addresses = lib.mkForce [
+      { address = "fd00::2"; prefixLength = 64; }
+    ];
+    services.resolved.enable = true;
+    services.resolved.fallbackDns = [ ];
+    networking.useNetworkd = true;
+    networking.useDHCP = false;
+    systemd.network.networks."40-eth0".enable = false;
+
+    testing.initrdBackdoor = true;
+    boot.initrd = {
+      systemd.enable = true;
+      systemd.initrdBin = [ pkgs.iputils ];
+      network.enable = true;
+      services.resolved.enable = true;
+    };
+  };
+
+  testScript = { nodes, ... }: let
+    address4 = (lib.head nodes.server.networking.interfaces.eth1.ipv4.addresses).address;
+    address6 = (lib.head nodes.server.networking.interfaces.eth1.ipv6.addresses).address;
+  in ''
+    start_all()
+    server.wait_for_unit("multi-user.target")
+
+    def test_client():
+        query = client.succeed("resolvectl query example.com")
+        assert "${address4}" in query
+        assert "${address6}" in query
+        client.succeed("ping -4 -c 1 example.com")
+        client.succeed("ping -6 -c 1 example.com")
+
+    client.wait_for_unit("initrd.target")
+    test_client()
+    client.switch_root()
+
+    client.wait_for_unit("multi-user.target")
+    test_client()
+  '';
+})
diff --git a/nixos/tests/tigervnc.nix b/nixos/tests/tigervnc.nix
index ed575682d9338..b80cb49519c45 100644
--- a/nixos/tests/tigervnc.nix
+++ b/nixos/tests/tigervnc.nix
@@ -38,16 +38,18 @@ makeTest {
     server.succeed("Xvnc -geometry 720x576 :1 -PasswordFile vncpasswd >&2 &")
     server.wait_until_succeeds("nc -z localhost 5901", timeout=10)
     server.succeed("DISPLAY=:1 xwininfo -root | grep 720x576")
-    server.execute("DISPLAY=:1 display -size 360x200 -font sans -gravity south label:'HELLO VNC WORLD' >&2 &")
+    server.execute("DISPLAY=:1 display -size 360x200 -font sans -gravity south label:'HELLO VNC' >&2 &")
 
     client.wait_for_x()
     client.execute("vncviewer server:1 -PasswordFile vncpasswd >&2 &")
     client.wait_for_window(r"VNC")
     client.screenshot("screenshot")
     text = client.get_screen_text()
+
     # Displayed text
-    assert 'HELLO VNC WORLD' in text
+    assert 'HELLO VNC' in text
     # Client window title
-    assert 'TigerVNC' in text
+    # get_screen_text can't get correct string from screenshot
+    # assert 'TigerVNC' in text
   '';
 }
diff --git a/nixos/tests/vector.nix b/nixos/tests/vector.nix
deleted file mode 100644
index a55eb4e012c5b..0000000000000
--- a/nixos/tests/vector.nix
+++ /dev/null
@@ -1,37 +0,0 @@
-{ system ? builtins.currentSystem, config ? { }
-, pkgs ? import ../.. { inherit system config; } }:
-
-with import ../lib/testing-python.nix { inherit system pkgs; };
-with pkgs.lib;
-
-{
-  test1 = makeTest {
-    name = "vector-test1";
-    meta.maintainers = [ pkgs.lib.maintainers.happysalada ];
-
-    nodes.machine = { config, pkgs, ... }: {
-      services.vector = {
-        enable = true;
-        journaldAccess = true;
-        settings = {
-          sources.journald.type = "journald";
-
-          sinks = {
-            file = {
-              type = "file";
-              inputs = [ "journald" ];
-              path = "/var/lib/vector/logs.log";
-              encoding = { codec = "json"; };
-            };
-          };
-        };
-      };
-    };
-
-    # ensure vector is forwarding the messages appropriately
-    testScript = ''
-      machine.wait_for_unit("vector.service")
-      machine.wait_for_file("/var/lib/vector/logs.log")
-    '';
-  };
-}
diff --git a/nixos/tests/vector/api.nix b/nixos/tests/vector/api.nix
new file mode 100644
index 0000000000000..8aa3a0c1b771f
--- /dev/null
+++ b/nixos/tests/vector/api.nix
@@ -0,0 +1,39 @@
+import ../make-test-python.nix ({ lib, pkgs, ... }:
+
+{
+  name = "vector-api";
+  meta.maintainers = [ pkgs.lib.maintainers.happysalada ];
+
+  nodes.machineapi = { config, pkgs, ... }: {
+    services.vector = {
+      enable = true;
+      journaldAccess = false;
+      settings = {
+        api.enabled = true;
+
+        sources = {
+          demo_logs = {
+            type = "demo_logs";
+            format = "json";
+          };
+        };
+
+        sinks = {
+          file = {
+            type = "file";
+            inputs = [ "demo_logs" ];
+            path = "/var/lib/vector/logs.log";
+            encoding = { codec = "json"; };
+          };
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    machineapi.wait_for_unit("vector")
+    machineapi.wait_for_open_port(8686)
+    machineapi.succeed("journalctl -o cat -u vector.service | grep 'API server running'")
+    machineapi.wait_until_succeeds("curl -sSf http://localhost:8686/health")
+  '';
+})
diff --git a/nixos/tests/vector/default.nix b/nixos/tests/vector/default.nix
new file mode 100644
index 0000000000000..990b067e81774
--- /dev/null
+++ b/nixos/tests/vector/default.nix
@@ -0,0 +1,11 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../../.. { inherit system config; }
+}:
+
+{
+  file-sink = import ./file-sink.nix { inherit system pkgs; };
+  api = import ./api.nix { inherit system pkgs; };
+  dnstap = import ./dnstap.nix { inherit system pkgs; };
+  nginx-clickhouse = import ./nginx-clickhouse.nix { inherit system pkgs; };
+}
diff --git a/nixos/tests/vector/dnstap.nix b/nixos/tests/vector/dnstap.nix
new file mode 100644
index 0000000000000..15d643311b604
--- /dev/null
+++ b/nixos/tests/vector/dnstap.nix
@@ -0,0 +1,118 @@
+import ../make-test-python.nix ({ lib, pkgs, ... }:
+
+let
+  dnstapSocket = "/var/run/vector/dnstap.sock";
+in
+{
+  name = "vector-dnstap";
+  meta.maintainers = [ pkgs.lib.maintainers.happysalada ];
+
+  nodes = {
+    unbound = { config, pkgs, ... }: {
+      networking.firewall.allowedUDPPorts = [ 53 ];
+
+      services.vector = {
+        enable = true;
+
+        settings = {
+          sources = {
+            dnstap = {
+              type = "dnstap";
+              multithreaded = true;
+              mode = "unix";
+              lowercase_hostnames = true;
+              socket_file_mode = 504;
+              socket_path = "${dnstapSocket}";
+            };
+          };
+
+          sinks = {
+            file = {
+              type = "file";
+              inputs = [ "dnstap" ];
+              path = "/var/lib/vector/logs.log";
+              encoding = { codec = "json"; };
+            };
+          };
+        };
+      };
+
+      systemd.services.vector.serviceConfig = {
+        RuntimeDirectory = "vector";
+        RuntimeDirectoryMode = "0770";
+      };
+
+      services.unbound = {
+        enable = true;
+        enableRootTrustAnchor = false;
+        package = pkgs.unbound-full;
+        settings = {
+          server = {
+            interface = [ "0.0.0.0" "::" ];
+            access-control = [ "192.168.1.0/24 allow" ];
+
+            domain-insecure = "local";
+            private-domain = "local";
+
+            local-zone = "local. static";
+            local-data = [
+              ''"test.local. 10800 IN A 192.168.123.5"''
+            ];
+          };
+
+          dnstap = {
+            dnstap-enable = "yes";
+            dnstap-socket-path = "${dnstapSocket}";
+            dnstap-send-identity = "yes";
+            dnstap-send-version = "yes";
+            dnstap-log-client-query-messages = "yes";
+            dnstap-log-client-response-messages = "yes";
+          };
+        };
+      };
+
+      systemd.services.unbound = {
+        after = [ "vector.service" ];
+        wants = [ "vector.service" ];
+        serviceConfig = {
+          # DNSTAP access
+          ReadWritePaths = [ "/var/run/vector" ];
+          SupplementaryGroups = [ "vector" ];
+        };
+      };
+    };
+
+    dnsclient = { config, pkgs, ... }: {
+      environment.systemPackages = [ pkgs.dig ];
+    };
+  };
+
+  testScript = ''
+    unbound.wait_for_unit("unbound")
+    unbound.wait_for_unit("vector")
+
+    unbound.wait_until_succeeds(
+      "journalctl -o cat -u vector.service | grep 'Socket permissions updated to 0o770'"
+    )
+    unbound.wait_until_succeeds(
+      "journalctl -o cat -u vector.service | grep 'component_type=dnstap' | grep 'Listening... path=\"${dnstapSocket}\"'"
+    )
+
+    unbound.wait_for_file("${dnstapSocket}")
+    unbound.succeed("test 770 -eq $(stat -c '%a' ${dnstapSocket})")
+
+    dnsclient.wait_for_unit("network-online.target")
+    dnsclient.succeed(
+      "dig @unbound test.local"
+    )
+
+    unbound.wait_for_file("/var/lib/vector/logs.log")
+
+    unbound.wait_until_succeeds(
+      "grep ClientQuery /var/lib/vector/logs.log | grep '\"domainName\":\"test.local.\"' | grep '\"rcodeName\":\"NoError\"'"
+    )
+    unbound.wait_until_succeeds(
+      "grep ClientResponse /var/lib/vector/logs.log | grep '\"domainName\":\"test.local.\"' | grep '\"rData\":\"192.168.123.5\"'"
+    )
+  '';
+})
diff --git a/nixos/tests/vector/file-sink.nix b/nixos/tests/vector/file-sink.nix
new file mode 100644
index 0000000000000..2220d20ac55c3
--- /dev/null
+++ b/nixos/tests/vector/file-sink.nix
@@ -0,0 +1,49 @@
+import ../make-test-python.nix ({ lib, pkgs, ... }:
+
+{
+  name = "vector-test1";
+  meta.maintainers = [ pkgs.lib.maintainers.happysalada ];
+
+  nodes.machine = { config, pkgs, ... }: {
+    services.vector = {
+      enable = true;
+      journaldAccess = true;
+      settings = {
+        sources = {
+          journald.type = "journald";
+
+          vector_metrics.type = "internal_metrics";
+
+          vector_logs.type = "internal_logs";
+        };
+
+        sinks = {
+          file = {
+            type = "file";
+            inputs = [ "journald" "vector_logs" ];
+            path = "/var/lib/vector/logs.log";
+            encoding = { codec = "json"; };
+          };
+
+          prometheus_exporter = {
+            type = "prometheus_exporter";
+            inputs = [ "vector_metrics" ];
+            address = "[::]:9598";
+          };
+        };
+      };
+    };
+  };
+
+  # ensure vector is forwarding the messages appropriately
+  testScript = ''
+    machine.wait_for_unit("vector.service")
+    machine.wait_for_open_port(9598)
+    machine.wait_until_succeeds("journalctl -o cat -u vector.service | grep 'version=\"${pkgs.vector.version}\"'")
+    machine.wait_until_succeeds("journalctl -o cat -u vector.service | grep 'API is disabled'")
+    machine.wait_until_succeeds("curl -sSf http://localhost:9598/metrics | grep vector_build_info")
+    machine.wait_until_succeeds("curl -sSf http://localhost:9598/metrics | grep vector_component_received_bytes_total | grep journald")
+    machine.wait_until_succeeds("curl -sSf http://localhost:9598/metrics | grep vector_utilization | grep prometheus_exporter")
+    machine.wait_for_file("/var/lib/vector/logs.log")
+  '';
+})
diff --git a/nixos/tests/vector/nginx-clickhouse.nix b/nixos/tests/vector/nginx-clickhouse.nix
new file mode 100644
index 0000000000000..3d99bac6ac161
--- /dev/null
+++ b/nixos/tests/vector/nginx-clickhouse.nix
@@ -0,0 +1,168 @@
+import ../make-test-python.nix ({ lib, pkgs, ... }:
+
+{
+  name = "vector-nginx-clickhouse";
+  meta.maintainers = [ pkgs.lib.maintainers.happysalada ];
+
+  nodes = {
+    clickhouse = { config, pkgs, ... }: {
+      virtualisation.memorySize = 4096;
+
+      # Clickhouse module can't listen on a non-loopback IP.
+      networking.firewall.allowedTCPPorts = [ 6000 ];
+      services.clickhouse.enable = true;
+
+      # Exercise Vector sink->source for now.
+      services.vector = {
+        enable = true;
+
+        settings = {
+          sources = {
+            vector_source = {
+              type = "vector";
+              address = "[::]:6000";
+            };
+          };
+
+          sinks = {
+            clickhouse = {
+              type = "clickhouse";
+              inputs = [ "vector_source" ];
+              endpoint = "http://localhost:8123";
+              database = "nginxdb";
+              table = "access_logs";
+              skip_unknown_fields = true;
+            };
+          };
+        };
+      };
+    };
+
+    nginx = { config, pkgs, ... }: {
+      services.nginx = {
+        enable = true;
+        virtualHosts.localhost = {};
+      };
+
+      services.vector = {
+        enable = true;
+
+        settings = {
+          sources = {
+            nginx_logs = {
+              type = "file";
+              include = [ "/var/log/nginx/access.log" ];
+              read_from = "end";
+            };
+          };
+
+          sinks = {
+            vector_sink = {
+              type = "vector";
+              inputs = [ "nginx_logs" ];
+              address = "clickhouse:6000";
+            };
+          };
+        };
+      };
+
+      systemd.services.vector.serviceConfig = {
+        SupplementaryGroups = [ "nginx" ];
+      };
+    };
+  };
+
+  testScript =
+  let
+    # work around quote/substitution complexity by Nix, Perl, bash and SQL.
+    databaseDDL = pkgs.writeText "database.sql" "CREATE DATABASE IF NOT EXISTS nginxdb";
+
+    tableDDL = pkgs.writeText "table.sql" ''
+      CREATE TABLE IF NOT EXISTS  nginxdb.access_logs (
+        message String
+      )
+      ENGINE = MergeTree()
+      ORDER BY tuple()
+    '';
+
+    # Graciously taken from https://clickhouse.com/docs/en/integrations/vector
+    tableView = pkgs.writeText "table-view.sql" ''
+      CREATE MATERIALIZED VIEW nginxdb.access_logs_view
+      (
+        RemoteAddr String,
+        Client String,
+        RemoteUser String,
+        TimeLocal DateTime,
+        RequestMethod String,
+        Request String,
+        HttpVersion String,
+        Status Int32,
+        BytesSent Int64,
+        UserAgent String
+      )
+      ENGINE = MergeTree()
+      ORDER BY RemoteAddr
+      POPULATE AS
+      WITH
+       splitByWhitespace(message) as split,
+       splitByRegexp('\S \d+ "([^"]*)"', message) as referer
+      SELECT
+        split[1] AS RemoteAddr,
+        split[2] AS Client,
+        split[3] AS RemoteUser,
+        parseDateTimeBestEffort(replaceOne(trim(LEADING '[' FROM split[4]), ':', ' ')) AS TimeLocal,
+        trim(LEADING '"' FROM split[6]) AS RequestMethod,
+        split[7] AS Request,
+        trim(TRAILING '"' FROM split[8]) AS HttpVersion,
+        split[9] AS Status,
+        split[10] AS BytesSent,
+        trim(BOTH '"' from referer[2]) AS UserAgent
+      FROM
+        (SELECT message FROM nginxdb.access_logs)
+    '';
+
+    selectQuery = pkgs.writeText "select.sql" "SELECT * from nginxdb.access_logs_view";
+  in
+  ''
+    clickhouse.wait_for_unit("clickhouse")
+    clickhouse.wait_for_open_port(8123)
+
+    clickhouse.wait_until_succeeds(
+      "journalctl -o cat -u clickhouse.service | grep 'Started ClickHouse server'"
+    )
+
+    clickhouse.wait_for_unit("vector")
+    clickhouse.wait_for_open_port(6000)
+
+    clickhouse.succeed(
+      "cat ${databaseDDL} | clickhouse-client"
+    )
+
+    clickhouse.succeed(
+      "cat ${tableDDL} | clickhouse-client"
+    )
+
+    clickhouse.succeed(
+      "cat ${tableView} | clickhouse-client"
+    )
+
+    nginx.wait_for_unit("nginx")
+    nginx.wait_for_open_port(80)
+    nginx.wait_for_unit("vector")
+    nginx.wait_until_succeeds(
+      "journalctl -o cat -u vector.service | grep 'Starting file server'"
+    )
+
+    nginx.succeed("curl http://localhost/")
+    nginx.succeed("curl http://localhost/")
+
+    nginx.wait_for_file("/var/log/nginx/access.log")
+    nginx.wait_until_succeeds(
+      "journalctl -o cat -u vector.service | grep 'Found new file to watch. file=/var/log/nginx/access.log'"
+    )
+
+    clickhouse.wait_until_succeeds(
+      "cat ${selectQuery} | clickhouse-client | grep 'curl'"
+    )
+  '';
+})
diff --git a/nixos/tests/virtualbox.nix b/nixos/tests/virtualbox.nix
index 3c2a391233dbd..5fce3ba548123 100644
--- a/nixos/tests/virtualbox.nix
+++ b/nixos/tests/virtualbox.nix
@@ -98,7 +98,6 @@ let
     cfg = (import ../lib/eval-config.nix {
       system = if use64bitGuest then "x86_64-linux" else "i686-linux";
       modules = [
-        ../modules/profiles/minimal.nix
         (testVMConfig vmName vmScript)
       ];
     }).config;
diff --git a/nixos/tests/web-apps/nextjs-ollama-llm-ui.nix b/nixos/tests/web-apps/nextjs-ollama-llm-ui.nix
new file mode 100644
index 0000000000000..3bb9d1e62aefe
--- /dev/null
+++ b/nixos/tests/web-apps/nextjs-ollama-llm-ui.nix
@@ -0,0 +1,22 @@
+{ lib, ... }:
+
+{
+  name = "nextjs-ollama-llm-ui";
+  meta.maintainers = with lib.maintainers; [ malteneuss ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    {
+      services.nextjs-ollama-llm-ui = {
+        enable = true;
+        port = 8080;
+      };
+    };
+
+  testScript = ''
+    # Ensure the service is started and reachable
+    machine.wait_for_unit("nextjs-ollama-llm-ui.service")
+    machine.wait_for_open_port(8080)
+    machine.succeed("curl --fail http://127.0.0.1:8080")
+  '';
+}
diff --git a/nixos/tests/web-apps/pretalx.nix b/nixos/tests/web-apps/pretalx.nix
index 76e261b2207ec..cbb6580aa0515 100644
--- a/nixos/tests/web-apps/pretalx.nix
+++ b/nixos/tests/web-apps/pretalx.nix
@@ -5,13 +5,16 @@
   meta.maintainers = lib.teams.c3d2.members;
 
   nodes = {
-    pretalx = {
+    pretalx = { config, ... }: {
       networking.extraHosts = ''
         127.0.0.1 talks.local
       '';
 
       services.pretalx = {
         enable = true;
+        plugins = with config.services.pretalx.package.plugins; [
+          pages
+        ];
         nginx.domain = "talks.local";
         settings = {
           site.url = "http://talks.local";
diff --git a/nixos/tests/your_spotify.nix b/nixos/tests/your_spotify.nix
new file mode 100644
index 0000000000000..a1fa0e459a8e1
--- /dev/null
+++ b/nixos/tests/your_spotify.nix
@@ -0,0 +1,33 @@
+import ./make-test-python.nix ({pkgs, ...}: {
+  name = "your_spotify";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [patrickdag];
+  };
+
+  nodes.machine = {
+    services.your_spotify = {
+      enable = true;
+      spotifySecretFile = pkgs.writeText "spotifySecretFile" "deadbeef";
+      settings = {
+        CLIENT_ENDPOINT = "http://localhost";
+        API_ENDPOINT = "http://localhost:3000";
+        SPOTIFY_PUBLIC = "beefdead";
+      };
+      enableLocalDB = true;
+      nginxVirtualHost = "localhost";
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("your_spotify.service")
+
+    machine.wait_for_open_port(3000)
+    machine.wait_for_open_port(80)
+
+    out = machine.succeed("curl --fail -X GET 'http://localhost:3000/'")
+    assert "Hello !" in out
+
+    out = machine.succeed("curl --fail -X GET 'http://localhost:80/'")
+    assert "<title>Your Spotify</title>" in out
+  '';
+})