about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/configuration/abstractions.section.md2
-rw-r--r--nixos/doc/manual/release-notes/rl-2405.section.md89
-rw-r--r--nixos/lib/qemu-common.nix2
-rw-r--r--nixos/modules/config/no-x-libs.nix5
-rw-r--r--nixos/modules/hardware/openrazer.nix64
-rw-r--r--nixos/modules/security/pam.nix4
-rw-r--r--nixos/modules/services/databases/postgresql.nix33
-rw-r--r--nixos/modules/services/databases/redis.nix11
-rw-r--r--nixos/modules/services/desktops/pipewire/wireplumber.nix183
-rw-r--r--nixos/modules/services/home-automation/ebusd.nix24
-rw-r--r--nixos/modules/services/home-automation/wyoming/faster-whisper.nix2
-rw-r--r--nixos/modules/services/logging/logrotate.nix2
-rw-r--r--nixos/modules/services/mail/mailman.nix11
-rw-r--r--nixos/modules/services/monitoring/vmagent.nix114
-rw-r--r--nixos/modules/services/networking/adguardhome.nix122
-rw-r--r--nixos/modules/services/networking/knot.nix2
-rw-r--r--nixos/modules/services/security/vault.nix4
-rw-r--r--nixos/modules/services/system/dbus.nix2
-rw-r--r--nixos/modules/services/web-apps/coder.nix1
-rw-r--r--nixos/modules/services/web-apps/movim.nix22
-rw-r--r--nixos/modules/services/web-apps/nextcloud.md28
-rw-r--r--nixos/modules/services/web-apps/nextcloud.nix9
-rw-r--r--nixos/modules/services/web-servers/nginx/default.nix3
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix28
-rw-r--r--nixos/modules/virtualisation/incus.nix17
-rw-r--r--nixos/modules/virtualisation/qemu-vm.nix18
-rw-r--r--nixos/tests/adguardhome.nix85
-rw-r--r--nixos/tests/all-tests.nix1
-rw-r--r--nixos/tests/coder.nix4
-rw-r--r--nixos/tests/installed-tests/default.nix2
-rw-r--r--nixos/tests/libreswan.nix6
-rw-r--r--nixos/tests/nextcloud/default.nix2
-rw-r--r--nixos/tests/openssh.nix38
-rw-r--r--nixos/tests/ssh-keys.nix12
-rw-r--r--nixos/tests/swayfx.nix207
-rw-r--r--nixos/tests/switch-test.nix21
36 files changed, 869 insertions, 311 deletions
diff --git a/nixos/doc/manual/configuration/abstractions.section.md b/nixos/doc/manual/configuration/abstractions.section.md
index 5bc44aa722457..06356c472ba94 100644
--- a/nixos/doc/manual/configuration/abstractions.section.md
+++ b/nixos/doc/manual/configuration/abstractions.section.md
@@ -35,7 +35,7 @@ in
 {
   services.httpd.virtualHosts =
     { "blog.example.org" = (commonConfig // { documentRoot = "/webroot/blog.example.org"; });
-      "wiki.example.org" = (commonConfig // { documentRoot = "/webroot/wiki.example.com"; });
+      "wiki.example.org" = (commonConfig // { documentRoot = "/webroot/wiki.example.org"; });
     };
 }
 ```
diff --git a/nixos/doc/manual/release-notes/rl-2405.section.md b/nixos/doc/manual/release-notes/rl-2405.section.md
index bd3ae81cc2ec8..6e0281794626e 100644
--- a/nixos/doc/manual/release-notes/rl-2405.section.md
+++ b/nixos/doc/manual/release-notes/rl-2405.section.md
@@ -10,13 +10,13 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - `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 highlight 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.
+  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.
 
-- The default kernel package has been updated from 6.1 to 6.6. All supported kernels remain available.
+- (TODO not sure what path to use here) The default kernel package has been updated from 6.1 to 6.6. All supported kernels remain available.
 
 - NixOS now installs a stub ELF loader that prints an informative error message when users attempt to run binaries not made for NixOS.
    - This can be disabled through the `environment.stub-ld.enable` option.
@@ -30,9 +30,13 @@ 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.
 
-- Julia environments can now be built with arbitrary packages from the ecosystem using the `.withPackages` function. For example: `julia.withPackages ["Plots"]`.
+- `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 PipeWire and WirePlumber modules have removed support for using
+- `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."
@@ -41,11 +45,9 @@ Use `services.pipewire.extraConfig` or `services.pipewire.configPackages` for Pi
   Refer to upstream [upgrade instructions](https://goteleport.com/docs/management/operations/upgrading/)
   and release notes for [v15](https://goteleport.com/docs/changelog/#1500-013124).
 
-- A new option `systemd.sysusers.enable` was added. If enabled, users and
+- `systemd.sysusers.enable` option was added. If enabled, users and
   groups are created with systemd-sysusers instead of with a custom perl script.
 
-- The default dbus implementation has transitioned to dbus-broker from the classic dbus daemon for better performance and reliability. Users can revert to the classic dbus daemon by setting `services.dbus.implementation = "dbus";`. For detailed deviations, refer to [dbus-broker's deviations page](https://github.com/bus1/dbus-broker/wiki/Deviations).
-
 - `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
@@ -67,16 +69,16 @@ Use `services.pipewire.extraConfig` or `services.pipewire.configPackages` for Pi
   }
   ```
 
-- The initial Incus LTS release (v6.0.x) is now available through `virtualisation.incus` as the default. 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.
+- `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.
 
-- 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.
+- 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.
 
-- lua interpreters default LUA_PATH and LUA_CPATH are not overriden by nixpkgs
+- `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.
 
-- The desktop mode of Lomiri (formerly known as Unity8), 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.
+- `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.
 
 ## New Services {#sec-release-24.05-new-services}
 
@@ -98,7 +100,7 @@ Use `services.pipewire.extraConfig` or `services.pipewire.configPackages` for Pi
 
 - [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)
 
-- systemd's gateway, upload, and remote services, which provides 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`'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).
 
 - [GNS3](https://www.gns3.com/), a network software emulator. Available as [services.gns3-server](#opt-services.gns3-server.enable).
 
@@ -125,7 +127,7 @@ Use `services.pipewire.extraConfig` or `services.pipewire.configPackages` for Pi
 - [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).
 
 - [Anki Sync Server](https://docs.ankiweb.net/sync-server.html), the official sync server built into recent versions of Anki. Available as [services.anki-sync-server](#opt-services.anki-sync-server.enable).
-The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been marked deprecated and will be dropped after 24.05 due to lack of maintenance of the anki-sync-server softwares.
+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.
 
 - [mautrix-meta](https://github.com/mautrix/meta), a Matrix <-> Facebook and Matrix <-> Instagram hybrid puppeting/relaybot bridge. Available as services.mautrix-meta
 
@@ -137,7 +139,7 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
 
 - [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).
 
-- A self-hosted management server for the [Netbird](https://netbird.io). Available as [services.netbird.server](#opt-services.netbird.server.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).
 
 - [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).
 
@@ -149,9 +151,9 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
 
 - [Monado](https://monado.freedesktop.org/), an open source XR runtime. Available as [services.monado](#opt-services.monado.enable).
 
-- [Pretix](https://pretix.eu/about/en/), an open source ticketing software for events. Available as [services.pretix]($opt-services-pretix.enable).
+- [Pretix](https://pretix.eu/about/en/), an open source ticketing software for events. Available as [services.pretix](#opt-services.pretix.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).
+- [microsocks](https://github.com/rofl0r/microsocks), a tiny, portable SOCKS5 server with very moderate resource usage. Available as [services.microsocks](#opt-services.microsocks.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).
 
@@ -161,21 +163,21 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
 
 - [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`.
 
-- [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).
+- [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).
 
 - [TuxClocker](https://github.com/Lurkki14/tuxclocker), a hardware control and monitoring program. Available as [programs.tuxclocker](#opt-programs.tuxclocker.enable).
 
-- binfmt option for AppImage-run to support running [AppImage](https://appimage.org/)'s seamlessly on NixOS.. Available as [programs.appimage.binfmt](#opt-programs.appimage.binfmt).
+- [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).
 
 - [nh](https://github.com/viperML/nh), yet another Nix CLI helper. Available as [programs.nh](#opt-programs.nh.enable).
 
 - [ALVR](https://github.com/alvr-org/alvr), a VR desktop streamer. Available as [programs.alvr](#opt-programs.alvr.enable)
 
-- [RustDesk](https://rustdesk.com), a full-featured open source remote control alternative for self-hosting and security with minimal configuration. Alternative to TeamViewer.
+- [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).
 
-- [Scrutiny](https://github.com/AnalogJ/scrutiny), a S.M.A.R.T monitoring tool for hard disks with a web frontend.
+- [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).
+- [davis](https://github.com/tchapi/davis), a simple CardDav and CalDav server inspired by Baïkal. Available as [services.davis](#opt-services.davis.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)
 
@@ -201,6 +203,20 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
 
 - `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.
 
+- `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
+
 - 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.
 
 - `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.
@@ -210,7 +226,7 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
 
 - `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 create the binary at `$out/bin/${pname}` rather than `$out/bin/${pname}-${version}`, which will break downstream workarounds.
+- `appimageTools.wrapAppImage` now creates the binary at `$out/bin/${pname}` rather than `$out/bin/${pname}-${version}`, which will break downstream workarounds.
 
 - `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.
 
@@ -230,16 +246,16 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
   The list in `nixos/modules/virtualisation/amazon-ec2-amis.nix` will stop
   being updated and will be removed in the future.
 
-- The option `services.postgresql.ensureUsers._.ensurePermissions` has been removed as it's
+- 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's more suited for managing the data inside a postgresql database.
+  instead or a tool that is more suited for managing the data inside a postgresql database.
 
 - `idris2` was updated to v0.7.0. This version introduces breaking changes. Check out the [changelog](https://github.com/idris-lang/Idris2/blob/v0.7.0/CHANGELOG.md#v070) for details.
 
 - `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}`
 
-- `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/)
+- `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/).
 
 - `services.neo4j.allowUpgrade` was removed and no longer has any effect. Neo4j 5 supports automatic rolling upgrades.
 
@@ -279,6 +295,8 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
 
 - `network-interfaces.target` system target was removed as it has been deprecated for a long time. Use `network.target` instead.
 
+- `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.
+
 - `azure-cli` now has extension support. For example, to install the `aks-preview` extension, use
 
   ```nix
@@ -305,6 +323,10 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
 
 - `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).
 
 - `services.nginx` will no longer advertise HTTP/3 availability automatically. This must now be manually added, preferably to each location block.
@@ -323,7 +345,7 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
 
 - `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.
 
-- `kanata` package has been updated to v1.5.0, which includes [breaking changes](https://github.com/jtroo/kanata/releases/tag/v1.5.0).
+- `kanata` package has been updated to v1.6.0, 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.
 
 - `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.
@@ -333,11 +355,14 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
 
 - `gtest` package has been updated past v1.13.0, which requires C++14 or higher.
 
-- The latest available version of Nextcloud is v28 (available as `pkgs.nextcloud28`). The installation logic is as follows:
+- Nextcloud 26 has been removed since it's not maintained anymore by upstream.
+
+- The latest available version of Nextcloud is 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.nextcloud28` will be installed by default.
+  - 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.
-  - Please note that an upgrade from v26 (or older) to v28 directly is not possible. Please upgrade to `nextcloud27` (or earlier) first. Nextcloud prohibits skipping major versions while upgrading. You can upgrade by declaring [`services.nextcloud.package = pkgs.nextcloud27;`](options.html#opt-services.nextcloud.package).
+  - Please note that an upgrade from v27 (or older) to v29 directly is not possible. Please upgrade to `nextcloud28` (or earlier) first. Nextcloud prohibits skipping major versions while upgrading. You can upgrade by declaring [`services.nextcloud.package = pkgs.nextcloud28;`](options.html#opt-services.nextcloud.package).
+  - Known warnings after the upgrade are documented in [](#module-services-nextcloud-known-warnings).
 
 - 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`.
 
@@ -572,10 +597,6 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
 
 - `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).
 
-- `security.pam.enableSSHAgentAuth` was renamed to `security.pam.sshAgentAuth.enable` and an `authorizedKeysFiles`
-  option was added, to control which `authorized_keys` files are trusted.  It defaults to the previous behaviour,
-  **which is insecure**: see [#31611](https://github.com/NixOS/nixpkgs/issues/31611).
-
 - [](#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.
 
 - [TODO: reword to place an attribute at the front] A new top-level package set, `pkgsExtraHardening` is 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).
diff --git a/nixos/lib/qemu-common.nix b/nixos/lib/qemu-common.nix
index b946f62d93dc3..f1e19c5b3b720 100644
--- a/nixos/lib/qemu-common.nix
+++ b/nixos/lib/qemu-common.nix
@@ -35,6 +35,8 @@ rec {
         aarch64-linux = "${qemuPkg}/bin/qemu-system-aarch64 -machine virt,gic-version=max,accel=kvm:tcg -cpu max";
         powerpc64le-linux = "${qemuPkg}/bin/qemu-system-ppc64 -machine powernv";
         powerpc64-linux = "${qemuPkg}/bin/qemu-system-ppc64 -machine powernv";
+        riscv32-linux = "${qemuPkg}/bin/qemu-system-riscv32 -machine virt";
+        riscv64-linux = "${qemuPkg}/bin/qemu-system-riscv64 -machine virt";
         x86_64-darwin = "${qemuPkg}/bin/qemu-kvm -cpu max";
       };
       otherHostGuestMatrix = {
diff --git a/nixos/modules/config/no-x-libs.nix b/nixos/modules/config/no-x-libs.nix
index c9a133d0558a5..1d7976cef36a2 100644
--- a/nixos/modules/config/no-x-libs.nix
+++ b/nixos/modules/config/no-x-libs.nix
@@ -31,8 +31,11 @@ with lib;
       cairo = super.cairo.override { x11Support = false; };
       dbus = super.dbus.override { x11Support = false; };
       fastfetch = super.fastfetch.override { vulkanSupport = false; waylandSupport = false; x11Support = false; };
+      ffmpeg = super.ffmpeg.override { ffmpegVariant = "headless"; };
       ffmpeg_4 = super.ffmpeg_4.override { ffmpegVariant = "headless"; };
       ffmpeg_5 = super.ffmpeg_5.override { ffmpegVariant = "headless"; };
+      ffmpeg_6 = super.ffmpeg_6.override { ffmpegVariant = "headless"; };
+      ffmpeg_7 = super.ffmpeg_7.override { ffmpegVariant = "headless"; };
       # dep of graphviz, libXpm is optional for Xpm support
       gd = super.gd.override { withXorg = false; };
       ghostscript = super.ghostscript.override { cupsSupport = false; x11Support = false; };
@@ -44,7 +47,7 @@ with lib;
       gst_all_1 = super.gst_all_1 // {
         gst-plugins-bad = super.gst_all_1.gst-plugins-bad.override { guiSupport = false; };
         gst-plugins-base = super.gst_all_1.gst-plugins-base.override { enableWayland = false; enableX11 = false; };
-        gst-plugins-good = super.gst_all_1.gst-plugins-good.override { enableX11 = false; };
+        gst-plugins-good = super.gst_all_1.gst-plugins-good.override { enableWayland = false; enableX11 = false; gtkSupport = false; qt5Support = false; qt6Support = false; };
       };
       imagemagick = super.imagemagick.override { libX11Support = false; libXtSupport = false; };
       imagemagickBig = super.imagemagickBig.override { libX11Support = false; libXtSupport = false; };
diff --git a/nixos/modules/hardware/openrazer.nix b/nixos/modules/hardware/openrazer.nix
index 99b5510543233..5ba6abfdb3d7e 100644
--- a/nixos/modules/hardware/openrazer.nix
+++ b/nixos/modules/hardware/openrazer.nix
@@ -19,7 +19,9 @@ let
       [Startup]
       sync_effects_enabled = ${toPyBoolStr cfg.syncEffectsEnabled}
       devices_off_on_screensaver = ${toPyBoolStr cfg.devicesOffOnScreensaver}
-      mouse_battery_notifier = ${toPyBoolStr cfg.mouseBatteryNotifier}
+      battery_notifier = ${toPyBoolStr (cfg.mouseBatteryNotifier || cfg.batteryNotifier.enable)}
+      battery_notifier_freq = ${builtins.toString cfg.batteryNotifier.frequency}
+      battery_notifier_percent = ${builtins.toString cfg.batteryNotifier.percentage}
 
       [Statistics]
       key_statistics = ${toPyBoolStr cfg.keyStatistics}
@@ -86,6 +88,41 @@ in
         '';
       };
 
+      batteryNotifier = mkOption {
+        description = ''
+          Settings for device battery notifications.
+        '';
+        default = {};
+        type = types.submodule {
+          options = {
+            enable = mkOption {
+              type = types.bool;
+              default = true;
+              description = ''
+                Mouse battery notifier.
+              '';
+            };
+            frequency = mkOption {
+              type = types.int;
+              default = 600;
+              description = ''
+                How often battery notifications should be shown (in seconds).
+                A value of 0 disables notifications.
+              '';
+            };
+
+            percentage = mkOption {
+              type = types.int;
+              default = 33;
+              description = ''
+                At what battery percentage the device should reach before
+                sending notifications.
+              '';
+            };
+          };
+        };
+      };
+
       keyStatistics = mkOption {
         type = types.bool;
         default = false;
@@ -107,6 +144,13 @@ in
   };
 
   config = mkIf cfg.enable {
+    warnings = flatten [
+      (optional cfg.mouseBatteryNotifier ''
+        The option openrazer.mouseBatteryNotifier is deprecated.
+        Please use openrazer.batteryNotifier instead to enable and configure battery notifications.
+      '')
+    ];
+
     boot.extraModulePackages = [ kernelPackages.openrazer ];
     boot.kernelModules = drivers;
 
@@ -127,15 +171,15 @@ in
     systemd.user.services.openrazer-daemon = {
       description = "Daemon to manage razer devices in userspace";
       unitConfig.Documentation = "man:openrazer-daemon(8)";
-        # Requires a graphical session so the daemon knows when the screensaver
-        # starts. See the 'devicesOffOnScreensaver' option.
-        wantedBy = [ "graphical-session.target" ];
-        partOf = [ "graphical-session.target" ];
-        serviceConfig = {
-          Type = "dbus";
-          BusName = "org.razer";
-          ExecStart = "${daemonExe} --foreground";
-          Restart = "always";
+      # Requires a graphical session so the daemon knows when the screensaver
+      # starts. See the 'devicesOffOnScreensaver' option.
+      wantedBy = [ "graphical-session.target" ];
+      partOf = [ "graphical-session.target" ];
+      serviceConfig = {
+        Type = "dbus";
+        BusName = "org.razer";
+        ExecStart = "${daemonExe} --foreground";
+        Restart = "always";
       };
     };
   };
diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix
index a9bd28b154794..5d3bed2fb02c8 100644
--- a/nixos/modules/security/pam.nix
+++ b/nixos/modules/security/pam.nix
@@ -1044,9 +1044,7 @@ in
           See [issue #31611](https://github.com/NixOS/nixpkgs/issues/31611)
           :::
         '';
-        example = [ "/etc/ssh/authorized_keys.d/%u" ];
-        default = config.services.openssh.authorizedKeysFiles;
-        defaultText = literalExpression "config.services.openssh.authorizedKeysFiles";
+        default = [ "/etc/ssh/authorized_keys.d/%u" ];
       };
     };
 
diff --git a/nixos/modules/services/databases/postgresql.nix b/nixos/modules/services/databases/postgresql.nix
index 8a386b4848b91..35d3ba0aa2094 100644
--- a/nixos/modules/services/databases/postgresql.nix
+++ b/nixos/modules/services/databases/postgresql.nix
@@ -1,8 +1,31 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
+  inherit (lib)
+    attrValues
+    concatMapStrings
+    concatStringsSep
+    const
+    elem
+    filterAttrs
+    isString
+    literalExpression
+    mapAttrs
+    mapAttrsToList
+    mkAfter
+    mkBefore
+    mkDefault
+    mkEnableOption
+    mkIf
+    mkMerge
+    mkOption
+    mkPackageOption
+    mkRemovedOptionModule
+    mkRenamedOptionModule
+    optionalString
+    types
+    versionAtLeast
+    ;
 
   cfg = config.services.postgresql;
 
@@ -24,7 +47,7 @@ let
     if true == value then "yes"
     else if false == value then "no"
     else if isString value then "'${lib.replaceStrings ["'"] ["''"] value}'"
-    else toString value;
+    else builtins.toString value;
 
   # The main PostgreSQL configuration file.
   configFile = pkgs.writeTextDir "postgresql.conf" (concatStringsSep "\n" (mapAttrsToList (n: v: "${n} = ${toStr v}") (filterAttrs (const (x: x != null)) cfg.settings)));
@@ -439,7 +462,7 @@ in
   config = mkIf cfg.enable {
 
     assertions = map ({ name, ensureDBOwnership, ... }: {
-      assertion = ensureDBOwnership -> builtins.elem name cfg.ensureDatabases;
+      assertion = ensureDBOwnership -> elem name cfg.ensureDatabases;
       message = ''
         For each database user defined with `services.postgresql.ensureUsers` and
         `ensureDBOwnership = true;`, a database with the same name must be defined
@@ -537,7 +560,7 @@ in
         # Wait for PostgreSQL to be ready to accept connections.
         postStart =
           ''
-            PSQL="psql --port=${toString cfg.settings.port}"
+            PSQL="psql --port=${builtins.toString cfg.settings.port}"
 
             while ! $PSQL -d postgres -c "" 2> /dev/null; do
                 if ! kill -0 "$MAINPID"; then exit 1; fi
diff --git a/nixos/modules/services/databases/redis.nix b/nixos/modules/services/databases/redis.nix
index 1da2fa9f11604..ad88a4f589a20 100644
--- a/nixos/modules/services/databases/redis.nix
+++ b/nixos/modules/services/databases/redis.nix
@@ -57,9 +57,9 @@ in {
       package = mkPackageOption pkgs "redis" { };
 
       vmOverCommit = mkEnableOption ''
-        setting of vm.overcommit_memory to 1
+        set `vm.overcommit_memory` sysctl to 1
         (Suggested for Background Saving: <https://redis.io/docs/get-started/faq/>)
-      '';
+      '' // { default = true; };
 
       servers = mkOption {
         type = with types; attrsOf (submodule ({ config, name, ... }: {
@@ -312,10 +312,9 @@ in {
       '';
     }) enabledServers);
 
-    boot.kernel.sysctl = mkMerge [
-      { "vm.nr_hugepages" = "0"; }
-      ( mkIf cfg.vmOverCommit { "vm.overcommit_memory" = "1"; } )
-    ];
+    boot.kernel.sysctl = mkIf cfg.vmOverCommit {
+      "vm.overcommit_memory" = "1";
+    };
 
     networking.firewall.allowedTCPPorts = concatMap (conf:
       optional conf.openFirewall conf.port
diff --git a/nixos/modules/services/desktops/pipewire/wireplumber.nix b/nixos/modules/services/desktops/pipewire/wireplumber.nix
index 6ab62eb03c25f..c924801bcd8ba 100644
--- a/nixos/modules/services/desktops/pipewire/wireplumber.nix
+++ b/nixos/modules/services/desktops/pipewire/wireplumber.nix
@@ -1,18 +1,40 @@
 { config, lib, pkgs, ... }:
 
 let
-  inherit (builtins) attrNames concatMap length;
+  inherit (builtins) concatMap;
   inherit (lib) maintainers;
-  inherit (lib.attrsets) attrByPath filterAttrs;
+  inherit (lib.attrsets) attrByPath mapAttrsToList;
   inherit (lib.lists) flatten optional;
   inherit (lib.modules) mkIf;
   inherit (lib.options) literalExpression mkOption;
-  inherit (lib.strings) hasPrefix;
-  inherit (lib.types) bool listOf package;
+  inherit (lib.strings) concatStringsSep makeSearchPath;
+  inherit (lib.types) bool listOf attrsOf package lines;
+  inherit (lib.path) subpath;
 
   pwCfg = config.services.pipewire;
   cfg = pwCfg.wireplumber;
   pwUsedForAudio = pwCfg.audio.enable;
+
+  json = pkgs.formats.json { };
+
+  configSectionsToConfFile = path: value:
+    pkgs.writeTextDir
+      path
+      (concatStringsSep "\n" (
+        mapAttrsToList
+          (section: content: "${section} = " + (builtins.toJSON content))
+          value
+      ));
+
+  mapConfigToFiles = config:
+    mapAttrsToList
+      (name: value: configSectionsToConfFile "share/wireplumber/wireplumber.conf.d/${name}.conf" value)
+      config;
+
+  mapScriptsToFiles = scripts:
+    mapAttrsToList
+      (relativePath: value: pkgs.writeTextDir (subpath.join ["share/wireplumber/scripts" relativePath]) value)
+      scripts;
 in
 {
   meta.maintainers = [ maintainers.k900 ];
@@ -33,6 +55,114 @@ in
         description = "The WirePlumber derivation to use.";
       };
 
+      extraConfig = mkOption {
+        # Two layer attrset is necessary before using JSON, because of the whole
+        # config file not being a JSON object, but a concatenation of JSON objects
+        # in sections.
+        type = attrsOf (attrsOf json.type);
+        default = { };
+        example = literalExpression ''{
+          "log-level-debug" = {
+            "context.properties" = {
+              # Output Debug log messages as opposed to only the default level (Notice)
+              "log.level" = "D";
+            };
+          };
+          "wh-1000xm3-ldac-hq" = {
+            "monitor.bluez.rules" = [
+              {
+                matches = [
+                  {
+                    # Match any bluetooth device with ids equal to that of a WH-1000XM3
+                    "device.name" = "~bluez_card.*";
+                    "device.product.id" = "0x0cd3";
+                    "device.vendor.id" = "usb:054c";
+                  }
+                ];
+                actions = {
+                  update-props = {
+                    # Set quality to high quality instead of the default of auto
+                    "bluez5.a2dp.ldac.quality" = "hq";
+                  };
+                };
+              }
+            ];
+          };
+        }'';
+        description = ''
+          Additional configuration for the WirePlumber daemon when run in
+          single-instance mode (the default in nixpkgs and currently the only
+          supported way to run WirePlumber configured via `extraConfig`).
+
+          See also:
+          - [The configuration file][docs-the-conf-file]
+          - [Modifying configuration][docs-modifying-config]
+          - [Locations of files][docs-file-locations]
+          - and the [configuration section][docs-config-section] of the docs in general
+
+          Note that WirePlumber (and PipeWire) use dotted attribute names like
+          `device.product.id`. These are not nested, but flat objects for WirePlumber/PipeWire,
+          so to write these in nix expressions, remember to quote them like `"device.product.id"`.
+          Have a look at the example for this.
+
+          [docs-the-conf-file]: https://pipewire.pages.freedesktop.org/wireplumber/daemon/configuration/conf_file.html
+          [docs-modifying-config]: https://pipewire.pages.freedesktop.org/wireplumber/daemon/configuration/modifying_configuration.html
+          [docs-file-locations]: https://pipewire.pages.freedesktop.org/wireplumber/daemon/configuration/locations.html
+          [docs-config-section]: https://pipewire.pages.freedesktop.org/wireplumber/daemon/configuration.html
+        '';
+      };
+
+      extraScripts = mkOption {
+        type = attrsOf lines;
+        default = { };
+        example = {
+          "test/hello-world.lua" = ''
+            print("Hello, world!")
+          '';
+        };
+        description = ''
+          Additional scripts for WirePlumber to be used by configuration files.
+
+          Every item in this attrset becomes a separate lua file with the path
+          relative to the `scripts` directory specified in the name of the item.
+          The scripts get passed to the WirePlumber service via the `XDG_DATA_DIRS`
+          variable. Scripts specified here are preferred over those shipped with
+          WirePlumber if they occupy the same relative path.
+
+          For a script to be loaded, it needs to be specified as part of a component,
+          and that component needs to be required by an active profile (e.g. `main`).
+          Components can be defined in config files either via `extraConfig` or `configPackages`.
+
+          For the hello-world example, you'd have to add the following `extraConfig`:
+          ```nix
+            services.pipewire.wireplumber.extraConfig."99-hello-world" = {
+              "wireplumber.components" = [
+                {
+                  name = "test/hello-world.lua";
+                  type = "script/lua";
+                  provides = "custom.hello-world";
+                }
+              ];
+
+              "wireplumber.profiles" = {
+                main = {
+                  "custom.hello-world" = "required";
+                };
+              };
+            };
+          ```
+
+          See also:
+          - [Location of scripts][docs-file-locations-scripts]
+          - [Components & Profiles][docs-components-profiles]
+          - [Migration - Loading custom scripts][docs-migration-loading-custom-scripts]
+
+          [docs-file-locations-scripts]: https://pipewire.pages.freedesktop.org/wireplumber/daemon/locations.html#location-of-scripts
+          [docs-components-profiles]: https://pipewire.pages.freedesktop.org/wireplumber/daemon/configuration/components_and_profiles.html
+          [docs-migration-loading-custom-scripts]: https://pipewire.pages.freedesktop.org/wireplumber/daemon/configuration/migration.html#loading-custom-scripts
+        '';
+      };
+
       configPackages = mkOption {
         type = listOf package;
         default = [ ];
@@ -57,7 +187,7 @@ in
 
       extraLv2Packages = mkOption {
         type = listOf package;
-        default = [];
+        default = [ ];
         example = literalExpression "[ pkgs.lsp-plugins ]";
         description = ''
           List of packages that provide LV2 plugins in `lib/lv2` that should
@@ -96,9 +226,22 @@ in
         }
       '';
 
+      extraConfigPkg = pkgs.buildEnv {
+        name = "wireplumber-extra-config";
+        paths = mapConfigToFiles cfg.extraConfig;
+        pathsToLink = [ "/share/wireplumber/wireplumber.conf.d" ];
+      };
+
+      extraScriptsPkg = pkgs.buildEnv {
+        name = "wireplumber-extra-scrips";
+        paths = mapScriptsToFiles cfg.extraScripts;
+        pathsToLink = [ "/share/wireplumber/scripts" ];
+      };
+
       configPackages = cfg.configPackages
-          ++ optional (!pwUsedForAudio) pwNotForAudioConfigPkg
-          ++ optional pwCfg.systemWide systemwideConfigPkg;
+        ++ [ extraConfigPkg extraScriptsPkg ]
+        ++ optional (!pwUsedForAudio) pwNotForAudioConfigPkg
+        ++ optional pwCfg.systemWide systemwideConfigPkg;
 
       configs = pkgs.buildEnv {
         name = "wireplumber-configs";
@@ -110,7 +253,7 @@ in
         (
           concatMap
             (p:
-              attrByPath ["passthru" "requiredLv2Packages"] [] p
+              attrByPath [ "passthru" "requiredLv2Packages" ] [ ] p
             )
             configPackages
         );
@@ -127,24 +270,10 @@ in
           assertion = !config.hardware.bluetooth.hsphfpd.enable;
           message = "Using WirePlumber conflicts with hsphfpd, as it provides the same functionality. `hardware.bluetooth.hsphfpd.enable` needs be set to false";
         }
-        {
-          assertion = length
-            (attrNames
-              (
-                filterAttrs
-                  (name: value:
-                    hasPrefix "wireplumber/" name || name == "wireplumber"
-                  )
-                  config.environment.etc
-              )) == 1;
-          message = "Using `environment.etc.\"wireplumber<...>\"` directly is no longer supported in 24.05. Use `services.pipewire.wireplumber.configPackages` instead.";
-        }
       ];
 
       environment.systemPackages = [ cfg.package ];
 
-      environment.etc.wireplumber.source = "${configs}/share/wireplumber";
-
       systemd.packages = [ cfg.package ];
 
       systemd.services.wireplumber.enable = pwCfg.systemWide;
@@ -156,10 +285,16 @@ in
       systemd.services.wireplumber.environment = mkIf pwCfg.systemWide {
         # Force WirePlumber to use system dbus.
         DBUS_SESSION_BUS_ADDRESS = "unix:path=/run/dbus/system_bus_socket";
+
+        # Make WirePlumber find our config/script files and lv2 plugins required by those
+        # (but also the configs/scripts shipped with WirePlumber)
+        XDG_DATA_DIRS = makeSearchPath "share" [ configs cfg.package ];
         LV2_PATH = "${lv2Plugins}/lib/lv2";
       };
 
-      systemd.user.services.wireplumber.environment.LV2_PATH =
-        mkIf (!pwCfg.systemWide) "${lv2Plugins}/lib/lv2";
+      systemd.user.services.wireplumber.environment = mkIf (!pwCfg.systemWide) {
+        XDG_DATA_DIRS = makeSearchPath "share" [ configs cfg.package ];
+        LV2_PATH = "${lv2Plugins}/lib/lv2";
+      };
     };
 }
diff --git a/nixos/modules/services/home-automation/ebusd.nix b/nixos/modules/services/home-automation/ebusd.nix
index aaf2ca1d775d5..d388022d7b50b 100644
--- a/nixos/modules/services/home-automation/ebusd.nix
+++ b/nixos/modules/services/home-automation/ebusd.nix
@@ -97,50 +97,50 @@ in
 
     logs = {
       main = mkOption {
-        type = types.enum [ "error" "notice" "info" "debug"];
+        type = types.enum [ "none" "error" "notice" "info" "debug"];
         default = "info";
         description = ''
-          Only write log for matching AREAs (main|network|bus|update|other|all) below or equal to LEVEL (error|notice|info|debug) [all:notice].
+          Only write log for matching AREAs (main|network|bus|update|other|all) below or equal to LEVEL (none|error|notice|info|debug) [all:notice].
         '';
       };
 
       network = mkOption {
-        type = types.enum [ "error" "notice" "info" "debug"];
+        type = types.enum [ "none" "error" "notice" "info" "debug"];
         default = "info";
         description = ''
-          Only write log for matching AREAs (main|network|bus|update|other|all) below or equal to LEVEL (error|notice|info|debug) [all:notice].
+          Only write log for matching AREAs (main|network|bus|update|other|all) below or equal to LEVEL (none|error|notice|info|debug) [all:notice].
         '';
       };
 
       bus = mkOption {
-        type = types.enum [ "error" "notice" "info" "debug"];
+        type = types.enum [ "none" "error" "notice" "info" "debug"];
         default = "info";
         description = ''
-          Only write log for matching AREAs (main|network|bus|update|other|all) below or equal to LEVEL (error|notice|info|debug) [all:notice].
+          Only write log for matching AREAs (main|network|bus|update|other|all) below or equal to LEVEL (none|error|notice|info|debug) [all:notice].
         '';
       };
 
       update = mkOption {
-        type = types.enum [ "error" "notice" "info" "debug"];
+        type = types.enum [ "none" "error" "notice" "info" "debug"];
         default = "info";
         description = ''
-          Only write log for matching AREAs (main|network|bus|update|other|all) below or equal to LEVEL (error|notice|info|debug) [all:notice].
+          Only write log for matching AREAs (main|network|bus|update|other|all) below or equal to LEVEL (none|error|notice|info|debug) [all:notice].
         '';
       };
 
       other = mkOption {
-        type = types.enum [ "error" "notice" "info" "debug"];
+        type = types.enum [ "none" "error" "notice" "info" "debug"];
         default = "info";
         description = ''
-          Only write log for matching AREAs (main|network|bus|update|other|all) below or equal to LEVEL (error|notice|info|debug) [all:notice].
+          Only write log for matching AREAs (main|network|bus|update|other|all) below or equal to LEVEL (none|error|notice|info|debug) [all:notice].
         '';
       };
 
       all = mkOption {
-        type = types.enum [ "error" "notice" "info" "debug"];
+        type = types.enum [ "none" "error" "notice" "info" "debug"];
         default = "info";
         description = ''
-          Only write log for matching AREAs (main|network|bus|update|other|all) below or equal to LEVEL (error|notice|info|debug) [all:notice].
+          Only write log for matching AREAs (main|network|bus|update|other|all) below or equal to LEVEL (none|error|notice|info|debug) [all:notice].
         '';
       };
     };
diff --git a/nixos/modules/services/home-automation/wyoming/faster-whisper.nix b/nixos/modules/services/home-automation/wyoming/faster-whisper.nix
index cbff3ab3e617c..d0fca6a41c7b6 100644
--- a/nixos/modules/services/home-automation/wyoming/faster-whisper.nix
+++ b/nixos/modules/services/home-automation/wyoming/faster-whisper.nix
@@ -119,6 +119,8 @@ in
         wantedBy = [
           "multi-user.target"
         ];
+        # https://github.com/rhasspy/wyoming-faster-whisper/issues/27
+        environment."HF_HUB_CACHE" = "/tmp";
         serviceConfig = {
           DynamicUser = true;
           User = "wyoming-faster-whisper";
diff --git a/nixos/modules/services/logging/logrotate.nix b/nixos/modules/services/logging/logrotate.nix
index 3e29828eba2f2..9344277fc1e02 100644
--- a/nixos/modules/services/logging/logrotate.nix
+++ b/nixos/modules/services/logging/logrotate.nix
@@ -224,7 +224,7 @@ in
           and users are replaced by dummy users), so tests are complemented by a
           logrotate-checkconf service that is enabled by default.
           This extra check can be disabled by disabling it at the systemd level with the
-          {option}`services.systemd.services.logrotate-checkconf.enable` option.
+          {option}`systemd.services.logrotate-checkconf.enable` option.
 
           Conversely there are still things that might make this check fail incorrectly
           (e.g. a file path where we don't have access to intermediate directories):
diff --git a/nixos/modules/services/mail/mailman.nix b/nixos/modules/services/mail/mailman.nix
index 7e7ca7e4060ec..180c9800d7345 100644
--- a/nixos/modules/services/mail/mailman.nix
+++ b/nixos/modules/services/mail/mailman.nix
@@ -534,14 +534,11 @@ in {
               hyperkittyApiKey=$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 64)
               secretKey=$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 64)
 
-              mailmanWebCfgTmp=$(mktemp)
-              jq -n '.MAILMAN_ARCHIVER_KEY=$archiver_key | .SECRET_KEY=$secret_key' \
+              install -m 0440 -o root -g mailman \
+                <(jq -n '.MAILMAN_ARCHIVER_KEY=$archiver_key | .SECRET_KEY=$secret_key' \
                   --arg archiver_key "$hyperkittyApiKey" \
-                  --arg secret_key "$secretKey" \
-                  >"$mailmanWebCfgTmp"
-              chown root:mailman "$mailmanWebCfgTmp"
-              chmod 440 "$mailmanWebCfgTmp"
-              mv -n "$mailmanWebCfgTmp" "$mailmanWebCfg"
+                  --arg secret_key "$secretKey") \
+                "$mailmanWebCfg"
           fi
 
           hyperkittyApiKey="$(jq -r .MAILMAN_ARCHIVER_KEY "$mailmanWebCfg")"
diff --git a/nixos/modules/services/monitoring/vmagent.nix b/nixos/modules/services/monitoring/vmagent.nix
index cdcf1571154e9..4838e0709d09e 100644
--- a/nixos/modules/services/monitoring/vmagent.nix
+++ b/nixos/modules/services/monitoring/vmagent.nix
@@ -1,63 +1,62 @@
 { config, pkgs, lib, ... }:
-with lib;
+
 let
   cfg = config.services.vmagent;
   settingsFormat = pkgs.formats.json { };
 in {
-  options.services.vmagent = {
-    enable = mkEnableOption "vmagent";
-
-    user = mkOption {
-      default = "vmagent";
-      type = types.str;
-      description = ''
-        User account under which vmagent runs.
-      '';
-    };
+  imports = [
+    (lib.mkRemovedOptionModule [ "services" "vmagent" "dataDir" ] "dataDir has been deprecated in favor of systemd provided CacheDirectory")
+    (lib.mkRemovedOptionModule [ "services" "vmagent" "user" ] "user has been deprecated in favor of systemd DynamicUser")
+    (lib.mkRemovedOptionModule [ "services" "vmagent" "group" ] "group has been deprecated in favor of systemd DynamicUser")
+    (lib.mkRenamedOptionModule [ "services" "vmagent" "remoteWriteUrl" ] [ "services" "vmagent" "remoteWrite" "url" ])
+  ];
 
-    group = mkOption {
-      type = types.str;
-      default = "vmagent";
-      description = ''
-        Group under which vmagent runs.
-      '';
-    };
-
-    package = mkPackageOption pkgs "vmagent" { };
+  options.services.vmagent = {
+    enable = lib.mkEnableOption "vmagent";
 
-    dataDir = mkOption {
-      type = types.str;
-      default = "/var/lib/vmagent";
-      description = ''
-        The directory where vmagent stores its data files.
-      '';
-    };
+    package = lib.mkPackageOption pkgs "vmagent" { };
 
-    remoteWriteUrl = mkOption {
-      default = "http://localhost:8428/api/v1/write";
-      type = types.str;
-      description = ''
-        The storage endpoint such as VictoriaMetrics
-      '';
+    remoteWrite = {
+      url = lib.mkOption {
+        default = null;
+        type = lib.types.nullOr lib.types.str;
+        description = ''
+          Endpoint for prometheus compatible remote_write
+        '';
+      };
+      basicAuthUsername = lib.mkOption {
+        default = null;
+        type = lib.types.nullOr lib.types.str;
+        description = ''
+          Basic Auth username used to connect to remote_write endpoint
+        '';
+      };
+      basicAuthPasswordFile = lib.mkOption {
+        default = null;
+        type = lib.types.nullOr lib.types.str;
+        description = ''
+          File that contains the Basic Auth password used to connect to remote_write endpoint
+        '';
+      };
     };
 
-    prometheusConfig = mkOption {
+    prometheusConfig = lib.mkOption {
       type = lib.types.submodule { freeformType = settingsFormat.type; };
       description = ''
         Config for prometheus style metrics
       '';
     };
 
-    openFirewall = mkOption {
-      type = types.bool;
+    openFirewall = lib.mkOption {
+      type = lib.types.bool;
       default = false;
       description = ''
         Whether to open the firewall for the default ports.
       '';
     };
 
-    extraArgs = mkOption {
-      type = types.listOf types.str;
+    extraArgs = lib.mkOption {
+      type = lib.types.listOf lib.types.str;
       default = [];
       description = ''
         Extra args to pass to `vmagent`. See the docs:
@@ -67,37 +66,36 @@ in {
     };
   };
 
-  config = mkIf cfg.enable {
-    users.groups = mkIf (cfg.group == "vmagent") { vmagent = { }; };
-
-    users.users = mkIf (cfg.user == "vmagent") {
-      vmagent = {
-        group = cfg.group;
-        description = "vmagent daemon user";
-        home = cfg.dataDir;
-        isSystemUser = true;
-      };
-    };
-
-    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ 8429 ];
+  config = lib.mkIf cfg.enable {
+    networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ 8429 ];
 
     systemd.services.vmagent = let
       prometheusConfig = settingsFormat.generate "prometheusConfig.yaml" cfg.prometheusConfig;
+      startCommandLine = lib.concatStringsSep " " ([
+        "${cfg.package}/bin/vmagent"
+        "-promscrape.config=${prometheusConfig}"
+      ] ++ cfg.extraArgs
+        ++ lib.optionals (cfg.remoteWrite.url != null) [
+        "-remoteWrite.url=${cfg.remoteWrite.url}"
+        "-remoteWrite.tmpDataPath=%C/vmagent/remote_write_tmp"
+      ] ++ lib.optional (cfg.remoteWrite.basicAuthUsername != null) "-remoteWrite.basicAuth.username=${cfg.remoteWrite.basicAuthUsername}"
+        ++ lib.optional (cfg.remoteWrite.basicAuthPasswordFile != null) "-remoteWrite.basicAuth.passwordFile=\${CREDENTIALS_DIRECTORY}/remote_write_basic_auth_password");
     in {
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
       description = "vmagent system service";
       serviceConfig = {
-        User = cfg.user;
-        Group = cfg.group;
+        DynamicUser = true;
+        User = "vmagent";
+        Group = "vmagent";
         Type = "simple";
         Restart = "on-failure";
-        WorkingDirectory = cfg.dataDir;
-        ExecStart = "${cfg.package}/bin/vmagent -remoteWrite.url=${cfg.remoteWriteUrl} -promscrape.config=${prometheusConfig} ${escapeShellArgs cfg.extraArgs}";
+        CacheDirectory = "vmagent";
+        ExecStart = startCommandLine;
+        LoadCredential = lib.optional (cfg.remoteWrite.basicAuthPasswordFile != null) [
+          "remote_write_basic_auth_password:${cfg.remoteWrite.basicAuthPasswordFile}"
+        ];
       };
     };
-
-    systemd.tmpfiles.rules =
-      [ "d '${cfg.dataDir}' 0755 ${cfg.user} ${cfg.group} -" ];
   };
 }
diff --git a/nixos/modules/services/networking/adguardhome.nix b/nixos/modules/services/networking/adguardhome.nix
index 6958bcccf54cf..df9927351edc3 100644
--- a/nixos/modules/services/networking/adguardhome.nix
+++ b/nixos/modules/services/networking/adguardhome.nix
@@ -4,6 +4,7 @@ with lib;
 
 let
   cfg = config.services.adguardhome;
+  settingsFormat = pkgs.formats.yaml { };
 
   args = concatStringsSep " " ([
     "--no-check-update"
@@ -12,27 +13,33 @@ let
     "--config /var/lib/AdGuardHome/AdGuardHome.yaml"
   ] ++ cfg.extraArgs);
 
-  configFile = pkgs.writeTextFile {
-    name = "AdGuardHome.yaml";
-    text = builtins.toJSON cfg.settings;
-    checkPhase = "${pkgs.adguardhome}/bin/adguardhome -c $out --check-config";
-  };
-  defaultBindPort = 3000;
-
-in
-{
-
-  imports =
-    let cfgPath = [ "services" "adguardhome" ];
-    in
-    [
-      (mkRenamedOptionModuleWith { sinceRelease = 2211; from = cfgPath ++ [ "host" ]; to = cfgPath ++ [ "settings" "bind_host" ]; })
-      (mkRenamedOptionModuleWith { sinceRelease = 2211; from = cfgPath ++ [ "port" ]; to = cfgPath ++ [ "settings" "bind_port" ]; })
-    ];
-
+  settings = if (cfg.settings != null) then
+    cfg.settings // (if cfg.settings.schema_version < 23 then {
+      bind_host = cfg.host;
+      bind_port = cfg.port;
+    } else {
+      http.address = "${cfg.host}:${toString cfg.port}";
+    })
+  else
+    null;
+
+  configFile =
+    (settingsFormat.generate "AdGuardHome.yaml" settings).overrideAttrs (_: {
+      checkPhase = "${cfg.package}/bin/adguardhome -c $out --check-config";
+    });
+in {
   options.services.adguardhome = with types; {
     enable = mkEnableOption "AdGuard Home network-wide ad blocker";
 
+    package = mkOption {
+      type = package;
+      default = pkgs.adguardhome;
+      defaultText = literalExpression "pkgs.adguardhome";
+      description = ''
+        The package that runs adguardhome.
+      '';
+    };
+
     openFirewall = mkOption {
       default = false;
       type = bool;
@@ -43,8 +50,8 @@ in
     };
 
     allowDHCP = mkOption {
-      default = cfg.settings.dhcp.enabled or false;
-      defaultText = literalExpression ''config.services.adguardhome.settings.dhcp.enabled or false'';
+      default = settings.dhcp.enabled or false;
+      defaultText = literalExpression "config.services.adguardhome.settings.dhcp.enabled or false";
       type = bool;
       description = ''
         Allows AdGuard Home to open raw sockets (`CAP_NET_RAW`), which is
@@ -65,32 +72,34 @@ in
       '';
     };
 
+    host = mkOption {
+      default = "0.0.0.0";
+      type = str;
+      description = ''
+        Host address to bind HTTP server to.
+      '';
+    };
+
+    port = mkOption {
+      default = 3000;
+      type = port;
+      description = ''
+        Port to serve HTTP pages on.
+      '';
+    };
+
     settings = mkOption {
       default = null;
       type = nullOr (submodule {
-        freeformType = (pkgs.formats.yaml { }).type;
+        freeformType = settingsFormat.type;
         options = {
           schema_version = mkOption {
-            default = pkgs.adguardhome.schema_version;
-            defaultText = literalExpression "pkgs.adguardhome.schema_version";
+            default = cfg.package.schema_version;
+            defaultText = literalExpression "cfg.package.schema_version";
             type = int;
             description = ''
               Schema version for the configuration.
-              Defaults to the `schema_version` supplied by `pkgs.adguardhome`.
-            '';
-          };
-          bind_host = mkOption {
-            default = "0.0.0.0";
-            type = str;
-            description = ''
-              Host address to bind HTTP server to.
-            '';
-          };
-          bind_port = mkOption {
-            default = defaultBindPort;
-            type = port;
-            description = ''
-              Port to serve HTTP pages on.
+              Defaults to the `schema_version` supplied by `cfg.package`.
             '';
           };
         };
@@ -107,7 +116,7 @@ in
 
         Set this to `null` (default) for a non-declarative configuration without any
         Nix-supplied values.
-        Declarative configurations are supplied with a default `schema_version`, `bind_host`, and `bind_port`.
+        Declarative configurations are supplied with a default `schema_version`, and `http.address`.
         :::
       '';
     };
@@ -124,17 +133,25 @@ in
   config = mkIf cfg.enable {
     assertions = [
       {
-        assertion = cfg.settings != null -> cfg.mutableSettings
-          || (hasAttrByPath [ "dns" "bind_host" ] cfg.settings)
-          || (hasAttrByPath [ "dns" "bind_hosts" ] cfg.settings);
-        message =
-          "AdGuard setting dns.bind_host or dns.bind_hosts needs to be configured for a minimal working configuration";
+        assertion = cfg.settings != null
+          -> !(hasAttrByPath [ "bind_host" ] cfg.settings);
+        message = "AdGuard option `settings.bind_host' has been superseded by `services.adguardhome.host'";
+      }
+      {
+        assertion = cfg.settings != null
+          -> !(hasAttrByPath [ "bind_port" ] cfg.settings);
+        message = "AdGuard option `settings.bind_host' has been superseded by `services.adguardhome.port'";
+      }
+      {
+        assertion = settings != null -> cfg.mutableSettings
+          || hasAttrByPath [ "dns" "bootstrap_dns" ] settings;
+        message = "AdGuard setting dns.bootstrap_dns needs to be configured for a minimal working configuration";
       }
       {
-        assertion = cfg.settings != null -> cfg.mutableSettings
-          || hasAttrByPath [ "dns" "bootstrap_dns" ] cfg.settings;
-        message =
-          "AdGuard setting dns.bootstrap_dns needs to be configured for a minimal working configuration";
+        assertion = settings != null -> cfg.mutableSettings
+          || hasAttrByPath [ "dns" "bootstrap_dns" ] settings
+          && isList settings.dns.bootstrap_dns;
+        message = "AdGuard setting dns.bootstrap_dns needs to be a list";
       }
     ];
 
@@ -147,7 +164,7 @@ in
         StartLimitBurst = 10;
       };
 
-      preStart = optionalString (cfg.settings != null) ''
+      preStart = optionalString (settings != null) ''
         if    [ -e "$STATE_DIRECTORY/AdGuardHome.yaml" ] \
            && [ "${toString cfg.mutableSettings}" = "1" ]; then
           # Writing directly to AdGuardHome.yaml results in empty file
@@ -161,8 +178,9 @@ in
 
       serviceConfig = {
         DynamicUser = true;
-        ExecStart = "${pkgs.adguardhome}/bin/adguardhome ${args}";
-        AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ] ++ optionals cfg.allowDHCP [ "CAP_NET_RAW" ];
+        ExecStart = "${cfg.package}/bin/adguardhome ${args}";
+        AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]
+          ++ optionals cfg.allowDHCP [ "CAP_NET_RAW" ];
         Restart = "always";
         RestartSec = 10;
         RuntimeDirectory = "AdGuardHome";
@@ -170,6 +188,6 @@ in
       };
     };
 
-    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.settings.bind_port or defaultBindPort ];
+    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
   };
 }
diff --git a/nixos/modules/services/networking/knot.nix b/nixos/modules/services/networking/knot.nix
index 89d3ea5e9626a..145b4ad1dd3f1 100644
--- a/nixos/modules/services/networking/knot.nix
+++ b/nixos/modules/services/networking/knot.nix
@@ -226,7 +226,7 @@ in {
       };
 
       settings = mkOption {
-        type = types.submodule { freeformType = types.attrs; };
+        type = (pkgs.formats.yaml {}).type;
         default = {};
         description = ''
           Extra configuration as nix values.
diff --git a/nixos/modules/services/security/vault.nix b/nixos/modules/services/security/vault.nix
index ab86da47b2e1c..650f9bda99c33 100644
--- a/nixos/modules/services/security/vault.nix
+++ b/nixos/modules/services/security/vault.nix
@@ -56,8 +56,8 @@ in
       };
 
       devRootTokenID = mkOption {
-        type = types.str;
-        default = false;
+        type = types.nullOr types.str;
+        default = null;
         description = ''
           Initial root token. This only applies when {option}`services.vault.dev` is true
         '';
diff --git a/nixos/modules/services/system/dbus.nix b/nixos/modules/services/system/dbus.nix
index a9fc88e320c6b..8dba0aca64337 100644
--- a/nixos/modules/services/system/dbus.nix
+++ b/nixos/modules/services/system/dbus.nix
@@ -39,7 +39,7 @@ in
 
       implementation = mkOption {
         type = types.enum [ "dbus" "broker" ];
-        default = "broker";
+        default = "dbus";
         description = ''
           The implementation to use for the message bus defined by the D-Bus specification.
           Can be either the classic dbus daemon or dbus-broker, which aims to provide high
diff --git a/nixos/modules/services/web-apps/coder.nix b/nixos/modules/services/web-apps/coder.nix
index d4a5b7b2b89cd..5450adbe118da 100644
--- a/nixos/modules/services/web-apps/coder.nix
+++ b/nixos/modules/services/web-apps/coder.nix
@@ -223,4 +223,5 @@ in {
       };
     };
   };
+  meta.maintainers = pkgs.coder.meta.maintainers;
 }
diff --git a/nixos/modules/services/web-apps/movim.nix b/nixos/modules/services/web-apps/movim.nix
index bb88a185b4618..29bed0e067fa4 100644
--- a/nixos/modules/services/web-apps/movim.nix
+++ b/nixos/modules/services/web-apps/movim.nix
@@ -103,22 +103,20 @@ let
           lib.concatStringsSep "\n" [
             (lib.optionalString brotli.enable ''
               echo -n "Precompressing static files with Brotli …"
-              find ${appDir}/public -type f ${findTextFileNames} \
-                | ${lib.getExe pkgs.parallel} ${lib.escapeShellArgs [
-                    "--will-cite"
-                    "-j $NIX_BUILD_CORES"
-                    "${lib.getExe brotli.package} --keep --quality=${builtins.toString brotli.compressionLevel} --output={}.br {}"
-                   ]}
+              find ${appDir}/public -type f ${findTextFileNames} -print0 \
+                | xargs -0 -n 1 -P $NIX_BUILD_CORES ${pkgs.writeShellScript "movim_precompress_broti" ''
+                    file="$1"
+                    ${lib.getExe brotli.package} --keep --quality=${builtins.toString brotli.compressionLevel} --output=$file.br $file
+                  ''}
               echo " done."
             '')
             (lib.optionalString gzip.enable ''
               echo -n "Precompressing static files with Gzip …"
-              find ${appDir}/public -type f ${findTextFileNames} \
-                | ${lib.getExe pkgs.parallel} ${lib.escapeShellArgs [
-                    "--will-cite"
-                    "-j $NIX_BUILD_CORES"
-                    "${lib.getExe gzip.package} -c -${builtins.toString gzip.compressionLevel} {} > {}.gz"
-                   ]}
+              find ${appDir}/public -type f ${findTextFileNames} -print0 \
+                | xargs -0 -n 1 -P $NIX_BUILD_CORES ${pkgs.writeShellScript "movim_precompress_broti" ''
+                    file="$1"
+                    ${lib.getExe gzip.package} -c -${builtins.toString gzip.compressionLevel} $file > $file.gz
+                  ''}
               echo " done."
             '')
           ];
diff --git a/nixos/modules/services/web-apps/nextcloud.md b/nixos/modules/services/web-apps/nextcloud.md
index 06a8712b0b8ae..ec860d307b381 100644
--- a/nixos/modules/services/web-apps/nextcloud.md
+++ b/nixos/modules/services/web-apps/nextcloud.md
@@ -5,7 +5,7 @@ self-hostable cloud platform. The server setup can be automated using
 [services.nextcloud](#opt-services.nextcloud.enable). A
 desktop client is packaged at `pkgs.nextcloud-client`.
 
-The current default by NixOS is `nextcloud28` which is also the latest
+The current default by NixOS is `nextcloud29` which is also the latest
 major version available.
 
 ## Basic usage {#module-services-nextcloud-basic-usage}
@@ -184,6 +184,32 @@ Alternatively, extra apps can also be declared with the [](#opt-services.nextclo
 When using this setting, apps can no longer be managed statefully because this can lead to Nextcloud updating apps
 that are managed by Nix. If you want automatic updates it is recommended that you use web interface to install apps.
 
+## Known warnings {#module-services-nextcloud-known-warnings}
+
+### Failed to get an iterator for log entries: Logreader application only supports "file" log_type {#module-services-nextcloud-warning-logreader}
+
+This is because
+
+* our module writes logs into the journal (`journalctl -t Nextcloud`)
+* the Logreader application that allows reading logs in the admin panel is enabled
+  by default and requires logs written to a file.
+
+The logreader application doesn't work, as it was the case before. The only change is that
+it complains loudly now. So nothing actionable here by default. Alternatively you can
+
+* disable the logreader application to shut up the "error".
+
+  We can't really do that by default since whether apps are enabled/disabled is part
+  of the application's state and tracked inside the database.
+
+* set [](#opt-services.nextcloud.settings.log_type) to "file" to be able to view logs
+  from the admin panel.
+
+### Your web server is not properly set up to resolve `.well-known` URLs, failed on: `/.well-known/caldav` {#module-services-nextcloud-warning-wellknown-caldav}
+
+This warning appearing seems to be an upstream issue and is being sorted out
+in [nextcloud/server#45033](https://github.com/nextcloud/server/issues/45033).
+
 ## Maintainer information {#module-services-nextcloud-maintainer-info}
 
 As stated in the previous paragraph, we must provide a clean upgrade-path for Nextcloud
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
index f179a9854eb91..21f76938f20c5 100644
--- a/nixos/modules/services/web-apps/nextcloud.nix
+++ b/nixos/modules/services/web-apps/nextcloud.nix
@@ -819,7 +819,8 @@ in {
         ++ (optional (versionOlder cfg.package.version "25") (upgradeWarning 24 "22.11"))
         ++ (optional (versionOlder cfg.package.version "26") (upgradeWarning 25 "23.05"))
         ++ (optional (versionOlder cfg.package.version "27") (upgradeWarning 26 "23.11"))
-        ++ (optional (versionOlder cfg.package.version "28") (upgradeWarning 27 "24.05"));
+        ++ (optional (versionOlder cfg.package.version "28") (upgradeWarning 27 "24.05"))
+        ++ (optional (versionOlder cfg.package.version "29") (upgradeWarning 28 "24.11"));
 
       services.nextcloud.package = with pkgs;
         mkDefault (
@@ -832,10 +833,12 @@ in {
           else if versionOlder stateVersion "23.05" then nextcloud25
           else if versionOlder stateVersion "23.11" then nextcloud26
           else if versionOlder stateVersion "24.05" then nextcloud27
-          else nextcloud28
+          else nextcloud29
         );
 
-      services.nextcloud.phpPackage = pkgs.php82;
+      services.nextcloud.phpPackage =
+        if versionOlder cfg.package.version "29" then pkgs.php82
+        else pkgs.php83;
 
       services.nextcloud.phpOptions = mkMerge [
         (mapAttrs (const mkOptionDefault) defaultPHPSettings)
diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix
index 337d53e869efe..08fab09e1e559 100644
--- a/nixos/modules/services/web-servers/nginx/default.nix
+++ b/nixos/modules/services/web-servers/nginx/default.nix
@@ -352,7 +352,8 @@ let
 
         # The acme-challenge location doesn't need to be added if we are not using any automated
         # certificate provisioning and can also be omitted when we use a certificate obtained via a DNS-01 challenge
-        acmeLocation = optionalString (vhost.enableACME || (vhost.useACMEHost != null && config.security.acme.certs.${vhost.useACMEHost}.dnsProvider == null))
+        acmeName = if vhost.useACMEHost != null then vhost.useACMEHost else vhostName;
+        acmeLocation = optionalString ((vhost.enableACME || vhost.useACMEHost != null) && config.security.acme.certs.${acmeName}.dnsProvider == null)
           # Rule for legitimate ACME Challenge requests (like /.well-known/acme-challenge/xxxxxxxxx)
           # We use ^~ here, so that we don't check any regexes (which could
           # otherwise easily override this intended match accidentally).
diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
index 8b218cf1a6b2f..cee8663f0040e 100644
--- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
@@ -7,8 +7,22 @@ let
 
   efi = config.boot.loader.efi;
 
+  # We check the source code in a derivation that does not depend on the
+  # system configuration so that most users don't have to redo the check and require
+  # the necessary dependencies.
+  checkedSource = pkgs.runCommand "systemd-boot" {
+    preferLocalBuild = true;
+  } ''
+    install -m755 -D ${./systemd-boot-builder.py} $out
+    ${lib.getExe pkgs.buildPackages.mypy} \
+      --no-implicit-optional \
+      --disallow-untyped-calls \
+      --disallow-untyped-defs \
+      $out
+  '';
+
   systemdBootBuilder = pkgs.substituteAll rec {
-    src = ./systemd-boot-builder.py;
+    src = checkedSource;
 
     isExecutable = true;
 
@@ -66,19 +80,9 @@ let
     '';
   };
 
-  checkedSystemdBootBuilder = pkgs.runCommand "systemd-boot" { } ''
-    mkdir -p $out/bin
-    install -m755 ${systemdBootBuilder} $out/bin/systemd-boot-builder
-    ${lib.getExe pkgs.buildPackages.mypy} \
-      --no-implicit-optional \
-      --disallow-untyped-calls \
-      --disallow-untyped-defs \
-      $out/bin/systemd-boot-builder
-  '';
-
   finalSystemdBootBuilder = pkgs.writeScript "install-systemd-boot.sh" ''
     #!${pkgs.runtimeShell}
-    ${checkedSystemdBootBuilder}/bin/systemd-boot-builder "$@"
+    ${systemdBootBuilder} "$@"
     ${cfg.extraInstallCommands}
   '';
 in {
diff --git a/nixos/modules/virtualisation/incus.nix b/nixos/modules/virtualisation/incus.nix
index 2d7ccac7d92c8..4d04853d20a56 100644
--- a/nixos/modules/virtualisation/incus.nix
+++ b/nixos/modules/virtualisation/incus.nix
@@ -9,7 +9,7 @@ let
   cfg = config.virtualisation.incus;
   preseedFormat = pkgs.formats.yaml { };
 
-  serverBinPath = ''${pkgs.qemu_kvm}/libexec:${
+  serverBinPath = ''/run/wrappers/bin:${pkgs.qemu_kvm}/libexec:${
     lib.makeBinPath (
       with pkgs;
       [
@@ -33,30 +33,41 @@ let
         gzip
         iproute2
         iptables
+        iw
         kmod
+        libnvidia-container
+        libxfs
         lvm2
         minio
+        minio-client
         nftables
-        qemu_kvm
         qemu-utils
+        qemu_kvm
         rsync
+        squashfs-tools-ng
         squashfsTools
+        sshfs
         swtpm
         systemd
         thin-provisioning-tools
         util-linux
         virtiofsd
+        xdelta
         xz
+      ]
+      ++ lib.optionals config.security.apparmor.enable [
+        apparmor-bin-utils
 
         (writeShellScriptBin "apparmor_parser" ''
           exec '${apparmor-parser}/bin/apparmor_parser' -I '${apparmor-profiles}/etc/apparmor.d' "$@"
         '')
       ]
+      ++ lib.optionals config.services.ceph.client.enable [ ceph-client ]
+      ++ lib.optionals config.virtualisation.vswitch.enable [ config.virtualisation.vswitch.package ]
       ++ lib.optionals config.boot.zfs.enabled [
         config.boot.zfs.package
         "${config.boot.zfs.package}/lib/udev"
       ]
-      ++ lib.optionals config.virtualisation.vswitch.enable [ config.virtualisation.vswitch.package ]
     )
   }'';
 
diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix
index 06c1253d1fb64..c30f4577fdd86 100644
--- a/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixos/modules/virtualisation/qemu-vm.nix
@@ -249,7 +249,7 @@ let
           ${concatStringsSep " " config.virtualisation.qemu.networkingOptions} \
           ${concatStringsSep " \\\n    "
             (mapAttrsToList
-              (tag: share: "-virtfs local,path=${share.source},security_model=none,mount_tag=${tag}")
+              (tag: share: "-virtfs local,path=${share.source},security_model=${share.securityModel},mount_tag=${tag}")
               config.virtualisation.sharedDirectories)} \
           ${drivesCmdLine config.virtualisation.qemu.drives} \
           ${concatStringsSep " \\\n    " config.virtualisation.qemu.options} \
@@ -462,6 +462,18 @@ in
               type = types.path;
               description = "The mount point of the directory inside the virtual machine";
             };
+            options.securityModel = mkOption {
+              type = types.enum [ "passthrough" "mapped-xattr" "mapped-file" "none" ];
+              default = "mapped-xattr";
+              description = ''
+                The security model to use for this share:
+
+                - `passthrough`: files are stored using the same credentials as they are created on the guest (this requires QEMU to run as root)
+                - `mapped-xattr`: some of the file attributes like uid, gid, mode bits and link target are stored as file attributes
+                - `mapped-file`: the attributes are stored in the hidden .virtfs_metadata directory. Directories exported by this security model cannot interact with other unix tools
+                - `none`: same as "passthrough" except the sever won't report failures if it fails to set file attributes like ownership
+              '';
+            };
           });
         default = { };
         example = {
@@ -1091,18 +1103,22 @@ in
       nix-store = mkIf cfg.mountHostNixStore {
         source = builtins.storeDir;
         target = "/nix/store";
+        securityModel = "none";
       };
       xchg = {
         source = ''"$TMPDIR"/xchg'';
+        securityModel = "none";
         target = "/tmp/xchg";
       };
       shared = {
         source = ''"''${SHARED_DIR:-$TMPDIR/xchg}"'';
         target = "/tmp/shared";
+        securityModel = "none";
       };
       certs = mkIf cfg.useHostCerts {
         source = ''"$TMPDIR"/certs'';
         target = "/etc/ssl/certs";
+        securityModel = "none";
       };
     };
 
diff --git a/nixos/tests/adguardhome.nix b/nixos/tests/adguardhome.nix
index 80613ce825340..005d54e17dfdc 100644
--- a/nixos/tests/adguardhome.nix
+++ b/nixos/tests/adguardhome.nix
@@ -2,41 +2,39 @@
   name = "adguardhome";
 
   nodes = {
-    nullConf = { ... }: { services.adguardhome = { enable = true; }; };
+    nullConf = { services.adguardhome.enable = true; };
 
-    emptyConf = { lib, ... }: {
+    emptyConf = {
       services.adguardhome = {
         enable = true;
+
+        settings = { };
+      };
+    };
+
+    schemaVersionBefore23 = {
+      services.adguardhome = {
+        enable = true;
+
+        settings.schema_version = 20;
       };
     };
 
-    declarativeConf = { ... }: {
+    declarativeConf = {
       services.adguardhome = {
         enable = true;
 
         mutableSettings = false;
-        settings = {
-          schema_version = 0;
-          dns = {
-            bind_host = "0.0.0.0";
-            bootstrap_dns = "127.0.0.1";
-          };
-        };
+        settings.dns.bootstrap_dns = [ "127.0.0.1" ];
       };
     };
 
-    mixedConf = { ... }: {
+    mixedConf = {
       services.adguardhome = {
         enable = true;
 
         mutableSettings = true;
-        settings = {
-          schema_version = 0;
-          dns = {
-            bind_host = "0.0.0.0";
-            bootstrap_dns = "127.0.0.1";
-          };
-        };
+        settings.dns.bootstrap_dns = [ "127.0.0.1" ];
       };
     };
 
@@ -70,11 +68,7 @@
         allowDHCP = true;
         mutableSettings = false;
         settings = {
-          schema_version = 0;
-          dns = {
-            bind_host = "0.0.0.0";
-            bootstrap_dns = "127.0.0.1";
-          };
+          dns.bootstrap_dns = [ "127.0.0.1" ];
           dhcp = {
             # This implicitly enables CAP_NET_RAW
             enabled = true;
@@ -104,33 +98,38 @@
 
   testScript = ''
     with subtest("Minimal (settings = null) config test"):
-        nullConf.wait_for_unit("adguardhome.service")
+      nullConf.wait_for_unit("adguardhome.service")
+      nullConf.wait_for_open_port(3000)
 
     with subtest("Default config test"):
-        emptyConf.wait_for_unit("adguardhome.service")
-        emptyConf.wait_for_open_port(3000)
+      emptyConf.wait_for_unit("adguardhome.service")
+      emptyConf.wait_for_open_port(3000)
+
+    with subtest("Default schema_version 23 config test"):
+      schemaVersionBefore23.wait_for_unit("adguardhome.service")
+      schemaVersionBefore23.wait_for_open_port(3000)
 
     with subtest("Declarative config test, DNS will be reachable"):
-        declarativeConf.wait_for_unit("adguardhome.service")
-        declarativeConf.wait_for_open_port(53)
-        declarativeConf.wait_for_open_port(3000)
+      declarativeConf.wait_for_unit("adguardhome.service")
+      declarativeConf.wait_for_open_port(53)
+      declarativeConf.wait_for_open_port(3000)
 
     with subtest("Mixed config test, check whether merging works"):
-        mixedConf.wait_for_unit("adguardhome.service")
-        mixedConf.wait_for_open_port(53)
-        mixedConf.wait_for_open_port(3000)
-        # Test whether merging works properly, even if nothing is changed
-        mixedConf.systemctl("restart adguardhome.service")
-        mixedConf.wait_for_unit("adguardhome.service")
-        mixedConf.wait_for_open_port(3000)
+      mixedConf.wait_for_unit("adguardhome.service")
+      mixedConf.wait_for_open_port(53)
+      mixedConf.wait_for_open_port(3000)
+      # Test whether merging works properly, even if nothing is changed
+      mixedConf.systemctl("restart adguardhome.service")
+      mixedConf.wait_for_unit("adguardhome.service")
+      mixedConf.wait_for_open_port(3000)
 
     with subtest("Testing successful DHCP start"):
-        dhcpConf.wait_for_unit("adguardhome.service")
-        client.systemctl("start network-online.target")
-        client.wait_for_unit("network-online.target")
-        # Test IP assignment via DHCP
-        dhcpConf.wait_until_succeeds("ping -c 5 10.0.10.100")
-        # Test hostname resolution over DHCP-provided DNS
-        dhcpConf.wait_until_succeeds("ping -c 5 client.lan")
+      dhcpConf.wait_for_unit("adguardhome.service")
+      client.systemctl("start network-online.target")
+      client.wait_for_unit("network-online.target")
+      # Test IP assignment via DHCP
+      dhcpConf.wait_until_succeeds("ping -c 5 10.0.10.100")
+      # Test hostname resolution over DHCP-provided DNS
+      dhcpConf.wait_until_succeeds("ping -c 5 client.lan")
   '';
 }
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 2c9d1aa568bf2..6ef1d8d537980 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -867,6 +867,7 @@ in {
   swap-partition = handleTest ./swap-partition.nix {};
   swap-random-encryption = handleTest ./swap-random-encryption.nix {};
   sway = handleTest ./sway.nix {};
+  swayfx = handleTest ./swayfx.nix {};
   switchTest = handleTest ./switch-test.nix {};
   sympa = handleTest ./sympa.nix {};
   syncthing = handleTest ./syncthing.nix {};
diff --git a/nixos/tests/coder.nix b/nixos/tests/coder.nix
index 12813827284b9..fd1fa0cc3031f 100644
--- a/nixos/tests/coder.nix
+++ b/nixos/tests/coder.nix
@@ -1,8 +1,6 @@
 import ./make-test-python.nix ({ pkgs, ... }: {
   name = "coder";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ shyim ghuntley ];
-  };
+  meta.maintainers = pkgs.coder.meta.maintainers;
 
   nodes.machine =
     { pkgs, ... }:
diff --git a/nixos/tests/installed-tests/default.nix b/nixos/tests/installed-tests/default.nix
index e87edb2007e93..b1ddfe3dcbd80 100644
--- a/nixos/tests/installed-tests/default.nix
+++ b/nixos/tests/installed-tests/default.nix
@@ -1,5 +1,5 @@
 # NixOS tests for gnome-desktop-testing-runner using software
-# See https://wiki.gnome.org/Initiatives/GnomeGoals/InstalledTests
+# See https://github.com/NixOS/nixpkgs/issues/34987
 
 { system ? builtins.currentSystem,
   config ? {},
diff --git a/nixos/tests/libreswan.nix b/nixos/tests/libreswan.nix
index aadba941fab17..c798a04645bc0 100644
--- a/nixos/tests/libreswan.nix
+++ b/nixos/tests/libreswan.nix
@@ -119,11 +119,11 @@ in
       with subtest("Libreswan is ready"):
           alice.wait_for_unit("ipsec")
           bob.wait_for_unit("ipsec")
-          alice.succeed("ipsec verify 1>&2")
+          alice.succeed("ipsec checkconfig")
 
       with subtest("Alice and Bob can start the tunnel"):
-          alice.execute("ipsec auto --start tunnel >&2 &")
-          bob.succeed("ipsec auto --start tunnel")
+          alice.execute("ipsec start tunnel >&2 &")
+          bob.succeed("ipsec start tunnel")
           # apparently this is needed to "wake" the tunnel
           bob.execute("ping -c1 alice")
 
diff --git a/nixos/tests/nextcloud/default.nix b/nixos/tests/nextcloud/default.nix
index 84ac371537271..d024adffd9f06 100644
--- a/nixos/tests/nextcloud/default.nix
+++ b/nixos/tests/nextcloud/default.nix
@@ -22,4 +22,4 @@ foldl
     };
   })
 { }
-  [ 26 27 28 ]
+  [ 27 28 29 ]
diff --git a/nixos/tests/openssh.nix b/nixos/tests/openssh.nix
index a039986621cab..2684b6f45e84e 100644
--- a/nixos/tests/openssh.nix
+++ b/nixos/tests/openssh.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, ... }:
 
 let inherit (import ./ssh-keys.nix pkgs)
-      snakeOilPrivateKey snakeOilPublicKey;
+      snakeOilPrivateKey snakeOilPublicKey snakeOilEd25519PrivateKey snakeOilEd25519PublicKey;
 in {
   name = "openssh";
   meta = with pkgs.lib.maintainers; {
@@ -108,6 +108,31 @@ in {
         };
       };
 
+    server-no-openssl =
+      { ... }:
+      {
+        programs.ssh.package = pkgs.opensshPackages.openssh.override {
+          linkOpenssl = false;
+        };
+        services.openssh = {
+          enable = true;
+          hostKeys = [
+            { type = "ed25519"; path = "/etc/ssh/ssh_host_ed25519_key"; }
+          ];
+          settings = {
+            # Must not specify the OpenSSL provided algorithms.
+            Ciphers = [ "chacha20-poly1305@openssh.com" ];
+            KexAlgorithms = [
+              "curve25519-sha256"
+              "curve25519-sha256@libssh.org"
+            ];
+          };
+        };
+        users.users.root.openssh.authorizedKeys.keys = [
+          snakeOilEd25519PublicKey
+        ];
+      };
+
     server-no-pam =
       { pkgs, ... }:
       {
@@ -139,6 +164,7 @@ in {
     server_allowed_users.wait_for_unit("sshd", timeout=30)
     server_localhost_only.wait_for_unit("sshd", timeout=30)
     server_match_rule.wait_for_unit("sshd", timeout=30)
+    server_no_openssl.wait_for_unit("sshd", timeout=30)
     server_no_pam.wait_for_unit("sshd", timeout=30)
 
     server_lazy.wait_for_unit("sshd.socket", timeout=30)
@@ -230,6 +256,16 @@ in {
             timeout=30
         )
 
+    with subtest("no-openssl"):
+        client.succeed(
+            "cat ${snakeOilEd25519PrivateKey} > privkey.snakeoil"
+        )
+        client.succeed("chmod 600 privkey.snakeoil")
+        client.succeed(
+            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil server-no-openssl true",
+            timeout=30
+        )
+
     with subtest("no-pam"):
         client.succeed(
             "cat ${snakeOilPrivateKey} > privkey.snakeoil"
diff --git a/nixos/tests/ssh-keys.nix b/nixos/tests/ssh-keys.nix
index df9ff38a3b22a..675f3a0b43947 100644
--- a/nixos/tests/ssh-keys.nix
+++ b/nixos/tests/ssh-keys.nix
@@ -12,4 +12,16 @@ pkgs:
     "yNTYAAABBBChdA2BmwcG49OrQN33f/sj+OHL5sJhwVl2Qim0vkUJQCry1zFpKTa"
     "9ZcDMiWaEhoAR6FGoaGI04ff7CS+1yybQ= snakeoil"
   ];
+
+  snakeOilEd25519PrivateKey = pkgs.writeText "privkey.snakeoil" ''
+    -----BEGIN OPENSSH PRIVATE KEY-----
+    b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+    QyNTUxOQAAACAYBTIWo1J4PkY4/7AhVyPT8xvAUI67tp+yYFFRdSm7+QAAAJC89yCivPcg
+    ogAAAAtzc2gtZWQyNTUxOQAAACAYBTIWo1J4PkY4/7AhVyPT8xvAUI67tp+yYFFRdSm7+Q
+    AAAEDJmKp3lX6Pz0unTc0QZwrHb8Eyr9fJUopE9d2/+q+eCxgFMhajUng+Rjj/sCFXI9Pz
+    G8BQjru2n7JgUVF1Kbv5AAAACnRvbUBvemRlc2sBAgM=
+    -----END OPENSSH PRIVATE KEY-----
+  '';
+
+  snakeOilEd25519PublicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBgFMhajUng+Rjj/sCFXI9PzG8BQjru2n7JgUVF1Kbv5 snakeoil";
 }
diff --git a/nixos/tests/swayfx.nix b/nixos/tests/swayfx.nix
new file mode 100644
index 0000000000000..77844ec80ae1d
--- /dev/null
+++ b/nixos/tests/swayfx.nix
@@ -0,0 +1,207 @@
+import ./make-test-python.nix (
+  { pkgs, lib, ... }:
+  {
+    name = "swayfx";
+    meta = {
+      maintainers = with lib.maintainers; [ eclairevoyant ];
+    };
+
+    # testScriptWithTypes:49: error: Cannot call function of unknown type
+    #           (machine.succeed if succeed else machine.execute)(
+    #           ^
+    # Found 1 error in 1 file (checked 1 source file)
+    skipTypeCheck = true;
+
+    nodes.machine =
+      { config, ... }:
+      {
+        # Automatically login on tty1 as a normal user:
+        imports = [ ./common/user-account.nix ];
+        services.getty.autologinUser = "alice";
+
+        environment = {
+          # For glinfo and wayland-info:
+          systemPackages = with pkgs; [
+            mesa-demos
+            wayland-utils
+            alacritty
+          ];
+          # Use a fixed SWAYSOCK path (for swaymsg):
+          variables = {
+            "SWAYSOCK" = "/tmp/sway-ipc.sock";
+            # TODO: Investigate if we can get hardware acceleration to work (via
+            # virtio-gpu and Virgil). We currently have to use the Pixman software
+            # renderer since the GLES2 renderer doesn't work inside the VM (even
+            # with WLR_RENDERER_ALLOW_SOFTWARE):
+            # "WLR_RENDERER_ALLOW_SOFTWARE" = "1";
+            "WLR_RENDERER" = "pixman";
+          };
+          # For convenience:
+          shellAliases = {
+            test-x11 = "glinfo | tee /tmp/test-x11.out && touch /tmp/test-x11-exit-ok";
+            test-wayland = "wayland-info | tee /tmp/test-wayland.out && touch /tmp/test-wayland-exit-ok";
+          };
+
+          # To help with OCR:
+          etc."xdg/foot/foot.ini".text = lib.generators.toINI { } {
+            main = {
+              font = "inconsolata:size=14";
+            };
+            colors = rec {
+              foreground = "000000";
+              background = "ffffff";
+              regular2 = foreground;
+            };
+          };
+
+          etc."gpg-agent.conf".text = ''
+            pinentry-timeout 86400
+          '';
+        };
+
+        fonts.packages = [ pkgs.inconsolata ];
+
+        # Automatically configure and start Sway when logging in on tty1:
+        programs.bash.loginShellInit = ''
+          if [ "$(tty)" = "/dev/tty1" ]; then
+            set -e
+
+            mkdir -p ~/.config/sway
+            sed s/Mod4/Mod1/ /etc/sway/config > ~/.config/sway/config
+
+            sway --validate
+            sway && touch /tmp/sway-exit-ok
+          fi
+        '';
+
+        programs.sway = {
+          enable = true;
+          package = pkgs.swayfx.override { isNixOS = true; };
+        };
+
+        # To test pinentry via gpg-agent:
+        programs.gnupg.agent.enable = true;
+
+        # Need to switch to a different GPU driver than the default one (-vga std) so that Sway can launch:
+        virtualisation.qemu.options = [ "-vga none -device virtio-gpu-pci" ];
+      };
+
+    testScript =
+      { nodes, ... }:
+      ''
+        import shlex
+        import json
+
+        q = shlex.quote
+        NODE_GROUPS = ["nodes", "floating_nodes"]
+
+
+        def swaymsg(command: str = "", succeed=True, type="command"):
+            assert command != "" or type != "command", "Must specify command or type"
+            shell = q(f"swaymsg -t {q(type)} -- {q(command)}")
+            with machine.nested(
+                f"sending swaymsg {shell!r}" + " (allowed to fail)" * (not succeed)
+            ):
+                ret = (machine.succeed if succeed else machine.execute)(
+                    f"su - alice -c {shell}"
+                )
+
+            # execute also returns a status code, but disregard.
+            if not succeed:
+                _, ret = ret
+
+            if not succeed and not ret:
+                return None
+
+            parsed = json.loads(ret)
+            return parsed
+
+
+        def walk(tree):
+            yield tree
+            for group in NODE_GROUPS:
+                for node in tree.get(group, []):
+                    yield from walk(node)
+
+
+        def wait_for_window(pattern):
+            def func(last_chance):
+                nodes = (node["name"] for node in walk(swaymsg(type="get_tree")))
+
+                if last_chance:
+                    nodes = list(nodes)
+                    machine.log(f"Last call! Current list of windows: {nodes}")
+
+                return any(pattern in name for name in nodes)
+
+            retry(func)
+
+        start_all()
+        machine.wait_for_unit("multi-user.target")
+
+        # To check the version:
+        print(machine.succeed("sway --version"))
+
+        # Wait for Sway to complete startup:
+        machine.wait_for_file("/run/user/1000/wayland-1")
+        machine.wait_for_file("/tmp/sway-ipc.sock")
+
+        # Test XWayland (foot does not support X):
+        swaymsg("exec WINIT_UNIX_BACKEND=x11 WAYLAND_DISPLAY= alacritty")
+        wait_for_window("alice@machine")
+        machine.send_chars("test-x11\n")
+        machine.wait_for_file("/tmp/test-x11-exit-ok")
+        print(machine.succeed("cat /tmp/test-x11.out"))
+        machine.copy_from_vm("/tmp/test-x11.out")
+        machine.screenshot("alacritty_glinfo")
+        machine.succeed("pkill alacritty")
+
+        # Start a terminal (foot) on workspace 3:
+        machine.send_key("alt-3")
+        machine.sleep(3)
+        machine.send_key("alt-ret")
+        wait_for_window("alice@machine")
+        machine.send_chars("test-wayland\n")
+        machine.wait_for_file("/tmp/test-wayland-exit-ok")
+        print(machine.succeed("cat /tmp/test-wayland.out"))
+        machine.copy_from_vm("/tmp/test-wayland.out")
+        machine.screenshot("foot_wayland_info")
+        machine.send_key("alt-shift-q")
+        machine.wait_until_fails("pgrep foot")
+
+        # Test gpg-agent starting pinentry-gnome3 via D-Bus (tests if
+        # $WAYLAND_DISPLAY is correctly imported into the D-Bus user env):
+        swaymsg("exec mkdir -p ~/.gnupg")
+        swaymsg("exec cp /etc/gpg-agent.conf ~/.gnupg")
+
+        swaymsg("exec DISPLAY=INVALID gpg --no-tty --yes --quick-generate-key test", succeed=False)
+        machine.wait_until_succeeds("pgrep --exact gpg")
+        wait_for_window("gpg")
+        machine.succeed("pgrep --exact gpg")
+        machine.screenshot("gpg_pinentry")
+        machine.send_key("alt-shift-q")
+        machine.wait_until_fails("pgrep --exact gpg")
+
+        # Test swaynag:
+        def get_height():
+            return [node['rect']['height'] for node in walk(swaymsg(type="get_tree")) if node['focused']][0]
+
+        before = get_height()
+        machine.send_key("alt-shift-e")
+        retry(lambda _: get_height() < before)
+        machine.screenshot("sway_exit")
+
+        swaymsg("exec swaylock")
+        machine.wait_until_succeeds("pgrep -x swaylock")
+        machine.sleep(3)
+        machine.send_chars("${nodes.machine.config.users.users.alice.password}")
+        machine.send_key("ret")
+        machine.wait_until_fails("pgrep -x swaylock")
+
+        # Exit Sway and verify process exit status 0:
+        swaymsg("exit", succeed=False)
+        machine.wait_until_fails("pgrep -x sway")
+        machine.wait_for_file("/tmp/sway-exit-ok")
+      '';
+  }
+)
diff --git a/nixos/tests/switch-test.nix b/nixos/tests/switch-test.nix
index a57d66f82eac9..4a7bcd5a82264 100644
--- a/nixos/tests/switch-test.nix
+++ b/nixos/tests/switch-test.nix
@@ -610,6 +610,11 @@ in {
     # Returns a comma separated representation of the given list in sorted
     # order, that matches the output format of switch-to-configuration.pl
     sortedUnits = xs: lib.concatStringsSep ", " (builtins.sort builtins.lessThan xs);
+
+    dbusService = {
+      "dbus" = "dbus.service";
+      "broker" = "dbus-broker.service";
+    }.${nodes.machine.services.dbus.implementation};
   in /* python */ ''
     def switch_to_specialisation(system, name, action="test", fail=False):
         if name == "":
@@ -691,9 +696,9 @@ in {
     with subtest("continuing from an aborted switch"):
         # An aborted switch will write into a file what it tried to start
         # and a second switch should continue from this
-        machine.succeed("echo dbus-broker.service > /run/nixos/start-list")
+        machine.succeed("echo ${dbusService} > /run/nixos/start-list")
         out = switch_to_specialisation("${machine}", "modifiedSystemConf")
-        assert_contains(out, "starting the following units: dbus-broker.service\n")
+        assert_contains(out, "starting the following units: ${dbusService}\n")
 
     with subtest("fstab mounts"):
         switch_to_specialisation("${machine}", "")
@@ -732,7 +737,7 @@ in {
         out = switch_to_specialisation("${machine}", "")
         assert_contains(out, "stopping the following units: test.mount\n")
         assert_lacks(out, "NOT restarting the following changed units:")
-        assert_contains(out, "reloading the following units: dbus-broker.service\n")
+        assert_contains(out, "reloading the following units: ${dbusService}\n")
         assert_lacks(out, "\nrestarting the following units:")
         assert_lacks(out, "\nstarting the following units:")
         assert_lacks(out, "the following new units were started:")
@@ -740,7 +745,7 @@ in {
         out = switch_to_specialisation("${machine}", "storeMountModified")
         assert_lacks(out, "stopping the following units:")
         assert_contains(out, "NOT restarting the following changed units: -.mount")
-        assert_contains(out, "reloading the following units: dbus-broker.service\n")
+        assert_contains(out, "reloading the following units: ${dbusService}\n")
         assert_lacks(out, "\nrestarting the following units:")
         assert_lacks(out, "\nstarting the following units:")
         assert_lacks(out, "the following new units were started:")
@@ -751,7 +756,7 @@ in {
         out = switch_to_specialisation("${machine}", "swap")
         assert_lacks(out, "stopping the following units:")
         assert_lacks(out, "NOT restarting the following changed units:")
-        assert_contains(out, "reloading the following units: dbus-broker.service\n")
+        assert_contains(out, "reloading the following units: ${dbusService}\n")
         assert_lacks(out, "\nrestarting the following units:")
         assert_lacks(out, "\nstarting the following units:")
         assert_contains(out, "the following new units were started: swapfile.swap")
@@ -760,7 +765,7 @@ in {
         assert_contains(out, "stopping swap device: /swapfile")
         assert_lacks(out, "stopping the following units:")
         assert_lacks(out, "NOT restarting the following changed units:")
-        assert_contains(out, "reloading the following units: dbus-broker.service\n")
+        assert_contains(out, "reloading the following units: ${dbusService}\n")
         assert_lacks(out, "\nrestarting the following units:")
         assert_lacks(out, "\nstarting the following units:")
         assert_lacks(out, "the following new units were started:")
@@ -781,7 +786,7 @@ in {
         assert_lacks(out, "installing dummy bootloader")  # test does not install a bootloader
         assert_lacks(out, "stopping the following units:")
         assert_lacks(out, "NOT restarting the following changed units:")
-        assert_contains(out, "reloading the following units: dbus-broker.service\n")  # huh
+        assert_contains(out, "reloading the following units: ${dbusService}\n")  # huh
         assert_lacks(out, "\nrestarting the following units:")
         assert_lacks(out, "\nstarting the following units:")
         assert_contains(out, "the following new units were started: test.service\n")
@@ -858,7 +863,7 @@ in {
         assert_lacks(out, "installing dummy bootloader")  # test does not install a bootloader
         assert_lacks(out, "stopping the following units:")
         assert_lacks(out, "NOT restarting the following changed units:")
-        assert_contains(out, "reloading the following units: dbus-broker.service\n")  # huh
+        assert_contains(out, "reloading the following units: ${dbusService}\n")  # huh
         assert_lacks(out, "\nrestarting the following units:")
         assert_lacks(out, "\nstarting the following units:")
         assert_contains(out, "the following new units were started: test.service\n")